0

因为这个,我晚上睡不着觉

我正在使用我的类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...);
      },
    );
  }
}
4

0 回答 0