setState()
怎么更新界面?Stream
怎么推送数据?背后都是这位“情报站长”在忙活!
想象一下这个场景:
你是个忙碌的店长 📱,管理着你的奶茶店帝国。你有一个超级重要的任务:每当店里发生大事(比如新奶茶配方到货、今日优惠更新、VIP顾客进店),你需要立刻通知所有相关人员!
- 前台点单员需要知道新配方来做奶茶。
- 营销专员需要知道优惠信息来更新海报。
- 店长助理需要知道 VIP 进店来准备特殊服务。
店长不可能每次事件都亲自跑去挨个通知每个人,效率太低,而且店长可能正在忙其他事!
解决方案?建立一个“情报广播站”:
- 店长 (Subject / Observable): 他是事件的源头(比如“新配方到货”)。
- 情报广播站 (Notification Center): 负责管理和分发消息。
- 员工们 (Observers): 点单员、营销专员、店长助理。他们对特定事件感兴趣,提前向广播站登记(订阅) 说:“老板,有新品到货时请务必通知我!”
- 事件发生: 当店长(事件源)有新配方到货时,他不用管谁要听,只需告诉广播站:“广播!新配方到货了!”
- 自动通知: 广播站立刻查找所有订阅了“新配方到货”通知的员工,挨个给他们发消息:“嘿,点单员A、点单员B、营销专员C,新配方来了,快处理!”
- 员工行动: 收到消息的员工根据消息内容更新自己的工作(点单员更新菜单,营销专员制作海报)。
在 Flutter 世界里,这个“情报广播站”就是“观察者模式”!
观察者模式是啥?一句话:
定义对象间的一种一对多的依赖关系,当一个对象(Subject/Observable,被观察者)的状态发生改变时,所有依赖于它的对象(Observers,观察者)都会自动得到通知并更新。
为什么在 Flutter 里它无处不在?
因为 Flutter UI 的核心是 响应式编程!UI 应该是数据的映射。当数据变化时,UI 应该自动更新以反映最新的数据。观察者模式完美地实现了这个“数据变 -> UI 自动变”的机制!
Flutter 中的经典例子:
-
最最最核心的
setState()
:- 被观察者 (Subject):
State
对象本身(它持有数据,比如counter
)。 - 观察者 (Observer):
build
方法返回的整个 Widget 树(更准确地说,是框架内部管理的机制,它知道哪些 Widget 依赖于State
的数据)。 - 过程: 当你调用
setState(() { counter++; })
时:- 你修改了
State
对象的数据 (counter
)。 setState
内部会标记这个State
对象为“脏的”(状态改变了)。- Flutter 框架(就像那个广播站)在下一帧绘制前,会检查所有“脏”的
State
。 - 框架找到对应的
State
对象,通知它:“你的数据变了,需要重建 UI!” State
对象重新调用build
方法。build
方法用新的counter
值重建 Widget 树。- UI 更新了! 🎉 点单员(UI)知道了新配方(
counter
值)并更新了菜单(显示的数字)。
- 你修改了
- 本质:
setState
触发的重建流程,其底层通知机制就蕴含着观察者模式的思想。Widget 树(或其部分)“观察”着State
对象的数据变化。
- 被观察者 (Subject):
-
Stream
和StreamBuilder
:- 被观察者 (Subject):
Stream
数据流(比如来自网络请求、传感器、定时器的连续数据)。 - 观察者 (Observer):
StreamBuilder
Widget。 - 过程:
- 你在 UI 中使用
StreamBuilder
,并给它一个Stream
。 StreamBuilder
订阅 (subscribe) 了这个Stream
(向广播站登记:“有数据来请通知我!”)。- 当
Stream
中有新数据emit
(发射)出来时(事件发生)。 Stream
内部机制(广播站)通知所有订阅者(StreamBuilder
):“新数据来了!”StreamBuilder
收到通知,获取最新的数据,并调用自身的builder
函数。builder
函数根据新数据重建它负责的那部分 UI。
- 你在 UI 中使用
- 本质:
StreamBuilder
是典型的观察者,它监听Stream
(被观察者)的数据变化并更新 UI。
- 被观察者 (Subject):
动手!用观察者模式升级我们的“购物车”
上次我们用单例模式创建了一个全局唯一的购物车管理器 (CartManager
)。现在,我们面临一个新问题:当购物车里的商品发生变化(添加、删除)时,如何让 App 里所有显示购物车信息的地方(商品列表页的徽章、购物车页面列表、底部结算栏)都自动刷新?
单例保证了“独一份儿”,但变化如何“广播”出去?—— 观察者模式登场!
在 Flutter 中,实现观察者模式最常用、最轻量级的内置工具是 ChangeNotifier
和 ValueNotifier
。它们本质上就是“被观察者 (Subject)”。
import 'package:flutter/foundation.dart'; // 引入 ChangeNotifier
// 升级版 CartManager,继承自 ChangeNotifier,让它成为一个"被观察者"
class CartManager with ChangeNotifier {
// 1. 私有静态实例变量 (单例部分保留)
static final CartManager _instance = CartManager._internal();
// 2. 私有构造函数
CartManager._internal() {
_items = [];
print('购物车经理(带广播功能)诞生了!');
}
// 3. 公共访问点 (单例部分保留)
static CartManager get instance => _instance;
// 4. 购物车商品 (私有)
List<String> _items = [];
// 5. 获取商品列表 (只读副本)
List<String> get items => List.unmodifiable(_items);
// 6. 添加商品 (关键升级!)
void addItem(String item) {
_items.add(item);
print('添加了: $item');
// 广播!通知所有观察者:“购物车有变化啦!”
notifyListeners(); // <----- 观察者模式的核心!
}
// 7. 清空购物车 (关键升级!)
void clearCart() {
_items.clear();
print('购物车已清空!');
// 广播!通知所有观察者:“购物车有变化啦!”
notifyListeners(); // <----- 观察者模式的核心!
}
}
升级点解析:
with ChangeNotifier
: 让CartManager
混入ChangeNotifier
。这个混入提供了被观察者的核心能力:管理观察者列表 (_listeners
) 和最重要的notifyListeners()
方法。notifyListeners()
: 这是广播站的核心功能!调用它,ChangeNotifier
就会自动遍历它内部记录的所有观察者 (listeners),并逐个调用它们的回调函数(相当于广播站挨个打电话通知员工)。我们在数据改变的地方(addItem
和clearCart
)调用它。
如何“观察”购物车的变化?使用 Consumer
或 ListenableBuilder
!
现在 CartManager
具备了广播能力,我们需要让那些关心购物车变化的 UI 部件(观察者)去订阅这个广播。
// 示例 1:在商品列表页,显示购物车商品总数的小徽章 (位于 AppBar 上)
AppBar(
title: Text('商品列表'),
actions: [
// 使用 Consumer 包裹需要响应变化的 Widget
Consumer<CartManager>(
builder: (context, cartManager, child) {
// cartManager 就是我们的单例 CartManager.instance
int itemCount = cartManager.items.length;
return Badge(
label: Text('$itemCount'),
child: Icon(Icons.shopping_cart),
);
},
),
],
)
// 示例 2:在购物车页面,显示购物车商品列表
class CartPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('我的购物车')),
body: // 使用 ListenableBuilder 同样可以
ListenableBuilder(
listenable: CartManager.instance, // 指定要监听谁 (被观察者)
builder: (context, child) {
// 当 CartManager 调用 notifyListeners() 时,这个 builder 会重新运行
return ListView.builder(
itemCount: CartManager.instance.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(CartManager.instance.items[index]),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => CartManager.instance.clearCart(),
child: Icon(Icons.delete),
),
);
}
}
Consumer
/ ListenableBuilder
做了什么?(观察者角色)
- 订阅 (Subscribe): 当这个 Widget 被构建时,它会自动向
CartManager.instance
(被观察者)注册自己(context
帮它找到了正确的实例),成为其观察者(listener)。相当于点单员向广播站登记:“有购物车变化请通知我这个徽章!”。 - 接收通知 (Notification): 当
CartManager
调用notifyListeners()
时,Consumer
/ListenableBuilder
注册的回调函数会被触发。 - 重建 UI (Rebuild): 它们的
builder
函数会被重新调用。在这个函数内部,它们读取CartManager
当前最新的数据(cartManager.items.length
或CartManager.instance.items
),并重建它们包裹的那部分 UI(徽章数字、列表项)。 - 高效局部刷新: 只有
Consumer
/ListenableBuilder
包裹的这部分 Widget 子树会重建,App 的其他部分不受影响!性能杠杠的!🚀
运行效果:
- 在商品列表页点击“加入购物车”按钮(调用
CartManager.instance.addItem('珍珠奶茶')
)。 CartManager
的addItem
方法内部调用notifyListeners()
。- 广播发出! 📢
- 商品列表页 AppBar 上的
Consumer<CartManager>
立刻收到通知,它的builder
重新运行,读取最新的items.length
(比如变成1),然后更新徽章显示为 “1”。 - 同时,如果你已经在购物车页面: 购物车页面里的
ListenableBuilder
也收到了同样的通知!它的builder
也重新运行,读取最新的items
列表(包含‘珍珠奶茶’),然后重建ListView
,显示出新加入的商品。 - 两个页面的 UI,同时、自动地更新了! 店长(
CartManager
)只喊了一声(notifyListeners
),所有关心购物车的员工(UI 部件)都同步行动了。
总结:观察者模式就是你的“事件广播网”
- 核心目标: 实现对象(通常是数据/状态)和依赖它的对象(通常是UI)之间的松耦合通信。数据源变,所有依赖它的UI自动变。
- 关键角色:
- Subject / Observable (被观察者): 状态持有者,负责通知变化(如
ChangeNotifier
,ValueNotifier
,Stream
)。在 Flutter 中常用ChangeNotifier
。 - Observer (观察者): 状态依赖者,订阅变化并响应(如
Consumer
,ListenableBuilder
,StreamBuilder
)。
- Subject / Observable (被观察者): 状态持有者,负责通知变化(如
- Flutter应用:
setState()
的底层通知机制。Stream
数据流与StreamBuilder
。- 状态管理库的核心!
Provider
(基于InheritedWidget
+ChangeNotifier
),Riverpod
,BLoC
(基于Stream
) 等流行状态管理库,其核心通信模式都是观察者模式的变体或增强。 - 任何需要一处数据变化,多处UI更新的场景。
- 优点: 解耦数据与UI;支持广播通知;实现响应式UI;是Flutter状态管理的基石。
- 注意: 记得在适当的时候取消订阅(例如在
StatefulWidget
的dispose
方法中,或用Consumer
等自动管理的 Widget),防止内存泄漏(想象员工离职了,广播站还给他打电话)。Consumer
和ListenableBuilder
通常会自动处理订阅的生命周期,比较安全。手动使用addListener
/removeListener
时需要小心。