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
인스턴스 생성
완성 화면
소스 코드 구현
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: 왼쪽 영역 너비 조정
댓글남기기