前端Flutter菜鸟之路用过的混合开发框架开发项目只有这几个 ApiCloud Ionic Taro Flutter
前言
用过的混合开发框架开发项目只有这几个 ApiCloud
Ionic
Taro
Flutter
,使用下来Flutter
确实是最让我喜欢的,最大的原因就是因为将语言换成 Dart
了吧 ,不是我装怪,确实强类型语言就是好啊~~~
为什么选择Flutter开发
最大的原因也就是大佬们认为Android和ios两端的大佬不好招聘且贵,web端小老弟人多又便宜吗/(ㄒoㄒ)/~~ ? 哈哈哈 😂苦笑ing......,于是乎就踏上了flutter从 0 到 有。
难道就因为浏览器一打开Console就可以输出Hello World的原因?
有时候我就在想,都是做前端开发,为啥web就这么不招人待见!!!
开始,起步
我虽然不是公司前端负责人,但是对于陌生的技术总喜欢跑在前面
我用了三天的时间把项目整体架构给搭建起来了,当然我是根据网上视频模仿来的。
在之前使用过Flutter开发,因为是第一次开发,所以按照官网走的,没有使用什么框架(Getx)之类的,开发过程是相当痛苦,和其它混合式开发差别不是一般大。
所以在有了经验后,这次开发App使用了 Getx
这个框架,类似于前端的Angular和Vue,所以只要把Flutter的UI写好了,其它逻辑方面的也就不是很难了,因为我觉得Dart和Javascript还是有很多相似之处的,当然Dart的空安全在刚接触的时候是很恶心的~~
技术栈
项目是用 GetCli 脚手架进行开发的,类似于Angular,可以使用命令生成页面,路由,Model等
dependencies:
sp_util: ^2.0.3
dio: ^4.0.0
intl: ^0.17.0
video_player: ^2.2.5
get: 4.3.8
xxtea: ^2.1.0
murmurhash: ^1.0.0
device_info_plus: ^3.1.0
pull_to_refresh: ^2.0.0
flutter_switch: 0.3.2
flutter_pickers: ^2.1.3
webview_flutter: ^2.1.1
flutter_datetime_picker: ^1.5.1
url_launcher: 6.0.12
flutter_staggered_animations: ^1.0.0
daydart: ^0.0.5
qr_flutter: 4.0.0
screenshot: ^1.2.3
image_gallery_saver: '^1.7.1'
package_info_plus: ^1.3.0
rxdart: ^0.27.2
event_bus: ^2.0.0
目录结构
- app #GetCli自动生成的文件,通过命令生成页面,路由
- components #存放页面公用组件
- http #基于Dio的网络请求封装,和数据Model
- lang #国际化
- middleware #用于Getx路由的中间件
- stream #对于StreamBuilder的封装
- theme #保存项目统一的颜色,样式,字体大小等
- utils #工具函数
项目历程
- 使用
GetCli
生成项目 - 使用
FlutterImgSync
插件进行项目静态资源的生成,使项目图片使用方面规范 - 基于
Dio
的网络封装,使用xxeta
加密,如果不想用Dio
,也可以快速切换其它的网络请求库 - 基于
Getx
的规范实现国际化的封装,实现了英语和简体中文 - 使用 GetMiddleware 封装页面未登录拦截,需要权限的跳转到登录页,就是不能写异步代码,就很难受,就这一点就需要写更多代码解决登录失效的问题。
- 封装 StreamBuilder,实际是使用rxdart插件的BehaviorSubject,因为StreamBuilder只能单监听,自带的多监听达不到我想要的效果,所以使用rxdart,这个可以统一项目管理[Loading, NoData,Error]状态,业务只需要关注成功状态,且使用简单
- 将项目公用颜色,字体大小,公用的样式(比如阴影,边框等)写入到theme,团队不是很规范,所以公用的不是很多😓
- 给String和Number添加颜色,字体大小等扩展,能方便使用蓝湖的属性
- 全局状态(Controller)写入,存整个项目公用的数据,token,userInfo等
项目遇到的问题
/// 初始化存储之前需要调用下面这句代码,不然就要报错,不知道为啥~~
WidgetsFlutterBinding.ensureInitialized();
/// 初始化存储
await SpUtil.getInstance();
/// 将MaterialApp 换成 GetMaterialApp
GetMaterialApp(
home: HomePage(), // 如果使用 home 属性初始化页面,可能会导致引导页白屏
initialRoute: Routes.HOME_PAGE // 换成这种方式初始化页面就好了
)
Obx构建期间错误,此链接可以解决 Flutter: setState() or markNeedsBuild() called during build. Using future builder and obx
跟页面使用TabBar(CustomAppBar内部是AppBar)和TabbarView,在小米10和OPPO手机上滑动会卡死,原因好像是滑动冲突的问题
使用NestedScrollView就会解决部分手机卡顿的问题(TIP:华为手机在低端也不会卡~~)
使用GetView的特殊情况处理
Get官方建议我们使用GetView作为页面的最外层Widget,也说几乎不需要使用StatefulWidget,但是很多情况是需要使用StatefulWidget的
- 比如需要缓存状态的页面 【PageView切换,需要保持页面的滚动状态】
- 比如需要多次重复进入的页面 【因为get的controller不会在执行onInit,所以需要使用StatefulWidget,在initState中进行页面的初始化操作】
经验教训
-
返回上一页面需要做一些刷新操作
- 比如【银行卡列表->添加银行卡】添加银行卡完成后,返回银行卡列表需要刷新列表,因为web前端写多了,经常去操作其它组件的实例,比如通过Get.find()找到银行卡列表的实例来更新页面,当然,这样看起来没什么问题,但是如果添加银行卡页面还可以从其它页面(不是银行卡列表),进入就会导致Get.find()这句话报错了,因为银行卡列表没有生成的,所以返回页面做操作最好的方式就是通过toNamed("add-bank").then的方式监听页面返回,自己内部实现刷新操作。 最好做到自己做自己的事情。
-
数据管理问题
- 如果某一个数据需要多个页面使用,最好单独建立一个controller来保存数据,不要放在某一个页面的controller中。
-
自定义实现全局Loading
-
刚开始觉得一个Loading没必要去安装第三方插件,就自己完成。
-
使用OverlayEntry完成Loading效果,但是OverlayEntry要到页面种,需要获取上下文,所以刚开始是在使用这个方法把context传入到页面种,后面发现有些地方获取不到context,所以去看其它插件的实现方式,发现都没有使用context,后面发现MaterialApp的navigatorKey可以获取当前的上下文
//main.dart // 建立一个全局Key GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // 写入GetMaterialApp中 GetMaterialApp( navigatorKey: navigatorKey ) // loading.dart // 通过此方法就可以达到去context化 navigatorKey.currentState!.overlay!.insert()
-
-
App经常登录失效各种问题解决(自我感觉不是正确的~~)
- 部分页面需要权限才能访问,这个使用中间件就可以解决
- 如果项目是单一登录,就可能导致使用途中被人挤下,当然如果做了socket链接,问题很容易解决,在失效时候将本地登录状态修改就好了,如果没有socket链接,只有在请求接口的时候才知道是否失效,如果在请求中拦截失效操作了的,就会直接跳转到登录页面,然后登录成功返回上一页面如果想要刷新页面,就不是很好办到了。没有想到完美解决方案。现在就是使用EventBus的方案进行通知,感觉不是很好!
-
Getx的controller的onClose什么周期里面别放入其它组件dispose方法,销毁方法dispose应该放在controller的dispose生命周期中,我知道这样说有点白痴,但是确实是会写出来这样的代码,且普遍情况不会报错,但是报错的时候就很不好定位问题了,还是对生命周期理解不透彻啊
-
// marin.dart RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); GetMaterialApp( navigatorObservers: [routeObserver] )
// 可以新建一个文件 abstract class RouteAwareState<T extends StatefulWidget> extends State<T> with RouteAware { @override void didChangeDependencies() { print("didChangeDependencies $widget"); routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); //Subscribe it here super.didChangeDependencies(); } @override void didPush() { print('didPush $widget'); } @override void didPopNext() { print('didPopNext $widget'); } @override void didPop() { print('didPop $widget'); } @override void didPushNext() { print('didPushNext $widget'); } @override void dispose() { print("dispose $widget"); routeObserver.unsubscribe(this); super.dispose(); } }
// 需要使用RouteAware相关事件的widget class MyView extends StatefulWidget { const MyView({Key? key}) : super(key: key); @override _MyViewState createState() => _MyViewState(); } // 继承RouteAwareState就可以了,就不需要继承state class _MyViewState extends RouteAwareState<MyView> { @override Widget build(BuildContext context) { return MyViewContent(); } }
插件安利
-
FlutterImgSync 方便且统一的使用静态图片
-
GetX 快速插入Getx相关的widget
-
JsonToDart 带有空安全的model生成插件
-
Flutter Snippets 能快速的生成代码片段
转载自:https://juejin.cn/post/7029746211519217671