selector: (context, provider) => provider.goodsList[index],
builder: (context, data, child) {
print((‘No.${index + 1} rebuild’));
},
);
},
);
ListView 这里就是使用了刚刚获取的 provider 中的 total 构建这个列表。然后列表中的每一个商品我们希望它根据自己的状态进行刷新,所以这时候就需要再次使用 Selector 来获取我们真正关心的那个 Good。
我们可以看到这里的 selector 返回了 provider.goodsList[index]
也就是具体的一个商品信息,所以每个商品只关注自己那部分信息,那么这个 Selector 的刷新范围就是 该商品。我们打印一下 rebuild 信息。
最后补上这个商品卡片的代码。
ListTile(
title: Text(data.goodsName),
trailing: GestureDetector(
onTap: () => provider.collect(index),
child: Icon(
data.isCollection ? Icons.star : Icons.star_border),
),
);
然后我们开始测试,点击收藏按钮,查看 rebuild 情况。
Performing hot reload…
Syncing files to device iPhone Xs Max…
…
flutter: No.8 rebuild
flutter: No.9 rebuild
flutter: No.10 rebuild
Reloaded 2 of 492 libraries in 446ms.
flutter: No.6 rebuild
flutter: No.1 rebuild
Cool! 现在只有我们当前收藏的这个 Selector 进行了 rebuild,避免了整个列表全部刷新。
你可以在这里查看该 Widget 的全部代码。
You also need to know
合理选择使用 Provides 的构造方法
在上面这个例子中👆,我们选择了使用 XProvider<T>.value
的构造方法来创建祖先节点中的 提供者。除了这种方式,我们还可以使用默认构造方法。
Provider({
Key key,
@required ValueBuilder builder,
Disposer dispose,
Widget child,
}) : this._(
key: key,
delegate: BuilderStateDelegate(builder, dispose: dispose),
updateShouldNotify: null,
child: child,
);
常规的 key/child 属性我们不在这里啰嗦。我们先来看这个看上去相对教复杂一点的 builder。
ValueBuilder
相比起 .value
构造方式中直接传入一个 value 就 ok,这里的 builder 要求我们传入一个 ValueBuilder。WTF?
typedef ValueBuilder<T> = T Function(BuildContext context);
其实很简单,就是传入一个 Function 返回一个数据而已。在上面这个例子中,你可以替换成这样。
Provider(
builder: (context) => textSize,
…
)
由于是 Builder 模式,这里默认需要传入 context,实际上我们的 Model(textSize)与 context 并没有关系,所以你完全可以这样写。
Provider(
builder: (_) => textSize,
…
)
Disposer
现在我们知道了 builder,那这个 dispose 方法又用来做什么的呢。实际上这才是 Provider 的点睛之笔。
typedef Disposer<T> = void Function(BuildContext context, T value);
dispose 属性需要一个 Disposer<T>
,而这个其实也是一个回调。
如果你之前使用过 BLoC 的话,相信你肯定遇到过一个头疼的问题。我应该在什么时候释放资源呢? BloC 使用了观察者模式,它旨在替代 StatefulWidget。然而大量的流使用完毕之后必须 close 掉,以释放资源。
然而 Stateless Widget 并没有给我们类似于 dispose 之类的方法,这便是 BLoC 的硬伤。你不得不为了释放资源而使用 StatefulWidget,这与我们的本意相违。而 Provider 则为我们解决了这一点。
当 Provider 所在节点被移除的时候,它就会启动 Disposer<T>
,然后我们便可以在这里释放资源。
举个例子,假如我们有这样一个 BLoC。
class ValidatorBLoC {
StreamController _validator = StreamController.broadcast();
get validator => _validator.stream;
validateAccount(String text) {
//Processing verification text …
}
dispose() {
_validator.close();
}
}
这时候我们想要在某个页面提供这个 BLoC 但是又不想使用 StatefulWidget。这时候我们可以在页面顶层套上这个 Provider。
Provider(
builder:() => ValidatorBLoC(),
dispose:(, ValidatorBLoC bloc) => bloc.dispose(),
}
)
这样就完美解决了数据释放的问题!🤩
现在我们可以放心的结合 BLoC 一起使用了,很赞有没有。但是现在你可能又有疑问了,在使用 Provider 的时候,我应该选择哪种构造方法呢。
我的推荐是,简单模型就选择 Provider<T>.value
,好处是可以精确控制刷新时机。而需要对资源进行释放处理等复杂模型的时候,Provider()
默认构造方式绝对是你的最佳选择。
其他几种 Provider 也遵循该模式,需要的时候可以自行查看源码。
我该使用哪种 Provider
如果你在 Provider 中提供了可监听对象(Listenable 或者 Stream)及其子类的话,那么你会得到下面这个异常警告。
你可以将本文中所使用到的 CounterModel 放入 Provider 进行提供(记得 hot restart 而不是 hot reload),那么你就能看到上面这个 Flutter