Today I Learned 8회차 - [플루터] 블로그 웹 앱 만들기
블로그 웹 앱 만들기
학습 목표
모바일이 지원되는 웹 사이트를 코드 몇 줄만으로 손쉽게 웹 앱으로 만들 수 있습니다. 이번 장에서는 웹 사이트를 앱으로 포장하는 방법을 배워봅시다.
프로젝트 구상하기
이번 앱을 구현하려면 두 가지 개념을 배워야 합니다.
- 웹뷰(WebView) - 앱 내에서 웹 브라우저 기능을 제공
- 앱바(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()함수를 먼저 호출해야 합니다.
대안 방법:
- main() 함수에서 직접 호출:
WidgetsFlutterBinding.ensureInitialized()먼저 실행 - StatefulWidget 사용:
initState()메서드에서WebViewController인스턴스 생성
완성 화면
.png)
소스 코드 구현
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),
);
}
}
코드 설명
주요 기술 요소
- Cascade 연산자 (..): 동일 객체에 연속적으로 메서드 호출
- const 사용: 성능 최적화를 위한 컴파일 타임 상수 선언
- Arrow Function: 간결한 콜백 함수 작성
AppBar 구성 요소
- leading: 왼쪽 영역 (이전/다음 버튼)
- title: 중앙 제목
- actions: 오른쪽 영역 (홈 버튼)
- leadingWidth: 왼쪽 영역 너비 조정
댓글남기기