技术文章

奇妙_艺术家
2026-06-21 14:18
阅读 588

秋招前夕死磕Flutter状态管理从Bloc到Riverpod的血泪探索

哈喽各位,这里是正在秋招苦海里疯狂扑腾的985计科大三学生。最近北京这天气说冷就冷,每天挤着地铁13号线,通勤整整1个小时,回到西二旗附近的出租屋往往已经晚上八点了。不过我这人有个毛病,白天在公司被各种杂事碎尸万段后,晚上反而精神抖擞,特别喜欢深夜写代码,凌晨一两点的时候思路最清晰,效率也最高。

为了秋招简历上能有个拿得出手的跨端项目,我前阵子头铁选了Flutter。我做的这个项目是个类似校园分布式任务调度的移动端看板,能实时查看各个节点的任务状态。结果在状态管理这块儿,我结结实实地栽了几个大跟头。今天趁着周末不用去公司卷,赶紧把这段时间的血泪经验盘一盘,希望能给同样在Flutter坑里挣扎的兄弟们避避雷。

从Provider到全局状态乱飞的噩梦

一开始,我图省事,直接上了Provider。讲真,小项目用Provider确实香,几行代码就能跑起来。但随着项目复杂度上去,产品经理又开始疯狂加需求,全局状态满天飞,我直接裂开了。

你们能想象吗?一个深层嵌套的Widget里,为了拿个状态,我得写 Provider.of<TaskState>(context, listen: false)。有时候context找错了,或者MultiProvider的层级没包对,直接报红。测试小姐姐一跑起来,控制台满屏的 ProviderNotFoundException,当时真的想砸电脑。

更恶心的是,状态逻辑和UI耦合得太深。我平时对分布式系统挺感兴趣的,研究过不少微服务架构,深知“单一数据源”和“高内聚低耦合”的重要性。但Provider用多了,我感觉整个项目的状态就像个没有治理过的单体应用,各种隐式依赖,改一个地方崩三个地方。

转向Bloc: boilerplate代码多到令人发指

被Provider折磨了两周后,我痛定思痛,决定换状态管理方案。看了一圈技术博客,大家都说Bloc是企业级首选,严谨、规范。我心想,规范好啊,正好治治我这乱飞的代码。

于是,我花了一个通宵把项目重构成了Bloc。结果呢?严谨是严谨了,但我快被那些boilerplate(样板)代码逼疯了。

在Bloc里,哪怕只是改变一个按钮的 loading 状态,我也得经历以下流程:

  1. 定义一个 Event(比如 FetchTaskEvent
  2. 定义一个 State(比如 TaskLoadingState
  3. 在 Bloc 里写 on<FetchTaskEvent> 的处理逻辑
  4. 在 UI 层用 context.read<TaskBloc>().add(FetchTaskEvent()) 触发
  5. BlocBuilder 去监听状态变化

一个小小的状态变更,要建好几个文件,写一堆模板代码。上周五晚上加班时,我就因为少写了一个 copyWith 方法里的字段,导致状态没更新,盯着屏幕眼瞎找了两个小时。

当时我实在憋不住了,打开通义千问,把那一长串 Bloc 代码和报错信息扔给它,让它帮我找 bug。大模型确实给力,一眼就看出是我在 TaskStatecopyWith 里把 status 字段漏了。虽然解决了问题,但我心里开始犯嘀咕:这种为了规范而牺牲开发效率的方案,真的是最优解吗?

遇见Riverpod:降维打击与依赖注入的艺术

就在我纠结要不要继续忍受Bloc的时候,我在掘金上刷到了Riverpod。作者也是Provider的作者,但他自己把Provider给“革命”了。

仔细研究了一下Riverpod的文档,我直呼内行。它彻底抛弃了 BuildContext,改用全局的 ProviderContainer(在Widget里通过 WidgetRef 访问)。这一下,状态逻辑和UI彻底解耦了,连单元测试都变得无比简单,因为你根本不需要去Mock什么Widget树。

更让我兴奋的是,Riverpod的依赖图设计,简直跟我研究的分布式系统里的服务依赖拓扑一模一样!它天生支持依赖注入,Provider之间可以互相依赖,而且自带缓存和自动销毁机制。

比如我的任务看板里,任务列表依赖用户的登录状态,任务详情又依赖任务列表的选中项。用Riverpod写出来极其优雅:

// 用户状态Provider
final userProvider = StateNotifierProvider<UserNotifier, User>((ref) {
  return UserNotifier();
});

// 任务列表Provider,依赖用户状态
final taskListProvider = FutureProvider.autoDispose<List<Task>>((ref) async {
  // 监听用户状态,如果用户变了,自动重新获取任务
  final user = ref.watch(userProvider);
  if (user == null) throw Exception('未登录');
  
  // 模拟网络请求
  final response = await api.fetchTasks(user.id);
  return response.tasks;
});

// UI层使用
class TaskListWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听任务列表状态
    final tasksAsync = ref.watch(taskListProvider);
    
    return tasksAsync.when(
      data: (tasks) => ListView.builder(
        itemCount: tasks.length,
        itemBuilder: (context, index) => TaskItem(task: tasks[index]),
      ),
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (e, s) => Center(child: Text('加载失败: $e')),
    );
  }
}

看到这段代码的时候,我长舒了一口气,终于不用满世界找 context 了!

在重构Riverpod代码的过程中,我强烈安利一下 Augment Code。这玩意儿的代码补全简直神了,特别是写Riverpod那种复杂的泛型和依赖树时,我刚敲完 ref.watch(,它就能精准预测出我要watch的Provider,甚至连后面的 .when 回调都能一键补全。相比之下,我本来想偷懒用 讯飞星火 搞点 AI写作,让它帮我生成一下这些Provider的注释,结果它生成的注释全是“这是一个获取数据的函数”这种废话,气得我直接全删了。写代码的AI和写文章的AI,目前看来还是得术业有专攻。

移动开发实战:那些不为人知的坑

状态管理理顺了,接下来就是移动端特有的折磨。做跨端开发,你以为写一套代码就能走天下?太天真了。

1. 平台适配的痛 iOS和Android的导航栏、底部安全区(SafeArea)差异巨大。有一次我在Android上调试得好好的,底部按钮完美贴合屏幕。结果打包到iOS上,直接跑到了Home Indicator(底部小黑条)下面,根本点不到。 后来我学乖了,所有涉及底部操作的页面,老老实实套上 SafeArea,并且用 MediaQuery.of(context).padding.bottom 动态计算底部间距。

2. 列表滑动的性能优化 任务看板里有个长列表,数据量一大,滑动就开始掉帧。一开始我以为是 ListView.builder 没写对,后来用Flutter DevTools的Performance Overlay一看,好家伙,每次滑动都在疯狂rebuild。 排查了一圈,发现是我在 build 方法里直接用了 ref.watch 去监听一个频繁变化的全局状态(比如当前时间)。 血泪教训:在Riverpod中,ref.watch 会监听变化并触发rebuild,而 ref.read 只是读取一次。如果某个状态变化不需要刷新UI,千万用 ref.read! 我把时间状态改成 ref.read(timeProvider),并在需要的地方用 ref.listen 去处理副作用,列表滑动瞬间丝滑如德芙,稳在60帧。

3. 应用市场发布的血泪史 项目做完,总得上架吧。安卓市场还好,搞个软著,弄好签名,各大应用商店走一遍流程。iOS那边才是真的教做人。 第一次提交App Store,直接被拒。理由是我的App请求了相册权限,但没在隐私说明里写清楚用途。我赶紧去 Info.plist 里补上 NSPhotoLibraryUsageDescription。 第二次提交,又被拒,说我的App里有“隐藏功能”(其实是我用了个第三方的热更新SDK,苹果对这种动态下发代码的行为查得极严)。最后我狠心把热更新模块全删了,老老实实走TestFlight,磨了整整一周才终于上架。看着App Store里显示“准备提交”,那一刻,感觉前几个月掉的头发都值了。

总结与秋招展望

折腾了这么一大圈,从Provider的混乱,到Bloc的繁琐,再到Riverpod的优雅,我对Flutter的状态管理算是有了切肤之体会。

其实,状态管理从来没有绝对的“银弹”。Provider适合小步快跑的MVP阶段;Bloc适合团队庞大、需要严格规范的大型企业级项目;而Riverpod,则是兼顾了开发体验和架构优雅性的极佳选择,尤其适合喜欢函数式编程和依赖注入的开发者。

作为一名还在秋招泥潭里挣扎的大三学生,这次项目重构不仅让我的简历多了一个亮眼的“跨端架构优化”亮点,更让我深刻理解了“技术服务于业务,架构服务于团队”的道理。分布式系统里的微服务治理,和移动端的状态管理,本质上都是在解决“复杂系统中的状态一致性与依赖解耦”问题。

现在,我的项目已经稳定运行,秋招的提前批也投出去了不少。每天深夜敲完代码,看着控制台不再报红,那种纯粹的成就感,大概就是我们这些程序员坚持下去的最大动力吧。

祝各位秋招的兄弟们都能拿到心仪的Offer,咱们顶峰相见!如果有关于Flutter或者分布式系统的问题,欢迎在评论区交流,我尽量在通勤的地铁上(信号好的话)回复大家。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝