改造思路
- 首先要理解作者推出
Bind的思路,这次5.0.0升级废弃了Bindings
@Deprecated('Use Binding instead')
abstract class Bindings extends BindingsInterface<void> {
@override
void dependencies();
}
然后推出Binding
abstract class Binding extends BindingsInterface<List<Bind>> {}
区别在dependencies() 返回值,现在为List<Bind>, 我们知道这个dependencies()方法作用是给GetPage注入依赖,在推入GetPage入栈时BuildWidget调用
...
final localbindings = [
if (bindings != null) ...bindings!,
if (binding != null) ...[binding!]
];
final bindingsToBind = middlewareRunner.runOnBindingsStart(localbindings);
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
_child = middlewareRunner.runOnPageBuilt(pageToBuild());
return _child!;
...
final localbinds = [if (binds != null) ...binds!];
final bindingsToBind = middlewareRunner
.runOnBindingsStart(bindings.isNotEmpty ? bindings : localbinds);
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
if (bindingsToBind != null && bindingsToBind.isNotEmpty) {
if (bindingsToBind is List<BindingsInterface>) {
for (final item in bindingsToBind) {
final dep = item.dependencies();
if (dep is List<Bind>) {
_child = Binds(
child: middlewareRunner.runOnPageBuilt(pageToBuild()),
binds: dep,
);
}
}
} else if (bindingsToBind is List<Bind>) {
_child = Binds(
child: middlewareRunner.runOnPageBuilt(pageToBuild()),
binds: bindingsToBind,
);
}
}
return _child ??= middlewareRunner.runOnPageBuilt(pageToBuild());
那么这里出现的Binds做了什么处理呢
class Binds extends StatelessWidget {
final List<Bind<dynamic>> binds;
final Widget child;
Binds({
Key? key,
required this.binds,
required this.child,
})
: assert(binds.isNotEmpty),
super(key: key);
@override
Widget build(BuildContext context) =>
binds.reversed.fold(child, (widget, e) => e._copyWithChild(widget));
}
接下来看Bind又是啥,_copyWithChild(widget)又做了什么处理
abstract class Bind<T> extends StatelessWidget {
...
@factory
Bind<T> _copyWithChild(Widget child);
factory Bind.builder({
...
}) =>
_FactoryBind<T>(
...
);
}
class _FactoryBind<T> extends Bind<T> {
@override
Bind<T> _copyWithChild(Widget child) {
return Bind<T>.builder(
...
);
}
@override
Widget build(BuildContext context) {
return Binder<T>(
...
child: child!,
);
}
}
重点来了,前面都的处理都是提供接口以及迭代包括,那么处理逻辑都存在Binder中
class Binder<T> extends InheritedWidget {
const Binder({
...
}) : super(key: key, child: child);
...
@override
bool updateShouldNotify(Binder<T> oldWidget) {
return oldWidget.id != id ||
oldWidget.global != global ||
oldWidget.autoRemove != autoRemove ||
oldWidget.assignId != assignId;
}
@override
InheritedElement createElement() => BindElement<T>(this);
}
class BindElement<T> extends InheritedElement {
BindElement(Binder<T> widget) : super(widget) {
initState();
}
...
void initState() {
widget.initState?.call(this);
var isRegistered = Get.isRegistered<T>(tag: widget.tag);
if (widget.global) {
if (isRegistered) {
if (Get.isPrepared<T>(tag: widget.tag)) {
_isCreator = true;
} else {
_isCreator = false;
}
_controllerBuilder = () => Get.find<T>(tag: widget.tag);
} else {
_controllerBuilder =
() => (widget.create?.call(this) ?? widget.init?.call());
_isCreator = true;
if (widget.lazy) {
Get.lazyPut<T>(_controllerBuilder!, tag: widget.tag);
} else {
Get.put<T>(_controllerBuilder!(), tag: widget.tag);
}
}
} else {
_controllerBuilder =
(widget.create != null ? () => widget.create!.call(this) : null) ??
widget.init;
_isCreator = true;
_needStart = true;
}
}
...
void dispose() {
widget.dispose?.call(this);
if (_isCreator! || widget.assignId) {
if (widget.autoRemove && Get.isRegistered<T>(tag: widget.tag)) {
Get.delete<T>(tag: widget.tag);
}
}
...
}
...
@override
void unmount() {
dispose();
super.unmount();
}
}
通过翻阅源码,我大胆猜测作者使用Bind目的是拓展GetPage.bindings的功能,将组件put依赖的能力封装到Bind中,并拓展其自动管理生命周期的能力,4.x.x版本组件的依赖跟组件本身并不是强关联,Bingings里面不能很好的控制其生命周期,生命周期的控制是通过RouterReportManager加上Get.find()维护一个Map<routeName,List>,通过监听GetRoute路由的动作来控制depedency的销毁
开始改造
为啥还需要改造呢?复杂业务情况下,我们希望组件依赖的生命周期不能跟随组件的生命周期,而是类似与之前文章的根据路由的操作而定
而在复杂场景下logic 会绑错路由的情况这个问题依旧存在,不同的是,现在可以在put() or lazyPut() 拿到当前context,继而拿到当前路由,但是我们知道,RouterReportManager绑定depedencyKey是在Get.find(),所以4.x.x的改造思路是在find时加上context绑定到正确的路由上,那么现在的做法需要怎么做呢,首先我们假定RouterReportManager中currentRoute不可信,问题见. 可信的routeName从哪里获取,Bind .put() or .lazyPut() 时一定是对的,所以需要在RouterReportManager中维护一个Map<BindRoute,epedencyKey>来维系对应路由
class RouterReportManager<T> {
...
final Map<String, T> _depeKey2BindRoute = {};
...
T? _bindRoute(String depedencyKey) => _depeKey2BindRoute[depedencyKey]??_current;
void reportDepeKey2BindRoute(String depedencyKey, T? routeName) {
if (!_depeKey2BindRoute.containsKey(depedencyKey)) {
if (routeName != null) {
_depeKey2BindRoute.addAll({depedencyKey: routeName});
}
}
}
void reportDependencyLinkedToRoute(String depedencyKey) {
final _bind = _bindRoute(depedencyKey);
if (_bind == null) return;
if (_routesKey.containsKey(_bind ?? _current)) {
_routesKey[_bind]!.add(depedencyKey);
} else {
_routesKey[_bind] = <String>[depedencyKey];
}
}
}
还剩一个问题,怎么调用reportDepeKey2BindRoute()上报
static Bind lazyPut<S>(InstanceBuilderCallback<S> builder, {
String? tag,
bool fenix = true,
VoidCallback? onClose,
bool autoRemove = true,
}) {
Get.lazyPut<S>(builder, tag: tag, fenix: fenix);
return _FactoryBind<S>(
tag: tag,
autoRemove: autoRemove,
initState: (_) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
RouterReportManager.instance
.reportDepeKey2BindRoute(Get.getKey(S, tag), ModalRoute.of(_));
});
},
dispose: (_) {
onClose?.call();
},
);
}
到此为,改造完成,业务使用,例:
class LoginBinding extends Binding {
@override
List<Bind> dependencies() {
return [
Bind.lazyPut(() => LoginController()),
];
}
}
GetView不需要丑陋的继承context,也能在find时找到正确的context2bind