이전글
01. flutter_inappwebview로 hybrid app 만들기
02. flutter_inappwebview 파일 업로드, 파일 탐색기 안 열릴 때 해결 방법 (flutter_inappwebview file provider authority)
또 오류 발견.. 이지만 또 flutter_inappwebview 개발자님이 올려주신 글이 있더라! 다행..ㅎ
flutter_inappwebview onDownloadStart Example
https://gist.github.com/pichillilorenzo/ee74e103fdc324f761c5fde7b73bd430
flutter_downloader https://pub.dev/packages/flutter_downloader
flutter pub add flutter_downloader
하고 flutter pub get
저번 파일업로드 때와 비슷한데,
[project]/android/app/src/main/AndroidManifest.xml
파일에 프로젝트의 file download provider, initialization provider, flutter downloader initializer를 추가해줘야한다.
<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}" android:icon="@mipmap/ic_launcher"> ... // 밑의 provider를 추가해준다. // android:authorities의 패키지명을 본인 프로젝트 패키지명으로 바꿔줘야한다 <provider android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider" android:authorities="com.jhkim.jhkimvelog.flutter_downloader.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest>
쉽게 말해서 다운로드할 때 뜨는 알림들을 원하는 언어로 바꿔줄 수 있다.
우선 [project]/android/src/main/res/values
폴더에 "Values Resources File"을 새로 생성하여 strings.xml 이름으로 지정해준다.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="flutter_downloader_notification_started">다운로드 시작</string> <string name="flutter_downloader_notification_in_progress">다운로드 진행중</string> <string name="flutter_downloader_notification_canceled">다운로드 취소</string> <string name="flutter_downloader_notification_failed">다운로드 실패</string> <string name="flutter_downloader_notification_complete">다운로드 완료</string> <string name="flutter_downloader_notification_paused">다운로드 중지</string> </resources>
한국어로 사용할 경우 내용은 이런식으로 바꿔주면 되는데 string 내용은 원하는대로 맞춰주면 된다.
<string name="flutter_downloader_notification_started">Download started</string> <string name="flutter_downloader_notification_in_progress">Download in progress</string> <string name="flutter_downloader_notification_canceled">Download canceled</string> <string name="flutter_downloader_notification_failed">Download failed</string> <string name="flutter_downloader_notification_complete">Download complete</string> <string name="flutter_downloader_notification_paused">Download paused</string>
영어로 사용하고 싶다면 flutter_downloader 공식사이트에서 준 예시처럼 이대로 사용해도 될 것 같다.
apk 파일을 다운받을 수 있도록 하고싶으면 권한을 따로 추가해줘야한다.
[project]/android/app/src/main/AndroidManifest.xml
파일에 넣어주면 되는데,
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
를 추가해주면 된다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jhkim.jhkimvelog"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> // this line ... </manifest>
참고링크 https://medium.com/@son.rommer/fix-cleartext-traffic-error-in-android-9-pie-2f4e9e2235e6
이 과정 굉장히 중요하다... 이거 빼먹었다가 한참 헤맸다.
우선 [project]/android/src/main/res/xml
폴더를 만들어준다.
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">your_domain.com</domain> </domain-config> </network-security-config>
위의 내용대로 작성한 network_security_config.xml
파일을 추가해준다.
그리고 이에 대한 내용을 [project]/android/app/src/main/AndroidManifest.xml
에 추가해줘야한다.
android:networkSecurityConfig="@xml/network_security_config"
을 추가해주면 된다.
<application android:label="jhkim.log" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true" android:networkSecurityConfig="@xml/network_security_config"> // this line ... </application>
import 'package:flutter_downloader/flutter_downloader.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // Plugin must be initialized before using await FlutterDownloader.initialize( debug: true, // optional: set to false to disable printing logs to console (default: true) ignoreSsl: true // option: set to false to disable working with http links (default: false) ); runApp(/*...*/) }
flutter_Downloader 사이트에서 그대로 가져왔는데, 저렇게 main에서 initialize 해주면 된다.
파일을 다운받으려면 저장할 경로와 저장공간 권한이 필요하다.
flutter pub add path_provider
flutter pub add permission_handler
추가해주고 flutter pub get
까지 진행해준다.
void main() async{ WidgetsFlutterBinding.ensureInitialized(); await FlutterDownloader.initialize( debug: true, ignoreSsl: true ); await Permission.storage.request(); // 저장공간 권한 요청 추가 runApp(const MyApp()); }
저장공간 권한을 얻기 위해 main에서 request를 추가해준다.
그리고 [project]/android/app/src/main/AndroidManifest.xml
에도 android:requestLegacyExternalStorage="true""
와 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
을 추가해줘야 한다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jhkim.jhkimvelog"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> // this line <application android:label="jhkim.log" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true" // this line android:networkSecurityConfig="@xml/network_security_config"> ... </application> </manifest>
본격적으로 flutter_inappwebview 에서 download를 해보자.
이제 flutter_downloader를 사용할건인데, 공식사이트의 example 코드를 많이 참고했다.
final ReceivePort _port = ReceivePort(); ('vm:entry-point') static void downloadCallback(String id, DownloadTaskStatus status, int downloadProgress) { final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port')!; send.send([id, status, downloadProgress]); } void initState() { super.initState(); ... IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); _port.listen((dynamic data) { String id = data[0]; DownloadTaskStatus status = data[1]; int progress = data[2]; setState((){ }); }); FlutterDownloader.registerCallback(downloadCallback); } void dispose() { IsolateNameServer.removePortNameMapping('downloader_send_port'); super.dispose(); }
webview를 사용하는 페이지 안에 넣어주면 될 것 같다.
file downloader는 background isolation으로 동작하기 때문에 이 과정이 꼭 필요하다.
flutter_inappwebview의 InAppWebView widget에는 onDownloadStartRequest 함수가 있는데, 이 부분에서 file downloader를 호출해줘야한다.
(위의 링크에는 onDownloadStart로 적혀있는데, android가 업데이트되면서 onDownloadStartRequest로 바뀐 모양이다. 더 이상 사용되지 않는 함수라고 나온다.)
onDownloadStartRequest: (InAppWebViewController controller, DownloadStartRequest downloadStartRequest) async { final directory = await getApplicationDocumentsDirectory(); var savedDirPath = directory.path; await FlutterDownloader.enqueue( url: downloadStartRequest.url.toString(), savedDir: savedDirPath, saveInPublicStorage: true, showNotification: true, openFileFromNotification: true, ); }
이 부분을 추가해주면 된다.
파일을 저장할 경로인 savedDir은 원하는 경로로 지정해주면 되는데, 일단 기본경로로 진행해줬다.
이 때, 중요한 점은 InAppWebView의 option 중 useOnDownloadStart
를 true로 설정해줘야 한다.
InAppWebView( initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( useOnDownloadStart: true, // this line ...
이 부분을 설정해줘야 onDownloadStartRequest 함수가 작동한다.
download가 정상적으로 되는 것 확인할 수 있다.
아직 flutter도 앱 개발 시장에서 파이를 따지자면 많이 쓰는 편이 아닌데 그 중에 web view를 사용하는 사람도 적어서 그런지.. 생각보다 자료 찾기가 힘들다ㅜㅜ flutter_inappwebview에서 onDownloadStartRequest로 바뀐 것도 함수 설명에 나와서 알아채긴 했지만 찾아봐도 저걸 사용한 예시는 전무한 수준.. 아직 사용하는 사람이 없는 만큼 남들보다 조금 더 빠르게 시니어가 될 수 있는 기회일까 싶기도 하고~~ㅎ flutter가 업데이트하는 행보를 보면 더더 잘 될거라고 생각은 하는데 내 예상보다 속도가 느리다..ㅎ
flutter_downloader 1.10.4 기준 downloadcallback은 (String , DownloadTaskStatus , int ) 에서 (String , int , int ) 로 변경되어야 합니다. 그에 따라 DownloadTaskStatus status = DownloadTaskStatus(data[1]); 해주시면 됩니다.
매우 감사합니다.