哈喽,我是老刘
老刘做Flutter开发六年多了,经手过的项目很多,但已有项目中途接手的情况并不算多。
恰巧近期经手两个中途接棒的外包项目,有一些感触分享给大家。
这两个项目都是老刘组织人手帮外地的公司用Flutter做App开发业务的。其中一个是短期项目属于工业领域的App和AI还沾点边。另一个是长期项目,属于金融领域的。
但是这两个项目都呈现出典型的开发者为实现快速交付,在项目中堆砌了大量三方库和临时拼凑的代码。
作为一个开发者,我很能理解当初这些开发者的心理。
还记得我刚学Android开发的时候,需要实现一个列表页,每个列表项现在想想都很简单。
但我当时一门心思就想着找到一个现成的三方库,能有一个组件自动包含需求里面的布局。
因为对当时的我来说,实现一个自定义的布局item是挺复杂的事情。
那么接下来我们先来说说这个现象本身,然后讨论一下如何做正确的技术选型,尤其是在一个项目开始的时候。
一、被三方库绑架的技术架构
在这两个Flutter项目中,开发者几乎为每个功能模块都引入了独立的三方库:从路由管理到网络请求,从状态管理到UI组件,甚至基础的工具函数都依赖外部库实现。这种"拿来主义"导致项目呈现出明显的拼凑感:
- 路由系统深度绑定第三方框架
- 状态管理混用Provider/GetX/Riverpod等多套方案
- 基础工具类直接照搬pub.dev搜索结果前三条的库
这种开发方式看似快速高效,实则会埋下严重的技术隐患。
项目最终变成了三方库的"缝合怪",开发者的核心工作变成了在不同库的文档和API之间疲于奔命。
二、过度依赖的隐形成本
当然这种现象和项目人员不稳定有直接的关系。因为不需要长期维护,开发完可能就走人了,所以很多人选择自己最熟悉开发速度最快的方案。至于架构设计、公共功能的抽象封装大概率是没有的。
但是一旦项目需要长期迭代下去,随着各种各样需求的加入,前期图省事的隐形成本就开始逐渐显露出来了。
- 维护风险
很多三方库是个人或者小团队维护,生命周期比较短。
很可能你的项目正如火如荼呢,人家库就停更了。 - 定制化困境
在需要扩展功能时(如给路由系统增加埋点能力),发现三方库的抽象层级过高,难以在不修改库源码的情况下实现需求。
老刘的团队使用TDD开发模式,对定制化方面的要求会更高一些。 - 调试黑洞
很多开发引入三方库的目的就是为了减少工作量,因此更不会去关系三方库本身的实现逻辑。
但是一旦碰到bug定位,尤其是需要深入到三方库内部定位的场景,就会非常被动。 - 技术债利滚利
像状态管理、路由管理这样的核心功能,一旦定下来后期更改的成本是非常高的。
如果一开始随便选择了一个,后期发现不合适自己的项目,需要付出极大的开发和测试成本才能更换。
了解了这些问题,接下来我们看看如何在项目初期进行正确的技术选型。
三、正确的技术选型方法
- 基础建设自研原则
Flutter SDK的很多功能大家用起来觉得不方便一个很大的原因是这些功能和组件都是针对多种不同的场景设计的。
Flutter需要支持各种不同类型的app开发,所以组件就需要提供大量的可配置参数来满足不同场景的需求。
所以实际上很多组件并不是让大家直接使用的,而是让大家基于这些可配置的参数进行二次封装。
实际项目中应该使用的主要是这些二次封装后的组件。
举个例子,我们App中最常见的对话框。
一个App中的对话框通常会有固定的风格,比如背景色、按钮颜色、圆角、字体、动画等等。
我们基于Flutter自带的对话框组件,将这些通用的风格封装成项目中公共的对话框组件。
在业务逻辑的开发中只需要传入具体对话框的标题、内容和确认按钮的点击回调即可展示对话框。
对于Flutter SDK已提供良好支持的基础功能(如路由、Isolate通信、PlatformChannel等),更应该基于原生API进行二次封装。例如:
javascript
// 自定义路由封装示例
class AppNavigator {
static Future<T?> push<T>(BuildContext context, AppRoute route) {
return Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => route.builder(),
transitionsBuilder: (_, a, __, child) =>
_buildTransition(a, child, route.transition),
),
);
}
static TransitionBuilder _buildTransition = (...) {...}
}
作为这个封装的使用者,用起来是不是和很多三方的路由库很类似?
但是所有代码都是你可控的部分。
- 三方库引入评估矩阵
基础设施自研不代表不使用三方库,很多独立的功能使用封装好的三方库的确能大幅提高开发效率。
但是在引入三方库时需要遵循一定的标准:
- 维护活跃度(最近6个月commit频率)
- 测试覆盖率(不低于80%)
- 依赖层级(是否带来额外依赖)
- 代码质量(关键类的设计合理性)
- 逃生通道(能否方便地替换/移除)
当然,每个项目的情况都不一样,应该根据自己项目的具体情况进行筛选标准的增减。
- 抽象隔离设计
选择合适的三方库后也不建议直接引入项目中使用。
特别是对于关键模块来说,对引入的三方库建议进行封装。
下面以比较常见的网络库dio为例说明:
dart
// 网络库抽象示例
abstract class HttpClient {
Future<Response> get(String url);
}
// Dio实现
class DioClient implements HttpClient {
final Dio _dio = Dio();
@override
Future<Response> get(String url) => _dio.get(url);
}
// 封装代理
class AppHttpClient extends HttpClient {
static final instance = DioClient();
@override
Future<Response> get(String url) async {
// 统一添加拦截器、日志等
}
}
我们将底层使用dio的部分封装在HttpClient的某一个实现类中。
这样即使后续想要切换网络库,也只需要增加一个具体的实现类即可。
甚至可以实现多个网络请求库并存、随意切换。
实现对不同网络请求库进行灰度测试和容错备份等场景。
- 核心能力沉淀
随着项目的推进,一定会积累很多通用的工具和方法。
把这些内容提取出来,文档化、独立化。就可以推广到团队的其他项目中。
例如建立项目自身的utils库,将经过验证的工具函数沉淀为内部依赖:
yaml
# pubspec.yaml
dependencies:
core_utils:
path: ../core_utils
四、架构设计的平衡之道
前面提到的问题更多的是技术选型与封装。
而在这两个项目中,关于架构设计也不得不提一下。
试想你们团队来了一个新人,如何让他能快速掌握现有项目代码的脉络?如何能快速理解自己开发工作该如何进行模块拆分,该把不同代码放到哪里去?
这个一方面要有健全的文档系统,另一方面就要有一个简介清晰的架构体系。
老刘自己带的项目在Flutter上通常会使用bloc做状态管理,并且基于bloc封装页面、业务逻辑等功能模块的基类。
而这些中途接手的项目就比较麻烦,早期代码相对混乱和堆砌。
- 有些使用Getx,但是用的不太对。
- 有些自己做了简单的UI和业务逻辑的拆分。
- 还有些甚至就直接都写在了一起。
在这样的项目中再引入bloc无疑会增加混乱度和复杂度。
所以老刘这次的思路是不引入额外的三方库,基于Flutter自身的ChangeNotifer和已经使用的Provider做一个简单的架构封装。
具体的封装方法老刘单独写一篇文章详细说明,这里只介绍一下基本原则。
1、状态管理的核心目标是关注点分离
业务逻辑与UI呈现必须解耦。
通过分层架构将网络请求、本地存储等副作用操作封装在独立的Service层。
View层仅负责数据展示和事件传递。
状态变更通过ChangeNotifier通知,配合Provider实现精准的局部刷新。
因此基于ChangeNotifier封装协调View和底层Service的控制器。
2、单向数据流必须严格把控
禁止在Widget中直接修改状态。
事件处理流程遵循"View触发-> Controller协调 -> Service执行业务 -> 更新Notifier状态"的闭环,确保状态变更路径可追溯。
3、组件间状态共享分层治理
通过Provider的多层级注入机制,将全局状态(如用户信息)通过顶层Provider或者单例进行维护。
局部状态(如页面级数据)通过页面及的Provider限定作用域。
这套方案很简单,甚至可以说是简陋,只封装了BasePage和BaseController两个基类。
但是在中型项目规模下,开发效率与代码可维护性可以达到较好平衡。
对于需要快速迭代的业务场景,这种轻量化架构可能比过度设计的方案更具生命力。
这里要说明一下,架构设计不能简单的等同于状态管理。
但是状态管理是日常开发中使用最频繁,对开发效率和质量影响最大的部分。
因此单独拿出来做一个快速的封装,其它部分可以后续逐步补充完整。
五、开发者成长启示
过度依赖三方库本质上是技术深度不足的表现。
当我们将每个三方库的引入都视为一次架构决策,而非简单的CTRL+C/V时,项目自然会呈现出优雅健壮的特质。
毕竟,真正的技术实力不在于知道多少库,而在于能否用最简洁可靠的方案解决问题。
这或许就是Flutter哲学中"Everything is a widget"给我们最深刻的启示------简单,但足够强大。
如果看到这里的同学对客户端开发或者Flutter开发感兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》