Today I Learned 8회차 - [플루터] 블로그 웹 앱 만들기

3 분 소요

블로그 웹 앱 만들기

학습 목표

모바일이 지원되는 웹 사이트를 코드 몇 줄만으로 손쉽게 웹 앱으로 만들 수 있습니다. 이번 장에서는 웹 사이트를 앱으로 포장하는 방법을 배워봅시다.

프로젝트 구상하기

이번 앱을 구현하려면 두 가지 개념을 배워야 합니다.

  1. 웹뷰(WebView) - 앱 내에서 웹 브라우저 기능을 제공
  2. 앱바(AppBar) - 앱의 상단 네비게이션 바

사전 지식

주요 콜백 함수

  • onPageFinished(): 웹뷰에서 페이지 로딩이 완료된 뒤에 실행되는 콜백 함수
  • onPageStarted(): 페이지 로딩이 시작될 때 실행되는 콜백 함수
  • onProgress(): 페이지 로딩 진행상황을 모니터링하는 콜백 함수

웹뷰 위젯

웹뷰란?

  • 웹뷰는 프레임워크에 내장된 브라우저를 앱의 네이티브 컴포넌트에 임베딩하는 기능입니다.
  • 앱에서 웹 브라우저 기능을 구현해주는 기술로, HTML, CSS, JavaScript로 만들어진 웹 콘텐츠를 앱 내에서 직접 디스플레이할 수 있습니다.

웹뷰의 장단점

장점:

  • 기존 웹 사이트를 빠르게 앱으로 변환 가능
  • 웹 개발 기술을 재사용 가능
  • 플랫폼 간 일관된 UI/UX 제공

단점:

  • 네이티브 컴포넌트대비 성능이 떨어짐
  • 애니메이션이 부자연스러울 수 있음
  • 네이티브 기능 접근에 제약이 있음

Flutter WebView 사용법

  • 플루터에서는 webview_flutter 플러그인을 통해 웹뷰 기능을 제공합니다.
  • WebViewWidget의 controller 파라미터에 WebViewController 인스턴스를 입력합니다.
  • WebViewController는 웹뷰 위젯을 제어하는데 필요한 다양한 기능들을 제공합니다.

WebViewController 주요 함수

함수명 설명 사용 예시
setJavaScriptMode 웹뷰에서 JavaScript 실행을 허용할지 여부 설정 JavaScriptMode.unrestricted
setBackgroundColor 웹뷰의 배경색을 지정 Colors.white
loadRequest 지정된 URL로 웹페이지 이동 Uri.parse('https://example.com')
setNavigationDelegate 내비게이션 이벤트 콜백 함수 설정 onProgress, onPageStarted, onPageFinished
goBack 이전 페이지로 되돌아가기 내비게이션 버튼에 사용
goForward 다음 페이지로 이동 내비게이션 버튼에 사용
reload 현재 페이지 새로고침 새로고침 버튼에 사용

안드로이드와 iOS 네이티브 설정

  • 네이티브 설정이 필요한 플러그인은 보통 플러그인 홈페이지에 설정법이 상세히 기재

    Android 주요 권한 설정

권한 코드 설명 사용 예시
INTERNET 인터넷 연결 권한 웹뷰, API 호출
CAMERA 카메라 사용 권한 사진 촬영, QR코드 스캔
WRITE_EXTERNAL_STORAGE 외부 저장소 쓰기 권한 파일 다운로드, 이미지 저장
READ_EXTERNAL_STORAGE 외부 저장소 읽기 권한 이미지/비디오 업로드
VIBRATE 진동 기능 사용 알림, 피드백
ACCESS_FINE_LOCATION 정밀 위치 정보 접근 GPS 기반 위치 서비스
ACCESS_COARSE_LOCATION 대략적 위치 정보 접근 네트워크 기반 위치
ACCESS_BACKGROUND_LOCATION 백그라운드 위치 접근 위치 추적 앱
BILLING 앱 내 결제 기능 Google Play Billing
CALL_PHONE 전화 걸기 기능 전화번호 직접 다이얼
ACCESS_NETWORK_STATE 네트워크 상태 확인 인터넷 연결 상태 체크
RECORD_AUDIO 음성 녹음 권한 마이크 사용, 음성 메모

HTTP 프로토콜 웹사이트 허용 설정

Android 설정 (android/app/src/main/AndroidManifest.xml):

<application
    android:usesCleartextTraffic="true">
    <!-- 다른 설정들... -->
</application>

iOS 설정 (ios/Runner/Info.plist):

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>
</dict>

iOS Info.plist 개념

  • Info.plist 파일은 iOS 앱의 런타임 설정을 정의하는 파일입니다.
  • 주로 앱에서 이미지, 카메라, 마이크 등의 권한을 요청할 때 사용자에게 보여줄 메시지를 정의합니다.

권한 메시지 설정 예시:

<key>NSAppleMusicUsageDescription</key>
<string>음악을 재생하기 위해 미디어 라이브러리 접근 권한이 필요합니다.</string>

<key>NSCameraUsageDescription</key>
<string>사진 촬영을 위해 카메라 접근 권한이 필요합니다.</string>

## 프로젝트 초기 설정

### 폴더 구조 및 생성자 최적화

#### 화면 파일 관리
* 화면과 관련된 모든 위젯을 `screen` 폴더에 모아두어 프로젝트 구조를 체계적으로 관리합니다.

#### const 인스턴스 최적화
* 생성자 앞에 `const` 키워드를 추가하면 컴파일 시점에 인스턴스가 생성됩니다.
* 한 번 생성된 const 인스턴스 위젯은 메모리에서 재활용되어 성능이 향상됩니다.

#### Import 경로 규칙

**내부 파일 Import:**
```dart
import 'package:blog_web_app/screen/home_screen.dart';
// blog_web_app: 프로젝트의 이름 (pubspec.yaml에 정의)
// screen: lib 폴더로부터의 상대 경로
// home_screen.dart: 실제 파일명

외부 플러그인 Import:

import 'package:webview_flutter/webview_flutter.dart';
// 일반적으로 'package:[플러그인_이름]/[메인_파일].dart' 형식

main.dart 파일 수정하기

Flutter 초기화 과정 이해

  • runApp() 함수는 내부적으로 WidgetsFlutterBinding.ensureInitialized()를 호출합니다.
  • ensureInitialized() 함수는 Flutter 프레임워크가 앱을 실행할 준비가 완료되었는지 확인하는 역할을 합니다.

WebViewController 인스턴스화 주의사항

  • StatelessWidget에서 WebViewController를 프로퍼티로 직접 인스턴스화하려면 ensureInitialized() 함수를 먼저 호출해야 합니다.

대안 방법:

  1. main() 함수에서 직접 호출: WidgetsFlutterBinding.ensureInitialized() 먼저 실행
  2. StatefulWidget 사용: initState() 메서드에서 WebViewController 인스턴스 생성

완성 화면

소스 코드 구현

main.dart

import 'package:blog_web_app/screen/home_screen.dart';
import 'package:flutter/material.dart';

void main() {
  // Flutter 프레임워크 초기화 보장
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MaterialApp(home: HomeScreen()));
}

screen/home_screen.dart

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

class HomeScreen extends StatelessWidget {
  // WebViewController 인스턴스 생성 및 설정
  WebViewController webViewController = WebViewController()
    ..loadRequest(
      Uri.parse('https://blog.codefactory.ai'), // 초기 로드할 URL
    )
    // Cascade 연산자(..)를 사용하여 메서드 체이닝
    ..setJavaScriptMode(
      JavaScriptMode.unrestricted, // JavaScript 실행 허용
    );

  HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 상단 네비게이션 바 설정
      appBar: AppBar(
        backgroundColor: Colors.orange,
        title: const Text('Code Factory'),
        centerTitle: true,

        // 왼쪽 네비게이션 버튼들
        leading: Row(
          children: [
            IconButton(
              onPressed: () => webViewController.goBack(),
              icon: const Icon(Icons.arrow_back),
            ),
            IconButton(
              onPressed: () => webViewController.goForward(),
              icon: const Icon(Icons.arrow_forward),
            ),
          ],
        ),
        leadingWidth: 150, // 왼쪽 영역 너비 설정

        // 오른쪽 홈 버튼
        actions: [
          IconButton(
            onPressed: () {
              webViewController.loadRequest(
                Uri.parse('https://blog.codefactory.ai'),
              );
            },
            icon: const Icon(Icons.home),
          ),
        ],
      ),
      // WebView 위젯을 body에 배치
      body: WebViewWidget(controller: webViewController),
    );
  }
}

코드 설명

주요 기술 요소

  1. Cascade 연산자 (..): 동일 객체에 연속적으로 메서드 호출
  2. const 사용: 성능 최적화를 위한 컴파일 타임 상수 선언
  3. Arrow Function: 간결한 콜백 함수 작성

AppBar 구성 요소

  • leading: 왼쪽 영역 (이전/다음 버튼)
  • title: 중앙 제목
  • actions: 오른쪽 영역 (홈 버튼)
  • leadingWidth: 왼쪽 영역 너비 조정

댓글남기기