因为这个,我晚上睡不着觉
我正在使用我的类DataObjectListOptions<T>
,其中包含许多帮助程序,以便我收听来自 firestore 的数据并对数据应用过滤和任何自定义转换
我BehaviorSubject<T> objectsData
过去常常使用StreamBuilder
所以我的问题是:我应该创建一个 dispose 方法DataObjectListOptions<T>
来关闭BehaviorSubject 还是在取消所有 s 的订阅objectsData
后自动发生这种情况?StreamBuilder
数据对象列表选项:
abstract class BaseListOptions<L, U> {
BaseListOptions({
this.onLongPress,
this.tap,
this.empty,
this.showNull = false,
bool selectionMode = false,
Stream<L>? itemsStream,
L? items,
Map<String, U>? selected,
}) : assert(itemsStream != null || items != null),
assert(showNull == false || (showNull == true && empty != null)),
_selectionMode = BehaviorSubject<bool>.seeded(selectionMode),
_selected = BehaviorSubject<Map<String, U>>.seeded(selected ?? {}),
_objectsData = itemsStream != null
? (BehaviorSubject<L>()..addStream(itemsStream))
: BehaviorSubject<L>.seeded(items!);
final BehaviorSubject<L> _objectsData;
BehaviorSubject<L> get objectsData => _objectsData;
L? get items => _objectsData.value;
final BehaviorSubject<bool> _selectionMode;
BehaviorSubject<bool> get selectionMode => _selectionMode;
bool? get selectionModeLatest => _selectionMode.value;
final BehaviorSubject<Map<String, U>?> _selected;
BehaviorSubject<Map<String, U>?> get selected => _selected;
Map<String, U>? get selectedLatest => _selected.value;
final void Function(U)? tap;
final void Function(U)? onLongPress;
final U? empty;
final bool showNull;
void selectAll();
void selectNone() {
if (!_selectionMode.requireValue) _selectionMode.add(true);
_selected.add({});
}
void toggleSelected(U item);
void select(U item);
void deselect(U item);
}
class DataObjectListOptions<T extends DataObject>
implements BaseListOptions<List<T>, T> {
DataObjectListOptions({
Widget Function(T, void Function(T)? onLongPress, void Function(T)? onTap,
Widget? trailing, Widget? subtitle)?
itemBuilder,
this.onLongPress,
this.tap,
this.empty,
this.showNull = false,
bool selectionMode = false,
Stream<List<T>>? itemsStream,
List<T>? items,
Map<String, T>? selected,
required Stream<String> searchQuery,
List<T> Function(List<T>, String)? filter,
}) : assert(itemsStream != null || items != null),
assert(showNull == false || (showNull == true && empty != null)),
_filter = (filter ??
((o, f) =>
o.where((e) => filterString(e.name).contains(f)).toList())),
_searchQuery = BehaviorSubject<String>()..addStream(searchQuery),
_selected = BehaviorSubject<Map<String, T>>.seeded(selected ?? {}),
_selectionMode = BehaviorSubject<bool>.seeded(selectionMode),
originalObjectsData = itemsStream != null
? (BehaviorSubject<Map<String, T>>()
..addStream(itemsStream.map((l) => {for (final o in l) o.id: o})))
: BehaviorSubject<Map<String, T>>.seeded(
{for (final o in items!) o.id: o}),
itemBuilder = (itemBuilder ??
(i, void Function(T)? onLongPress, void Function(T)? onTap,
Widget? trailing, Widget? subtitle) =>
DataObjectWidget<T>(i,
subtitle: subtitle,
onLongPress:
onLongPress != null ? () => onLongPress(i) : null,
onTap: onTap != null ? () => onTap(i) : null,
trailing: trailing)) {
buildItem = (i, {onLongPress, onTap, trailing, subtitle}) {
return this.itemBuilder(i, onLongPress, onTap, trailing, subtitle);
};
_objectsData = (showNull
? BehaviorSubject<List<T>>.seeded([empty!])
: BehaviorSubject<List<T>>())
..addStream(Rx.combineLatest2<String, Map<String, T>, List<T>>(
_searchQuery,
originalObjectsData,
(search, items) => search.isNotEmpty
? _filter(items.values.toList(), search)
: items.values.toList()));
}
@override
late BehaviorSubject<List<T>> _objectsData;
@override
BehaviorSubject<List<T>> get objectsData => _objectsData;
@override
List<T>? get items => objectsData.value;
final BehaviorSubject<Map<String, T>> originalObjectsData;
@override
final BehaviorSubject<bool> _selectionMode;
@override
BehaviorSubject<bool> get selectionMode => _selectionMode;
@override
bool? get selectionModeLatest => _selectionMode.value;
@override
final BehaviorSubject<Map<String, T>> _selected;
@override
BehaviorSubject<Map<String, T>> get selected => _selected;
@override
Map<String, T>? get selectedLatest => _selected.value;
final BehaviorSubject<String> _searchQuery;
BehaviorSubject<String> get searchQuery => _searchQuery;
String? get searchQueryLatest => _searchQuery.value;
final List<T> Function(List<T>, String) _filter;
@override
final void Function(T)? tap;
@override
final void Function(T)? onLongPress;
@override
final T? empty;
@override
final bool showNull;
final Widget Function(T, void Function(T)? onLongPress,
void Function(T)? onTap, Widget? trailing, Widget? subtitle) itemBuilder;
late final Widget Function(T,
{void Function(T)? onLongPress,
void Function(T)? onTap,
Widget? trailing,
Widget? subtitle}) buildItem;
@override
void selectAll() {
if (!_selectionMode.requireValue) _selectionMode.add(true);
_selected.add({for (var item in _objectsData.requireValue) item.id: item});
}
@override
void selectNone() {
if (!_selectionMode.requireValue) _selectionMode.add(true);
_selected.add({});
}
@override
void toggleSelected(T item) {
if (_selected.requireValue.containsKey(item.id)) {
deselect(item);
} else {
select(item);
}
}
@override
void select(T item) {
assert(!_selected.requireValue.containsKey(item.id));
_selected.add({..._selected.requireValue, item.id: item});
}
@override
void deselect(T item) {
assert(_selected.requireValue.containsKey(item.id));
_selected.add(_selected.requireValue..remove(item.id));
}
}
String filterString(String s) => s.toLowerCase();
UI 中的示例用法:
class DataObjectList<T extends DataObject> extends StatefulWidget {
const DataObjectList({Key? key, required this.options}) : super(key: key);
final DataObjectListOptions<T> options;
@override
_ListState<T> createState() => _ListState<T>();
}
class _ListState<T extends DataObject> extends State<DataObjectList<T>>{
@override
Widget build(BuildContext context) {
return StreamBuilder<List<T>>(
stream: widget.options.objectsData,
builder: (context, stream) {
if (stream.hasError) return Center(child: ErrorWidget(stream.error!));
if (!stream.hasData)
return const Center(child: CircularProgressIndicator());
final List<T> _data = stream.data!;
if (_data.isEmpty) return const Center(child: Text('No data to show'));
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 6),
cacheExtent: 200,
itemCount: _data.length,
itemBuilder: (context, i) //item builder...);
},
);
}
}