本节目标
- app 升级策略
- android 动态授权
- android 设备目录
- ios 支持 swift 语言
- 快速提示框
视频
代码
https://github.com/ducafecat/flutter_learn_news/releases/tag/v1.0.11
正文
ios 支持 swift 语言
社区第三方包都在用 swift 开发,打包的时候需要加入 swift 语言包。
创建一个支持 swift 的新项目,然后把 lib assets pubspec.yaml 覆盖即可。
app 升级策略
代码实现
定义接口
加入依赖包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| dependencies: device_info: ^0.4.2+3
package_info: ^0.4.0+18
path_provider: ^1.6.8
permission_handler: ^5.0.0+hotfix.6
install_plugin: ^2.0.1
easy_dialog: ^1.0.5
|
升级工具类
- lib/common/utils/update.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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| import 'dart:io';
import 'package:dio/dio.dart'; import 'package:easy_dialog/easy_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_ducafecat_news/common/apis/app.dart'; import 'package:flutter_ducafecat_news/common/entitys/entitys.dart'; import 'package:flutter_ducafecat_news/common/widgets/toast.dart'; import 'package:flutter_ducafecat_news/global.dart'; import 'package:install_plugin/install_plugin.dart'; import 'package:path_provider/path_provider.dart';
class AppUpdateUtil { static AppUpdateUtil _instance = AppUpdateUtil._internal(); factory AppUpdateUtil() => _instance;
BuildContext _context; AppUpdateResponseEntity _appUpdateInfo;
AppUpdateUtil._internal();
Future run(BuildContext context) async { _context = context;
AppUpdateRequestEntity requestDeviceInfo = AppUpdateRequestEntity( device: Global.isIOS == true ? "ios" : "android", channel: Global.channel, architecture: Global.isIOS == true ? Global.iosDeviceInfo.utsname.machine : Global.androidDeviceInfo.device, model: Global.isIOS == true ? Global.iosDeviceInfo.name : Global.androidDeviceInfo.brand, ); _appUpdateInfo = await AppApi.update(context: context, params: requestDeviceInfo);
_runAppUpdate(); }
Future _runAppUpdate() async { final isNewVersion = (_appUpdateInfo.latestVersion.compareTo(Global.packageInfo.version) == 1);
if (isNewVersion == true) { _appUpdateConformDialog(() { Navigator.of(_context).pop(); if (Global.isIOS == true) { InstallPlugin.gotoAppStore(_appUpdateInfo.shopUrl); } else { toastInfo(msg: "开始下载升级包"); _downloadAPKAndSetup(_appUpdateInfo.fileUrl); } }); } }
Future _downloadAPKAndSetup(String fileUrl) async { Directory externalDir = await getExternalStorageDirectory(); String fullPath = externalDir.path + "/release.apk";
Dio dio = Dio(BaseOptions( responseType: ResponseType.bytes, followRedirects: false, validateStatus: (status) { return status < 500; })); Response response = await dio.get( fileUrl, );
File file = File(fullPath); var raf = file.openSync(mode: FileMode.write); raf.writeFromSync(response.data); await raf.close();
await InstallPlugin.installApk(fullPath, Global.packageInfo.packageName); }
void _appUpdateConformDialog(VoidCallback onPressed) { EasyDialog( title: Text( "发现新版本 ${_appUpdateInfo.latestVersion}", style: TextStyle(fontWeight: FontWeight.bold), textScaleFactor: 1.2, ), description: Text( _appUpdateInfo.latestDescription, textScaleFactor: 1.1, textAlign: TextAlign.center, ), height: 220, contentList: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new FlatButton( padding: const EdgeInsets.only(top: 8.0), textColor: Colors.lightBlue, onPressed: onPressed, child: new Text( "同意", textScaleFactor: 1.2, ), ), new FlatButton( padding: const EdgeInsets.only(top: 8.0), textColor: Colors.lightBlue, onPressed: () { Navigator.of(_context).pop(); }, child: new Text( "取消", textScaleFactor: 1.2, ), ), ], ) ]).show(_context); } }
|
读取设备信息
https://pub.flutter-io.cn/packages/device_info
lib/global.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
| static bool isIOS = Platform.isIOS;
static AndroidDeviceInfo androidDeviceInfo;
static IosDeviceInfo iosDeviceInfo;
static PackageInfo packageInfo;
static Future init() async { ...
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); if (Global.isIOS) { Global.iosDeviceInfo = await deviceInfoPlugin.iosInfo; } else { Global.androidDeviceInfo = await deviceInfoPlugin.androidInfo; }
Global.packageInfo = await PackageInfo.fromPlatform();
...
|
lib/common/entitys/app.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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| class AppUpdateRequestEntity { String device; String channel; String architecture; String model;
AppUpdateRequestEntity({ this.device, this.channel, this.architecture, this.model, });
factory AppUpdateRequestEntity.fromJson(Map<String, dynamic> json) => AppUpdateRequestEntity( device: json["device"], channel: json["channel"], architecture: json["architecture"], model: json["model"], );
Map<String, dynamic> toJson() => { "device": device, "channel": channel, "architecture": architecture, "model": model, }; }
class AppUpdateResponseEntity { String shopUrl; String fileUrl; String latestVersion; String latestDescription;
AppUpdateResponseEntity({ this.shopUrl, this.fileUrl, this.latestVersion, this.latestDescription, });
factory AppUpdateResponseEntity.fromJson(Map<String, dynamic> json) => AppUpdateResponseEntity( shopUrl: json["shopUrl"], fileUrl: json["fileUrl"], latestVersion: json["latestVersion"], latestDescription: json["latestDescription"], );
Map<String, dynamic> toJson() => { "shopUrl": shopUrl, "fileUrl": fileUrl, "latestVersion": latestVersion, "latestDescription": latestDescription, }; }
|
lib/common/apis/app.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class AppApi { static Future<AppUpdateResponseEntity> update({ @required BuildContext context, AppUpdateRequestEntity params, }) async { var response = await HttpUtil().post( '/app/update', context: context, params: params, ); return AppUpdateResponseEntity.fromJson(response); } }
|
lib/common/utils/update.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 42 43
| Future run(BuildContext context) async { _context = context;
AppUpdateRequestEntity requestDeviceInfo = AppUpdateRequestEntity( device: Global.isIOS == true ? "ios" : "android", channel: Global.channel, architecture: Global.isIOS == true ? Global.iosDeviceInfo.utsname.machine : Global.androidDeviceInfo.device, model: Global.isIOS == true ? Global.iosDeviceInfo.name : Global.androidDeviceInfo.brand, ); _appUpdateInfo = await AppApi.update(context: context, params: requestDeviceInfo);
_runAppUpdate(); }
Future _runAppUpdate() async { final isNewVersion = (_appUpdateInfo.latestVersion.compareTo(Global.packageInfo.version) == 1);
if (isNewVersion == true) { _appUpdateConformDialog(() { Navigator.of(_context).pop(); if (Global.isIOS == true) { InstallPlugin.gotoAppStore(_appUpdateInfo.shopUrl); } else { toastInfo(msg: "开始下载升级包"); _downloadAPKAndSetup(_appUpdateInfo.fileUrl); } }); } }
|
android 动态授权
https://pub.flutter-io.cn/packages/permission_handler
https://developer.android.com/training/permissions/requesting
https://developer.android.com/training/permissions/usage-notes
- AndroidManifest.xml 中加入权限
android/app/src/main/AndroidManifest.xml
1 2 3
| <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
lib/pages/index/index.dart
在 initState 是执行
延迟 3 秒,用户体验好些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class _IndexPageState extends State<IndexPage> { @override void initState() { super.initState();
if (Global.isRelease == true) { doAppUpdate(); } }
Future doAppUpdate() async { await Future.delayed(Duration(seconds: 3), () async { if (Global.isIOS == false && await Permission.storage.isGranted == false) { await [Permission.storage].request(); } if (await Permission.storage.isGranted) { AppUpdateUtil().run(context); } }); }
|
android 目录权限
https://pub.flutter-io.cn/packages/path_provider
https://pub.flutter-io.cn/packages/install_plugin
https://developer.android.com/reference/androidx/core/content/FileProvider.html
- lib/common/utils/update.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
| Future _downloadAPKAndSetup(String fileUrl) async { Directory externalDir = await getExternalStorageDirectory(); String fullPath = externalDir.path + "/release.apk";
Dio dio = Dio(BaseOptions( responseType: ResponseType.bytes, followRedirects: false, validateStatus: (status) { return status < 500; })); Response response = await dio.get( fileUrl, );
File file = File(fullPath); var raf = file.openSync(mode: FileMode.write); raf.writeFromSync(response.data); await raf.close();
await InstallPlugin.installApk(fullPath, Global.packageInfo.packageName); }
|
EasyDialog 快速提示框
https://pub.flutter-io.cn/packages/easy_dialog
- lib/common/utils/update.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 42
| void _appUpdateConformDialog(VoidCallback onPressed) { EasyDialog( title: Text( "发现新版本 ${_appUpdateInfo.latestVersion}", style: TextStyle(fontWeight: FontWeight.bold), textScaleFactor: 1.2, ), description: Text( _appUpdateInfo.latestDescription, textScaleFactor: 1.1, textAlign: TextAlign.center, ), height: 220, contentList: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new FlatButton( padding: const EdgeInsets.only(top: 8.0), textColor: Colors.lightBlue, onPressed: onPressed, child: new Text( "同意", textScaleFactor: 1.2, ), ), new FlatButton( padding: const EdgeInsets.only(top: 8.0), textColor: Colors.lightBlue, onPressed: () { Navigator.of(_context).pop(); }, child: new Text( "取消", textScaleFactor: 1.2, ), ), ], ) ]).show(_context); }
|
资源
视频
蓝湖设计稿(加微信给授权 ducafecat)
https://lanhuapp.com/url/lYuz1
密码: gSKl
蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿
商业设计稿文件不好直接分享, 可以加微信联系 ducafecat
YAPI 接口管理
http://yapi.demo.qunar.com/
参考
https://developer.android.com/training/permissions/requesting
https://developer.android.com/training/permissions/usage-notes
https://developer.android.com/reference/androidx/core/content/FileProvider.html
https://pub.flutter-io.cn/packages/device_info
https://pub.flutter-io.cn/packages/path_provider
https://pub.flutter-io.cn/packages/permission_handler
https://pub.flutter-io.cn/packages/install_plugin
https://pub.flutter-io.cn/packages/easy_dialog
VSCode 插件
© 猫哥
https://ducafecat.tech
邮箱 ducafecat@gmail.com / 微信 ducafecat / 留言板 disqus