Flutter- 在GetX仿fish-redux ListAdapter实现items懒加载
Flutter- 在GetX仿fish-redux ListAdapter实现items懒加载
前景
公司前端组在21年中旬经过fish-redux烂尾行为决定放弃使用,转为投入GetX的怀抱.GetX的便利这里就不在赘述,下面分享在实际项目中遇到的一些问题及解决办法:
起源
-
在业务开发中常常遇到一些业务复杂的列表,而Item子项牵扯到许多通用组件,而往往也需要知道Item子项的生命周期,比如在我们的业务开发中,Item项会在自己创建的时候去获取一些数据,并跟其他Item隔离,在fish-redux中,我们可以使用adapter,而在GetX里还没有类似组件可以直接用.
-
Adapter 具体理论见文档,简单来说adapter也是一个component,component产物是一个Widget,adapter产物是ListAdapter可生成一组Widget.
-
在转使用GetX中非常不适应没有ListAdapter的情况,想在Item抽离成组件,又想使用GetXController去控制状态,Item的GetController也注册到全局依赖管理,结果就是List跟Item数据没有保持一致,违背了单一数据源. 在fish-redux里,使用connector连接器使得父数据与子数据保持一致.那么只需要一个connector打通数据,再加上Getview实际本身就是StatelessWidget即可以在结构上实现仿ListAdapter
结构
-
设计思路就是列表里没有实际数据,只存了管理的tag,实际数据由子item组件保存,列表通过tag操作,获取实际数据.这样下来,列表就只负责列表项的修改以及渲染,子Item管理自己部分数据修改以及渲染
-
item项依赖使用lazyPut注入,那么只有在界面需要渲染到或者逻辑使用到子项时才会通过
Get.find(tag:itemTag);
实际调用_initDependencies
见源码加载到依赖管理里 -
实际上我们需要实现的是一个ListConnector而非ListAdapter,不管是List还是Items依然保持原结构,只是将数据转换成标签加以关联
具体实现
-
要实现的功能已经很明确:
- 一个Tag List.操作它同时可能伴随子项dependency操作等,比如新增,删除,列表绑定,当然还得是RxList,列表本身的变化也得通知界面刷新
- 一个ItemBindings,主要是item注册自己的dependencies,与GetX的[Bindings].
dependencies()
(github.com/jonataslaw/…)不同的地方在于itemDependencies(T itemState,String tag)
tag
: item在列表中的标记,列表想操作也是通过这个tag,所有,在这个方法put的dependency一定要加上这个tag,不然列表会找不到itemState
: 子item的数据- 一个mixin 去组装实现TagList和 ItemBindings,业务listGetController继承,注入
itemState2Tag(T itemState)
转换方法和一些必要参数
-
RTList
: 待通知机制Tag List/// Create a list similar to `List<T>` class RTList<E> extends ListMixin<E> with NotifyManager<List<String?>> implements RxInterface<List<String?>> { RTList({required this.item2LogicTag, required this.itemDependencies, required this.tag2ItemState}) { _tagValues = []; } ///通过标签获取实体 final Tag2ItemFunc<E> tag2ItemState; /// 通过实体获取标签 final Item2LogicTagFunc<E> item2LogicTag; ///列表标签 late final List<String?> _tagValues; /// 通过标签注册方法 late final ItemDependencies<E> itemDependencies; ... }
因为是操作的标签,所以需要注入一些必要的转换以及初始方法,下面就是实际使用:
@override void add(E element) { //拿到itemState之后转换为tag final tag = item2LogicTag(element); //判断是否是重复添加 if (!_tagValues.contains(tag)) { //不是则注册依赖(注意,这里如果itemDependencies是lazyPut的话,实际的itemLogic还没有创建,等待find) itemDependencies(element, tag); } //将tag添加到数组 _tagValues.add(tag); //变化通知监听 subject.add(_tagValues); }
上面描述的是当数组变化tag的操作,那要如何绑定通知呢
@override E operator [](int index) { //GetX挂载监听 RxInterface.proxy?.addListener(subject); //用tag查询实际的itemState返回 return tag2ItemState(_tagValues[index]!); }
其他修改,查询方法同理;
-
ItemBindings
: item注册本身和自己使用的组件的依赖///listItem 依赖bind abstract class ListItemBindings<T> { ///item注册本身和自己使用的组件的依赖 void itemDependencies(T item, String tag); }
-
RxListMixin
: 组装混入结构,这个只是简单封装,还有很大优化空间
///列表自加载itemLogic mixin
///[T] list item state
///[P] list item bindings
///[K] list item logic
mixin RxListMixin<T, P extends ListItemBindings<T>, K extends GetStateController> on DisposableInterface implements RxConnector<T> {
late final RTList<T> itemList;
bool _removeItemLogicsWhenClose = false;
/// 是否在onClose时删除itemLogic
/// 是onClose不一定是退路由触发,itemLogic没有remove
/// 退路由一定触发onClose
set removeItemLogicsWhenClose(bool removeItemLogicsWhenClose) => _removeItemLogicsWhenClose = removeItemLogicsWhenClose;
ListItemBindings<T>? _itemLazyBindings;
set itemBindings(ListItemBindings<T> itemBindings) => _itemLazyBindings = itemBindings;
Route? route;
@override
void onInit() {
super.onInit();
...
itemList = RTList(item2LogicTag: item2LogicTag, itemDependencies: _itemLazyBindings!.itemDependencies, tag2ItemState: tag2ItemState);
}
@override
void onClose() {
super.onClose();
if (_removeItemLogicsWhenClose) {
itemList.forEach((element) {
final tag = itemList.item2LogicTag(element);
Get.delete<K>(tag: tag, force: true);
});
}
}
///通过索引获取标签
String? getItemTagByIndex(int index) => itemList._tagValues[index];
}
///连接器
///[T] list item 泛型
///[P] list item logic 泛型
abstract class RxConnector<T> {
String item2LogicTag(T t);
}
使用
list目录结构:
主体为list/和item/,需要关注的:
list/logic.dart
class TestListLogic extends GetxController with RxListMixin<TestListItemState, TestListItemBinding, TestListItemLogic> {
TestListLogic() : super() {
super.itemBindings = TestListItemBinding();
}
...
@override
String item2LogicTag(TestListItemState t) {
//todo set item unionKey
throw Exception('None set itemKey');
return t.hashCode.toString();
}
}
list/view.dart
class TestListView extends GetView<TestListLogic> {
const TestListView({BuildContext? context, Key? key})
: super(
context: context,
key: key,
);
@override
Widget build(BuildContext context) {
return Obx(() => ListView.builder(
itemCount: controller.itemList.length,
itemBuilder: (context, index) => TestListItemView(
context: context,
tag: controller.getItemTagByIndex(index),
),
));
}
}
list/xxx_item/binding.dart
class TestListItemBinding extends ListItemBindings<TestListItemState> {
@override
void itemDependencies(TestListItemState item, String tag) {
//todo 懒加载的child logic
Get.lazyPut<TestListItemLogic>(() =>TestListItemLogic(item), tag: tag);
}
}
实际效果: demo演示,图片较大
因为封装不是很好,需要注意的地方还很多,多了就容易犯错,还配套一个模板生成工具,减少负担.
从效果上来看,item为懒加载模式,用到才开始走生命周期,且当列表销毁,item依赖同样销毁
后记
本次只针对List的数据类型的实现,Map同理
这次的改造引入了更多的复杂性,当然也只是我们习惯性的将复杂业务拆分成多个独立组件引起的,当然有很多别的办法也能达到目的,还更简单.这个只是一种想法,抛砖引玉吧
招聘
惠群2022前端招新啦,前端组19年开始使用flutter,并一直坚持flutter打造公司通用技术栈的道路,经过几年的沉淀,我们在app端,桌面端,web端都有项目产出,并有着自己的规范及插件库,我们欢迎志同道合且喜欢flutter的同学加入.投递邮箱:wlm@wiqun.com
转载自:https://juejin.cn/post/7065118867424542750