前端Flutter菜鸟之路

3,284 阅读7分钟

前言

用过的混合开发框架开发项目只有这几个 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 #工具函数

项目历程

  1. 使用 GetCli 生成项目
  2. 使用 FlutterImgSync 插件进行项目静态资源的生成,使项目图片使用方面规范
  3. 基于 Dio 的网络封装,使用 xxeta 加密,如果不想用Dio,也可以快速切换其它的网络请求库
  4. 基于 Getx 的规范实现国际化的封装,实现了英语和简体中文
  5. 使用 GetMiddleware 封装页面未登录拦截,需要权限的跳转到登录页,就是不能写异步代码,就很难受,就这一点就需要写更多代码解决登录失效的问题。
  6. 封装 StreamBuilder,实际是使用rxdart插件的BehaviorSubject,因为StreamBuilder只能单监听,自带的多监听达不到我想要的效果,所以使用rxdart,这个可以统一项目管理[Loading, NoData,Error]状态,业务只需要关注成功状态,且使用简单
  7. 将项目公用颜色,字体大小,公用的样式(比如阴影,边框等)写入到theme,团队不是很规范,所以公用的不是很多😓
  8. 给String和Number添加颜色,字体大小等扩展,能方便使用蓝湖的属性
  9. 全局状态(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手机上滑动会卡死,原因好像是滑动冲突的问题

image-20211104173137114.png

使用NestedScrollView就会解决部分手机卡顿的问题(TIP:华为手机在低端也不会卡~~)

image-20211104173000833.png

使用GetView的特殊情况处理

Get官方建议我们使用GetView作为页面的最外层Widget,也说几乎不需要使用StatefulWidget,但是很多情况是需要使用StatefulWidget的

  1. 比如需要缓存状态的页面 【PageView切换,需要保持页面的滚动状态】
  2. 比如需要多次重复进入的页面 【因为get的controller不会在执行onInit,所以需要使用StatefulWidget,在initState中进行页面的初始化操作】

经验教训

  1. 返回上一页面需要做一些刷新操作

    1. 比如【银行卡列表->添加银行卡】添加银行卡完成后,返回银行卡列表需要刷新列表,因为web前端写多了,经常去操作其它组件的实例,比如通过Get.find()找到银行卡列表的实例来更新页面,当然,这样看起来没什么问题,但是如果添加银行卡页面还可以从其它页面(不是银行卡列表),进入就会导致Get.find()这句话报错了,因为银行卡列表没有生成的,所以返回页面做操作最好的方式就是通过toNamed("add-bank").then的方式监听页面返回,自己内部实现刷新操作。 最好做到自己做自己的事情。
  2. 数据管理问题

    1. 如果某一个数据需要多个页面使用,最好单独建立一个controller来保存数据,不要放在某一个页面的controller中。
  3. 自定义实现全局Loading

    1. 刚开始觉得一个Loading没必要去安装第三方插件,就自己完成。

    2. 使用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()
      
  4. App经常登录失效各种问题解决(自我感觉不是正确的~~)

    1. 部分页面需要权限才能访问,这个使用中间件就可以解决
    2. 如果项目是单一登录,就可能导致使用途中被人挤下,当然如果做了socket链接,问题很容易解决,在失效时候将本地登录状态修改就好了,如果没有socket链接,只有在请求接口的时候才知道是否失效,如果在请求中拦截失效操作了的,就会直接跳转到登录页面,然后登录成功返回上一页面如果想要刷新页面,就不是很好办到了。没有想到完美解决方案。现在就是使用EventBus的方案进行通知,感觉不是很好!
  5. Getx的controller的onClose什么周期里面别放入其它组件dispose方法,销毁方法dispose应该放在controller的dispose生命周期中,我知道这样说有点白痴,但是确实是会写出来这样的代码,且普遍情况不会报错,但是报错的时候就很不好定位问题了,还是对生命周期理解不透彻啊

  6. 如何使用RouteAware相关的钩子函数

    // 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();
      }
    }
    

插件安利

  1. FlutterImgSync 方便且统一的使用静态图片

  2. GetX 快速插入Getx相关的widget

  3. JsonToDart 带有空安全的model生成插件

  4. Flutter Snippets 能快速的生成代码片段