写完第一个Flutter项目后的总结

目前跨平台解决方案有很多,各有各的优缺点。项目技术选型是需要根据自己的需求和技术栈,并结合各种跨平台技术的优缺点去选择。我选择 Flutter 的主要原因是我的本职工作是做原生开发,比起 React Native 等以 JavaScript 为主语言的跨平台技术,Flutter的开发模式对于原生开发会更加友好一些(个人觉得)。

我写下这篇文章也只是为了记录自己的一次跨平台实践,如果能够帮助到正在学习Flutter,或者正在使用Flutter写项目的初学者就更好了。

学习过程

我从开始学习Flutter,到完成第一个项目,一共耗时2个月左右。先学一遍 Dart 语法,再粗略看一遍翻译后的Flutter文档:https://flutterchina.club/docs/ (如果英文水平足够,直接看官方文档更好),让自己对Flutter先有一个整体的认识。

然后看了一本Flutter的书籍: https://book.flutterchina.club/ ,就开始结合搜索引擎和 Stack Overflow 写项目了。如果资金允许,也可以买一套口碑不错的Flutter项目实战课程进行学习,将会更容易上手。

我学习到什么程度,才可以开始写项目呢?

很多新手在写第一个项目的时候,都不敢下手,想继续增加一些知识点才开始写项目。但往往实践才是最有效的学习方式,重复低效的基础学习只会打击自信心。不要怀疑自己,Flutter项目开发上手是很快的,不像其他 webapp 系的跨平台解决方案,还需要你有一定的前端 HTML/CSS/JS 基础。而Flutter只需要你会一点 Widget 知识,就可以开始了。遇到不会的布局,优先想到的是套个 Container ,不要怕层次太多,性能太差,毕竟你才开始写呢。。。

开发中常见的疑问

在开始写项目的时候,会遇到很多问题,这里我列举一些我遇到的,并且认为比较重要的问题。

一、项目结构如何组织?

想要写一个完整的项目,必须要有清晰的项目结构,方便组织代码和快速定位需要的资源。下图是按照我自己的习惯和网上一些开源项目来组织的项目结构:

写完第一个Flutter项目后的总结

二、网络请求如何封装?

Flutter网络请求大多都是使用 Dio 库,我这里也是。我们可以将 Dio 库的请求方法封装起来,方便统一处理请求头、响应实体等,也方便后续修改。这种做法有一个比较文雅的名字,叫做隔离网络请求框架

下图是我封装的网络请求(仅供参考):

写完第一个Flutter项目后的总结

网络请求类我封装成单例,减少重复对象创建:

请求方法提供了返回值和回调方法两种方式来返回响应结果,方便使用 FutureBuilder 和回调调用的方式:

一般后端接口返回的数据格式都是一致的,我们可以定义一个通用的响应实体来接收,比如:

然后使用 Dio 去请求接口,并将返回的数据转换成我们定义的响应实体:

注意:这里的请求方式只是作为示例的伪代码,自己封装的时候,需要自己判断请求方法去调用 Dio 不同的请求方法,并传递对应的参数。

三、需不需要JSON转模型?

在原生iOS和Android开发中,绝大部分开发者都会将请求后端接口返回的数据转成 Model/Entity 类去使用。但是在Flutter中,这个问题就是智者见智仁者见仁了。

我个人的做法是:如果接口返回的数据非常简单,并且只需要获取返回数据中的个别字段的数据,可以直接获取。否则建议先把返回的数据转换成 Model/Entity 类再去使用。

我使用的模型创建插件是 Android Studio 中的 FlutterJsonBeanFactory 插件,可以直接搜索安装。

注意:Dart 语言是强类型语言,并且类型确定后不支持自动转换。比如后端返回了一个整数,但是 Model/Entity 中定义的是 double 类型,就会发生错误。我们可以把这种不确定整数还是小数的数值型定义为 num 类型。

四、登录状态和用户数据如何保存?

在前端开发中,大家都习惯直接保存登录返回的token,在下次打开网页时,根据token去请求后端的用户数据,也可以用于判断用户登录是否过期。但是在Flutter开发中,这种方式个人感觉体验并不是很好。

因为网页是放在服务器上的,反正加载网页都需要时间,先请求个用户数据也没啥大不了的。但是在Flutter中,想要获得更流畅的原生体验,还是尽可能多的利用原生的缓存功能,比如用户数据、图片和一些需要直接显示的后端数据。一些数据展示类的页面,还会直接缓存这些数据,让用户在断网状态或者第一次请求接口返回结果前,能直接看到界面效果。

Flutter中缓存数据一般会使用 SQLite 和本地文件缓存,如果是一些文本数据,则可以通过第三方插件(shared_preferences),调用原生的偏好设置去缓存小量文本数据,比如用户账号密码、用户信息等(敏感信息自己加密保存)。

下面是我封装的第三方插件的缓存方法,作用是为了在获取数据的时候直接同步获取:

用户信息缓存示例:

在运行APP的时候,初始化插件并获取用户数据:

五、涉及到原生功能怎么办?

当我们需要某些原生功能,或者接入某些原生的SDK时,我们可以在 pub.dev 上搜索有没有现成的插件可供直接使用。比如高德地图、七牛云OSS、ShareSDK、WebView等等,都是已经有现成的插件可以直接使用的。

但是如果没有现成的怎么办?比如我项目中使用了百度AI的OCR SDK,在 pub.dev 上并没有现成的插件,我们就只能直接开发插件。

Flutter的插件开发很简单,但是如果涉及到原生功能的插件开发,就必须要有一定的原生开发基础。就像有一句话:任何混合开发中涉及到原生功能开发的,都需要三端都会。如果不会怎么办?不会就学啊。这里不会详细介绍插件开发过程,网上随便搜Flutter插件开发,有很多文章。

如果只是我们自己项目中使用的插件,建议在项目根目录,也就是 lib 的同级目录下创建一个 plugins 来存放我们创建的Flutter插件项目。并在 pubspec.yaml 中引用。

六、图片素材怎么管理?

随着项目增大,项目中使用的素材也会越来越多。所以项目中的素材最好是在项目开始,就自己管理好。

Flutter是支持直接目录引入的,我们可以把素材根据不同的界面或者不同的模块来分类,然后引入目录。如下图所示:

写完第一个Flutter项目后的总结

然后提供一个方便获取图片路径的方法,减少我们在使用过程中的代码量:

七、屏幕如何适配?

现在手机屏幕的尺寸越来越多,移动端的屏幕适配也一直是一个重要的话题。在原生开发中也出现了一些非常方便的适配框架,比如Android平台的今日头条提供的自动适配框架。在Flutter中,并没有这种自动适配的库,不过也有大佬提供了一个比较方便的屏幕适配库,用来获取和计算屏幕尺寸,就是 flutter_screenutil

八、路由跳转

路由跳转已经有比较成熟的第三方库可直接使用,比如 fluro 。不过为了学习,我第一个项目,是直接使用Flutter的原生的方法。下面是最常用的几个路由跳转方法,一般使用这几个路由跳转方法,足够完成一个项目了。

最简单也是最常用的页面跳转方法,向路由栈中 push 一个路由,类似iOS里的 pushViewController

返回上一个页面,类似iOS里的 popViewController

移除路由栈中的所有路由,再跳转到一个页面,最常用的就是退出登录,并跳转到登录页面:

跳转页面后并且不需要再返回。比如在登录页面登录成功后,跳转到主界面:

返回到路由栈的第一个页面,类似iOS里的 popToRootViewController ,比如我在APP里连续 push 了多个页面后,我想直接返回首页,就可以用这个方法。也可以用于返回到路由栈中的某个页面。

开发期间卡壳的时候

我写这个项目,有2个地方卡了我比较多的时间。就是 AndroidX 适配和涉及页面跳转的插件开发。

建议在项目开始,就适配好 AndroidX ,这样在后续增加第三方库时,也可以顺带留意这个问题。我就是因为最初没有适配 AndroidX ,等到项目开开发完的时候才去适配,就遇到了多个第三方插件不兼容 AndroidX ,导致我需要更换第三方插件,从而浪费了很多时间。

在涉及页面跳转的插件开发中,iOS端可以直接获取到 keyWindowrootViewController 来进行页面跳转。然而 Android 中并没有这么顺心,都知道 Android 中页面跳转是需要上下文作为参数的,我们的上下文从哪里获取?经过我的一顿猛虎操作,结果发现我们可以直接获取到上下文。

然后我们就可以通过 registrar.activity()registrar.context() 获取我们需要的上下文了。

编码习惯

一定要注意抽取 Widget ,一定要注意抽取 Widget ,一定要注意抽取 Widget 。不要一个页面的UI代码都写到一块层层嵌套,这样会导致代码阅读性差、可维护性差,也可能会导致被同事打死。比如下面代码,把基础结构写在 build 方法中,局部的UI单独抽取:

示例效果图如下所示:

写完第一个Flutter项目后的总结

项目中一些通用的UI尽量抽取到单独的类,方便使用。比如一些样式相同的按钮、输入框、弹窗、导航栏等等,类似下图所示,我抽取了一些小部件在 widget 包下:

写完第一个Flutter项目后的总结

一些常用的工具类或者代码块,也建议抽取出来,减少重复代码,增加可维护性。

写完第一个Flutter项目后的总结
六阿哥

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: