最近使用 Flutter
实现了一个小的项目,分享一下;
项目背景
运行环境:wangpos, 实际为 Android-5.1.1
这个项目是重构的一个已经存在的项目,之前的项目使用 Android 原生 + Webview
混合开发的方式实现;
缺点如下:
1,需要嵌入浏览器内核到 apk
,导致安装包的大小增加了 n*10M
;
2,嵌入的浏览器内核对 css
的支持并不是特别完美,特别是一些新的特性,导致兼容性有一些问题;
3,代码相当于 public
的,意味着一些敏感信息写在代码里面会有顾虑;
优点:
1,可以无感知的更新版本;
正好 Google
正式发布了 Flutter
,似乎上面的缺点可以完美解决,并保留优点(当时 Google
明确表示可以支持热更新,可惜后来叫停了,感觉上了贼船),于是决定使用 Flutter
来重写;
下面介绍一下具体的实现:
状态管理
项目的状态管理是应该优先考虑的问题,如果状态管理没有考虑好,我们的项目往往会随着规模的扩大,复杂度的提升,慢慢的变得混乱,腐败;官方推荐的方式是 Bloc(Bussiness Logic Component)
,同时也查看了其它的一些实现,比如 fish-redux
,最后还是发现 Bloc
的学习曲线比较平缓,借助于 Dart
的 Stream
和 Sinks
,个人可以不借助第三方的框架或者 package
轻松的实现(实际用的是 RxDart, 一个对 Stream
和 Sinks
的封装);
就像上图所示,widget
可以发送指令到 Bloc
, 并且监听 Sream
获取状态变化,这样我们就可以很容易的将 业务逻辑
和 UI
分离开
文件结构
状态管理的方式确定之后,我们就可以很容易的确定项目的文件结构了:
model json 序列化
官方推荐得方式如下:
其实如果是手写 fromJson
和 toJson
感觉还是挺麻烦的,官方估计也是因为此推出了一个小工具 json_serializable,用法上官方文档也写的比较详细,这里就不多说了;
后来发现一个小工具也不错,https://flutter.beer/
Plugin 开发
使用新技术的一个劣势就是社区成熟度不高,需要自己造轮子,如果是纯 Dart
的项目建议创建 Package
,如果是跟原生有交互的话,那就需要创建为 Plugin
了,创建也很容易,使用 flutter create ...
就可以直接创建出来了,不多说,官方文档很详细,说说文档里面没有提到的一些问题,比如异步消息的处理,像 NFC
,我们开始监听,之后等待数据,这部分是异步的,这里推荐使用的是 EventChannel
Android
部分代码:
Flutter
部分:
使用:
状态持久化
因为当前项目需要实现应用重启之后恢复之前状态,意味着我必须将当前的 state
持久化到本地,一开始还是很怀念 Redux
的 Middleware
的,但是既然选择了 Bloc
,那就只能在此基础上想办法实现了,Dart
的语言特性还是很丰富的,比如 泛型
,我用此特性可以将 Bloc
的基础逻辑实现在一个 基类
里面,每个 Bloc
只要继承这个 基类
,就天然具有了 状态持有
,Sinks
, Stream
的能力,代码如下:
如果要持久化呢,就在此基础上继续改造一下:
这里我们使用模版模式,一旦子类设置了 cacheKey
那么每次 setState
时就存到本地,并且在初始化的时候从本地加载,而子类需要做的是实现两个方法 stateFromStr
和 stateToStr
,如此,我们就可以很容易的实现 state
的选择性持久化了,其实这里有个坑,因为 SharedPreferences
的实例获取是异步的,但是 dart
里面的构造函数不允许指定为 async
, 这就会造成构造函数完成之后,SharedPreferences
不可用,这种异步资源的加载后面会专门说一下;
异步资源的加载
因为 Dart
是单线程模型的,所以 Dart
里面比较耗时的操作如果是同步执行则会造成应用卡顿,所以我们会选择将一些操作异步来执行,Dart
里面的并发机制是使用的 isolate
, 具体我还没有理解很透彻,不敢讲太多,怕误导大家,这里我说一下上文提到的异步资源的加载问题,我记得我在 Flutter
github 提过一个 issue
,官方给出的回复在使用之前提前加载,所以我的解决方案是提供一个全局的引用,并在 main
函数里面初始化,完成之后执行 runApp()
config.dart
Config 是一个单例(这里有坑那,Dart
里面可没有什么饱汉式,饿汉式,之类的,只有一种,永远只在需要的时候才创建实例),使用 SharedPreferences
可以:
Network inspector
我们在开发调试的阶段,特别的有网络请求的情况,需要查看请求的参数,以及返回的结果,如果每次都打印 log
出来好像也没有什么不妥,但是一个是耗费一定的精力,二是打印出来也不直观,这里推荐一个工具 flutter_stetho,这个是基于 Android Stetho
实现的,我们可以在 Chrome remotedevice
里面查看:
而且我们还可以在查看 LocalStoage
的情况,完美, 这里不知道 iOS
有没有好的办法来解决。
区分开发环境与生产环境
Flutter
在运行时可以获取当前运行环境是属于 Debug
还是 Release
状态,我们可以根据此设置一个全局的配置类返回不同环境需要的参数,比如:
上面的代码我可以根据不同的环境选择不同的 ApiEndpoint
, 也可以只在开发环境初始化 flutter_stetho
。
Launcher Icons / splash screen
有个小工具可以设置 Lancher Icons
和 splash screen
:
flutter_launcher_icons
直接在 pubspec.yaml
设置:
然后执行:
就可以了
持续交付
我所实现的项目不是一个公开项目,我需要做的是 build
之后将 apk
重命名并将 apk
上传到 s3
上,所以我需要自定义一些脚本来实现,之后发现了 fastlane 实现的很好,可以支持 iOS
和 Android
, 并且可以设置一些应用商店的信息,直接将应用发布到 AppStore
这里我只使用了很简单的定义脚本的功能,具体用法就去文档里面看吧。
文档生成
Dart
实现了一个很好的功能 dartdoc
, 我们只要在 terminal
里面执行 dartdoc
,就可以生成整个项目的文档, 感觉还是很好用的,效果如下
Makefile
将所有的命令集中写到 Makefile
里面,避免祖传命令的出现。
坑
CSS Border-Image 效果
可以使用 Container
的 decoration
属性来实现
Svg 使用 Image Provide 的方式加载
之前写过一个 blog
键盘事件的监听
官网的文档不是很详细,可能实现的时候需要绕一些
关键在于需要设置 FocusScope.of(context).requestFocus(node);
,如果不加的化是监听不到键盘事件的,但是文档里面没有提及。
…
参考
https://flutter.dev/docs/development/data-and-backend/json
https://flutter.dev/docs/development/packages-and-plugins/developing-packages
https://medium.com/flutter-community/why-use-rxdart-and-how-we-can-use-with-bloc-pattern-in-flutter-a64ca2c7c52d
https://github.com/felangel/bloc
https://github.com/ReactiveX/rxdart
https://github.com/dart-lang/dartdoc
https://github.com/fastlane/fastlane
https://github.com/fluttercommunity/flutter_launcher_icons
https://flutter.beer/
https://api.flutter.dev/flutter/widgets/RawKeyboardListener-class.html