velog를 시작하고 나서 내 글 보는 재미가 생겼는데 맨날 삼성인터넷으로 들어가려니 불편하길래.. inappwebview로 간단하게 velog 어플을 만들어봤다. 출시할 것도 아니고.. 내 벨로그 수정하고 구경하기 편하려고 만든거라 정말 간단하게만 만들었다!
flutter의 webview 라이브러리로 가장 많이 쓰는게 flutter_webview랑 flutter_inappwbeview인데 flutter_inappwebview 쪽이 좀 더 자유도가 높은 편이고, flutter_webview는 새 창이 뜨도록 하는게 어렵다고한다. 반응형 웹이라고 해도 그대로 사용하려면 꽤 노력이 필요한데 flutter_inappwebview는 그 부분을 좀 더 편리하게 도와주는듯.
이번 어플은 간단히 만들거라 둘 중 어느 것을 사용하든 상관없긴한데 나는 전에 flutter_inappwebview 만들었던 경험이 있어 그 코드 그대로 응용하고자 flutter_inappwebview로 사용했다.
앱을 시작하는 splash screen. 사실 없어도 무방하지만 있으면 내가 좋아하므로 그냥 넣어봤다.
우선 로고로 사용할 png 파일이 필요하다.
다시 강조하지만 배포용도 아니고 디자이너도 아니기 때문에 내 velog 로고를 캡쳐해서 png로 저장해줬다.
저장한 파일을 [project]/assets
안에 넣어준다.
import 'dart:async'; import 'package:flutter/material.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({Key? key}):super(key:key); @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> { @override void initState() { super.initState(); Timer(const Duration(seconds:2), () { Navigator.of(context).pushReplacementNamed('/velog'); }); } @override Widget build(BuildContext context){ return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children:[ Container( margin: const EdgeInsets.symmetric(horizontal:50), child: Image.asset('assets/jhkim_velog_logo.png') ), ] ), ), ); } }
간단하게 이미지만 나오는 코드로 작성.
2초 후 velog 페이지로 넘어가게 해두었다.
web view를 사용하려면 준비과정이 필요하다.
flutter pub get flutter_inappwebview
우선 터미널에서 flutter_inappwebview 패키지부터 pub get 하고 시작.
flutter_inappwebview
https://pub.dev/packages/flutter_inappwebview
위의 공식 페이지를 들어가 보면 사용조건이 나와있다.
Requirements
Dart sdk: ">=2.14.0 <3.0.0"
Flutter: ">=3.0.0"
Android: minSdkVersion 17 and add support for androidx (see AndroidX Migration to migrate an existing app)
iOS: --ios-language swift, Xcode version >= 14
나는 일단 android로 제작할 예정이라 android 조건만 맞춰줬다.
flutter 버전 같은 경우 flutter 3.0은 flutter 2.0의 null-safety처럼 업그레이드 시 오류가 발생하거나 그러진 않아서 업그레이드 하는 것을 추천한다.
defaultConfig { applicationId "com.jhkim.jhkimvelog" minSdkVersion 17 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName }
[porject]/android/app/build.gradle 파일에 가서 minSdkVersion을 17로 잡아준다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jhkim.jhkimvelog"> <uses-permission android:name="android.permission.INTERNET"/> <application android:label="jhkim.log" android:name="${applicationName}" ...
그리고 <uses-permission android:name="android.permission.INTERNET"/>
을 꼭 넣어줘야 한다.
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; class InAppWebViewScreen extends StatefulWidget { const InAppWebViewScreen({Key? key}):super(key:key); @override State<InAppWebViewScreen> createState() => _InAppWebViewScreenState(); } class _InAppWebViewScreenState extends State<InAppWebViewScreen> { final GlobalKey webViewKey = GlobalKey(); Uri myUrl = Uri.parse("https://velog.io/@jhkim0122"); late final InAppWebViewController webViewController; late final PullToRefreshController pullToRefreshController; double progress = 0; @override void initState() { super.initState(); pullToRefreshController = (kIsWeb ? null : PullToRefreshController( options: PullToRefreshOptions(color: Colors.blue,), onRefresh: () async { if (defaultTargetPlatform == TargetPlatform.android) { webViewController.reload(); } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { webViewController.loadUrl(urlRequest: URLRequest(url: await webViewController.getUrl()));} }, ))!; } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: WillPopScope( onWillPop: () => _goBack(context), child: Column(children: <Widget>[ progress < 1.0 ? LinearProgressIndicator(value: progress, color: Colors.blue) : Container(), Expanded( child: Stack(children: [ InAppWebView( key: webViewKey, initialUrlRequest: URLRequest(url: myUrl), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( javaScriptCanOpenWindowsAutomatically: true, javaScriptEnabled: true, useOnDownloadStart: true, useOnLoadResource: true, useShouldOverrideUrlLoading: true, mediaPlaybackRequiresUserGesture: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, verticalScrollBarEnabled: true, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36' ), android: AndroidInAppWebViewOptions( useHybridComposition: true, allowContentAccess: true, builtInZoomControls: true, thirdPartyCookiesEnabled: true, allowFileAccess: true, supportMultipleWindows: true ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, allowsBackForwardNavigationGestures: true, ), ), pullToRefreshController: pullToRefreshController, onLoadStart: (InAppWebViewController controller, uri) { setState(() {myUrl = uri!;}); }, onLoadStop: (InAppWebViewController controller, uri) { setState(() {myUrl = uri!;}); }, onProgressChanged: (controller, progress) { if (progress == 100) {pullToRefreshController.endRefreshing();} setState(() {this.progress = progress / 100;}); }, androidOnPermissionRequest: (controller, origin, resources) async { return PermissionRequestResponse( resources: resources, action: PermissionRequestResponseAction.GRANT); }, onWebViewCreated: (InAppWebViewController controller) { webViewController = controller; }, onCreateWindow: (controller, createWindowRequest) async{ showDialog( context: context, builder: (context) { return AlertDialog( content: SizedBox( width: MediaQuery.of(context).size.width, height: 400, child: InAppWebView( // Setting the windowId property is important here! windowId: createWindowRequest.windowId, initialOptions: InAppWebViewGroupOptions( android: AndroidInAppWebViewOptions( builtInZoomControls: true, thirdPartyCookiesEnabled: true, ), crossPlatform: InAppWebViewOptions( cacheEnabled: true, javaScriptEnabled: true, userAgent: "Mozilla/5.0 (Linux; Android 9; LG-H870 Build/PKQ1.190522.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36" ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, allowsBackForwardNavigationGestures: true, ), ), onCloseWindow: (controller) async{ if (Navigator.canPop(context)) { Navigator.pop(context); } }, ), ),); }, ); return true; }, ) ])) ]) ) ) ); } Future<bool> _goBack(BuildContext context) async{ if(await webViewController.canGoBack()){ webViewController.goBack(); return Future.value(false); }else{ return Future.value(true); } } }
우선 추가한 기능으로는 화면을 아래로 당겼을 때 새로고침이 되도록 pullToRefreshController를 넣어줬다.
그리고 webview는 어플 입장에서 사실상 페이지 하나라서 뒤로가기를 하면 바로 어플이 꺼진다. 뒤로가기 했을을 때 webview 속 사이트의 이전 화면이 나오도록 WillPopScope를 사용해서 webViewController로 지정해줬다.
정상적으로 잘 된다. 편안~
우선 icon으로 사용할 이미지들이 필요한데 구글에 app icon generator 검색하면 잔뜩 나온다.
App Icon Generator
https://appicon.co/
내가 이용한 사이트는 여긴데 ios icon bundle도 만들어줘서 한 번에 만들기 좋다.
icon으로 사용할 png 이미지를 업로드해서 만들어준다.
그럼 이런 폴더를 다운받을 수 있는데 android 버전만 만들거고 배포는 안 할 예정이라 android 폴더만 사용할 것이다.
android 폴더 안에 보면 이렇게 5가지 폴더가 나온다.
그 파일들을 이제 [project]/android/app/main/res
폴더에 넣어준다.
그러고 새로 어플을 깔아주면 바뀐 로고를 확인할 수 있다.
flutter build apk --split-per-abi
터미널에서 build apk 를 사용하면 apk가 생성된다.
--split-per-abi 를 사용하면 abi별로 apk가 따로 생성되는데 용량을 조금 줄일 수 있다.
이런 간단한 앱에서는 굳이 사용하지 않아도 괜찮겠지만 습관이 되어서 그냥 저렇게 빌드해줬다.
빌드가 완료되면 [project]\build\app\outputs\flutter-apk\app-armeabi-v7a-release.apk
경로의 apk를 다운받아서 설치하면 완료!
프로젝트는 깃허브에 올려놓았는데, 앱 아이콘, 앱 라벨, splash 이미지, 웹 링크만 수정하면 다른 웹으로 연결되도록 사용할 수 있다.
프로젝트 깃허브
https://github.com/jhkim0122/jhkim_velogapk 다운로드 링크 (2022.11.08 버그 수정)
jhkim_velog.apk
다음글 > flutter_inappwebview 파일 업로드, 파일 탐색기 안 열릴 때 해결 방법 (flutter_inappwebview file provider authority)
안녕하세요. 개발 공부중입니다.
올려주신 자료 보면서 공부중인데요. 파일 다운로드 방법을 어려워서요.
git 소스 링크 다시 공유 가능하신가요?