Post

Flutter 블로그 웹앱 만들기

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로 이동
setNavigationDelegateNavigationDelegate 객체를 입력해야하면 여기에 사용할 수 있는 함수들이 존재
- 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),
          ),
        ],
This post is licensed under CC BY 4.0 by the author.