Flutter 블로그 웹앱 만들기
플러터에서 웹뷰를 이용해 기존에 있는 내 블로그를 웹뷰 앱으로 만들어 본다 이를 구현하기 위해서 콜백 함수와(url을 로딩) 웹뷰 위젯을 사용할 줄 알아야한다
콜백 함수
콜백 함수란 일정 작업이 완료되면 실행되는 함수이다 함수를 정의해두면 바로 실행하지 않고 특정 조건이 성립될 때 실행된다
1
2
3
4
5
6
WebViewController controller = WebViewController()
..setNavigationDelegate(NavigationDelegate(
onPageFinished: (String url){ // 로딩 완료 후 실행되는 함수
print(url);
}
))
위 코드는 유저가 화면을 터치했을 때 실행할 함수나 웹뷰의 로딩이 완료되면 실행할 콜백함수를 정의하는 코드
onPageFinished()는 웹뷰에서 페이지 로딩이 완료된 뒤에 실행되는 콜백함수이다
웹뷰 위젯
웹뷰는 앱에서 웹 브라우저 기능을 구현해주는 기술이다 웹뷰는 네이티브 컴포넌트에 비해서 속도가 느리고 애니메이션이 부자연스러울 수 있지만 기존에 만든 웹사이트를 앱에서 손쉽게 사용할 수 있기 때문에 사용한다 웹뷰를 구현할 때 사용할 웹뷰 위젯은 controller 파라미터에 WebViewController 객체를 입력해줘야한다
| 속성 | 설명 |
|---|---|
| setJavascriptMode | 웹뷰에서 자바스크립트를 실행할지 여부 결정 - JavascriptMode.unresticed: 자바스크립트 허용 - JavascriptMode.disabled: 자바스크립트 불가 |
| setBackgroundColor | 배경색 지정 |
| loadRequest | 새로운 URl로 이동 |
| setNavigationDelegate | NavigationDelegate 객체를 입력해야하면 여기에 사용할 수 있는 함수들이 존재 - onProgress: 새로운 페이지를 열어서 로딩이 될 때마다 실행되는 함수, 매개변수로 로딩의 진행도를 0~1 사이로 받을 수 있다 - onPageStarted: 새로운 페이지로 이동하면 실행되는 콜백함수, 이동하고 있는 페이지의 url을 매개변수로 받는다 - onPageFinished: 새로운 페이지로 이동이 완료되면 실행되는 콜백 함수, 로딩이 완료된 웹페이지의 url을 매개변수로 입력 받는다 |
웹뷰 사용하기
1. webviewWidget 설치
웹뷰를 사용하기 위해서 webview를 설치한다 flutter pub add webview_flutter명령어 실행
1
2
3
4
5
6
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
webview_flutter: ^4.10.0 # 이 부분을 추가된다
flutter pub get을 통해서 yaml 파일에 등록된 플러그인을 내려 받는다
이렇게만 하면 실행이 되야하지만 오류 발생
Launching lib/main.dart on sdk gphone64 arm64 in debug mode...
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':webview_flutter_android:compileDebugJavaWithJavac'.
> Could not resolve all files for configuration ':webview_flutter_android:androidJdkImage'.
> Failed to transform core-for-system-modules.jar to match attributes {artifactType=_internal_android_jdk_image, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
> Execution failed for JdkImageTransform: /Users/minjeong/Library/Android/sdk/platforms/android-34/core-for-system-modules.jar.
> Error while executing process /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/jlink with arguments {--module-path /Users/minjeong/.gradle/caches/transforms-3/2a48783f41ce081285e8842f94117c94/transformed/output/temp/jmod --add-modules java.base --output /Users/minjeong/.gradle/caches/transforms-3/2a48783f41ce081285e8842f94117c94/transformed/output/jdkImage --disable-plugin system-modules}
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 7s
┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────────┐
│ [!] This is likely due to a known bug in Android Gradle Plugin (AGP) versions less than 8.2.1, │
│ when │
│ 1. setting a value for SourceCompatibility and │
│ 2. using Java 21 or above. │
│ To fix this error, please upgrade your AGP version to at least 8.2.1. The version of AGP that │
│ your project uses is likely defined in: │
│ /Users/minjeong/Desktop/blog_app/android/settings.gradle, │
│ in the 'plugins' closure (by the number following "com.android.application"). │
│ Alternatively, if your project was created with an older version of the templates, it is likely │
│ in the buildscript.dependencies closure of the top-level build.gradle: │
│ /Users/minjeong/Desktop/blog_app/android/build.gradle, │
│ as the number following "com.android.tools.build:gradle:". │
│ │
│ For more information, see: │
│ https://issuetracker.google.com/issues/294137077 │
│ https://github.com/flutter/flutter/issues/156304 │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
Error: Gradle task assembleDebug failed with exit code 1
Exited (1).
위 오류를 보고 android/setting.gradle에 들어가서
1
2
3
4
5
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
8.1.0 -> 8.2.1로 수정 후 정삭적으로 실행되었다
2. main.dart 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(
const MaterialApp(
home: WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({Key? key}) : super(key: key);
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
// WebViewController 초기화
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) // JavaScript 허용
..loadRequest(Uri.parse('https://minnnning.github.io')); // 초기 URL 설정
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('my blog'),
),
body: WebViewWidget(controller: _controller), // WebViewWidget 사용
);
}
}
웹뷰 버튼 추가 및 파일 분리
1. 웹뷰 코드 분리
코드를 main과 home_screen파일로 분리한다 main에는 처음 실행될 함수가 작성되고 연결할 파일 home_screen.dart 파일을 작성한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.dart
import 'package:flutter/material.dart';
import 'package:blog_app/screen/home_screen.dart'; // 연결할 파일 위치
void main() {
WidgetsFlutterBinding.ensureInitialized(); //플러터 프레임 워크가 앱을 실행할 준비가 될때까지 기다림
// 이 코드가 없다면 플러터 초기화 오류가 발생한다
runApp(
MaterialApp(
home: HomeScreen(),
),
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//home_screen.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class HomeScreen extends StatelessWidget {
WebViewController webViewController = WebViewController()
..loadRequest(Uri.parse('https://minnnning.github.io')) // 초기 URL 설정;
..setJavaScriptMode(JavaScriptMode.unrestricted); // JavaScript 허용
HomeScreen({Key? key}): super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( //앱바 설정
backgroundColor: Colors.grey, //앱바 배경
title: Text("My Blog"), //앱바에 들어갈 제목
centerTitle: true, //가운데 정렬
),
body: WebViewWidget(
controller: webViewController,
)
);
}
}
위 코드는 main과 웹뷰를 보여주는 곳 이렇게 파일을 분리했다
2. 홈 버튼 구현하기
앱바에 홈 버튼을 추가해서 버튼을 누른다면 홈화면으로 이동한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( //앱바 설정
backgroundColor: Colors.grey, //앱바 배경
title: Text("My Blog"), //앱바에 들어갈 제목
centerTitle: true, //가운데 정렬
actions: [
IconButton(
onPressed: () {
webViewController.loadRequest(Uri.parse('https://minnnning.github.io'));
},
icon: Icon(
Icons.home,
),
),
],
),
body: WebViewWidget(
controller: webViewController,
)
);
}
3. 뒤로가기 앞으로 버튼 추가
뒤로가기와 앞으로 가기 기능은 webViewController.canGoBack()의 기능을 이용해서 구현했다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
actions: [
IconButton(
onPressed: () {
webViewController.loadRequest(Uri.parse('https://minnnning.github.io'));
},
icon: Icon(
Icons.home,
),
),
IconButton(
onPressed: () async {
if (await webViewController.canGoBack()) {
webViewController.goBack();
} else {
// 뒤로 갈 수 없는 경우 처리
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("뒤로 갈 수 없습니다.")),
);
}
},
icon: const Icon(Icons.arrow_back),
),
IconButton(
onPressed: () async {
if (await webViewController.canGoForward()) {
webViewController.goForward();
} else {
// 앞으로 갈 수 없는 경우 처리
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("앞으로 갈 수 없습니다.")),
);
}
},
icon: const Icon(Icons.arrow_forward),
),
],