본문 바로가기
Tech : 데이터 분석/by ChatGPT

Flutter WebView 앱 제작 완벽 가이드

by 프롭테크 2025. 9. 5.
반응형

Flutter WebView 앱 제작 완벽 가이드

1. 사전 준비

1.1 Flutter SDK 설치

  1. https://flutter.dev/docs/get-started/install 접속
  2. 운영체제에 맞는 Flutter SDK 다운로드
  3. 압축 해제 후 환경변수 PATH에 flutter/bin 경로 추가

1.2 개발 환경 설정

  • Android Studio 설치 (Android 개발용)
  • Xcode 설치 (iOS 개발용, macOS만)
  • VS Code 설치 (권장 에디터)

1.3 설치 확인

터미널에서 다음 명령어 실행:

flutter doctor

2. 프로젝트 생성

2.1 새 프로젝트 만들기

flutter create webview_app
cd webview_app

2.2 WebView 플러그인 추가

pubspec.yaml 파일을 열고 dependencies 섹션에 추가:

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.4.2
  connectivity_plus: ^5.0.2
  url_launcher: ^6.2.2

터미널에서 의존성 설치:

flutter pub get

3. 코드 작성

3.1 main.dart 파일 수정

lib/main.dart 파일을 다음 코드로 완전히 교체:

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(WebViewApp());
}

class WebViewApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My WebView App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: WebViewScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class WebViewScreen extends StatefulWidget {
  @override
  _WebViewScreenState createState() => _WebViewScreenState();
}

class _WebViewScreenState extends State<WebViewScreen> {
  // ⭐ 여기에 원하는 웹사이트 URL 입력 ⭐
  static const String WEBSITE_URL = 'https://example.com';

  late WebViewController _controller;
  bool _isLoading = true;
  bool _hasError = false;
  String _currentUrl = WEBSITE_URL;

  @override
  void initState() {
    super.initState();
    _initWebView();
  }

  void _initWebView() {
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            if (progress == 100) {
              setState(() {
                _isLoading = false;
              });
            }
          },
          onPageStarted: (String url) {
            setState(() {
              _isLoading = true;
              _hasError = false;
              _currentUrl = url;
            });
          },
          onPageFinished: (String url) {
            setState(() {
              _isLoading = false;
            });
          },
          onWebResourceError: (WebResourceError error) {
            setState(() {
              _hasError = true;
              _isLoading = false;
            });
          },
          onNavigationRequest: (NavigationRequest request) {
            // 외부 링크 처리
            if (_shouldOpenExternally(request.url)) {
              _launchURL(request.url);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(WEBSITE_URL));
  }

  bool _shouldOpenExternally(String url) {
    // 외부 브라우저에서 열어야 할 URL 패턴
    return url.contains('tel:') || 
           url.contains('mailto:') || 
           url.contains('sms:') ||
           url.contains('market://') ||
           url.contains('intent://');
  }

  Future<void> _launchURL(String url) async {
    if (await canLaunchUrl(Uri.parse(url))) {
      await launchUrl(Uri.parse(url));
    }
  }

  Future<void> _refresh() async {
    await _controller.reload();
  }

  Future<bool> _onWillPop() async {
    if (await _controller.canGoBack()) {
      _controller.goBack();
      return false;
    }
    return true;
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: _onWillPop,
      child: Scaffold(
        appBar: AppBar(
          title: Text('My App'),
          backgroundColor: Colors.blue,
          foregroundColor: Colors.white,
          elevation: 0,
          actions: [
            IconButton(
              icon: Icon(Icons.refresh),
              onPressed: _refresh,
            ),
          ],
        ),
        body: Stack(
          children: [
            if (_hasError)
              _buildErrorWidget()
            else
              WebViewWidget(controller: _controller),

            if (_isLoading)
              Center(
                child: CircularProgressIndicator(
                  color: Colors.blue,
                ),
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildErrorWidget() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.error_outline,
            size: 64,
            color: Colors.red,
          ),
          SizedBox(height: 16),
          Text(
            '페이지를 불러올 수 없습니다',
            style: TextStyle(fontSize: 18),
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: _refresh,
            child: Text('다시 시도'),
          ),
        ],
      ),
    );
  }
}

4. 앱 설정 커스터마이징

4.1 앱 이름 변경

pubspec.yaml 파일에서:

name: your_app_name
description: Your app description

4.2 Android 설정

android/app/src/main/AndroidManifest.xml:

<application
    android:label="Your App Name"
    android:name="${applicationName}"
    android:icon="@mipmap/ic_launcher"
    android:usesCleartextTraffic="true">

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:launchMode="singleTop"
        android:theme="@style/LaunchTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize">

인터넷 권한 추가:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

4.3 iOS 설정

ios/Runner/Info.plist에 추가:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

5. 앱 아이콘 변경

5.1 flutter_launcher_icons 플러그인 설치

pubspec.yaml에 추가:

dev_dependencies:
  flutter_launcher_icons: ^0.13.1

flutter_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/icon.png"
  min_sdk_android: 21

5.2 아이콘 생성

  1. 1024x1024 PNG 이미지 준비
  2. assets/icon/ 폴더 생성
  3. 이미지를 icon.png로 저장
  4. 터미널에서 실행:
    flutter pub run flutter_launcher_icons:main

6. 빌드 및 배포

6.1 Android APK 빌드

# 디버그 버전
flutter build apk

# 릴리즈 버전
flutter build apk --release

6.2 iOS 빌드 (macOS 전용)

flutter build ios --release

6.3 앱스토어 업로드용 빌드

# Android
flutter build appbundle --release

# iOS
flutter build ipa --release

7. URL 변경 방법

단일 URL 변경

main.dart 파일에서 다음 줄만 수정:

static const String WEBSITE_URL = 'https://your-new-website.com';

여러 웹사이트용 템플릿 만들기

  1. 프로젝트 복사
  2. URL 변경
  3. 앱 이름, 패키지명, 아이콘 변경
  4. 빌드

8. 고급 기능 추가

8.1 푸시 알림

pubspec.yaml에 추가:

firebase_messaging: ^14.7.9

8.2 오프라인 캐싱

pubspec.yaml에 추가:

flutter_cache_manager: ^3.3.1

8.3 진동 피드백

pubspec.yaml에 추가:

vibration: ^1.8.4

9. 트러블슈팅

자주 발생하는 오류

  1. Build 실패: flutter cleanflutter pub get 실행
  2. WebView 빈 화면: AndroidManifest.xml의 인터넷 권한 확인
  3. iOS 빌드 실패: Info.plist의 ATS 설정 확인

성능 최적화

  • 이미지 최적화
  • 캐시 설정 조정
  • 메모리 사용량 모니터링

10. 체크리스트

배포 전 확인사항:

  • URL 정상 작동 확인
  • 앱 이름 변경 완료
  • 아이콘 교체 완료
  • 권한 설정 완료
  • 테스트 기기에서 정상 작동 확인
  • 앱스토어 정책 준수 확인

완료! 이제 Flutter WebView 앱이 준비되었습니다.

반응형

댓글