首页 > 解决方案 > 如何使用来自inheritedWidget 的流处理导航?

问题描述

我正在使用继承的小部件来访问具有一些长期运行任务(例如搜索)的 Bloc。我想在第 1 页触发搜索,并在完成后继续到下一页。因此,我正在收听流并等待结果发生,然后导航到结果页面。现在,由于使用继承的小部件来访问 Bloc,我无法使用context.inheritFromWidgetOfExactType()duringinitState()和异常访问 Bloc,因此建议在didChangeDependencies().

这样做会导致一些奇怪的行为,因为我来回走动的次数越多,我访问的流就越频繁地触发,这会导致第二页被多次推送。这会随着每次来回交互而增加。我不明白为什么会发生这种情况。欢迎在这里提出任何见解。作为一种解决方法,我保留一个局部变量_onSecondPage来保存状态以避免多次推送到第二页。

我现在发现如何只从 InheritedWidget 调用方法一次?这对我的情况有帮助,我可以通过访问继承的小部件context.ancestorInheritedElementForWidgetOfExactType(),只需收听流并直接从initState(). 然后流的行为如我所料,但问题是,这是否有任何其他副作用,所以我宁愿通过监听流来让它工作didChangeDependencides()

代码示例

我的 FirstPage 小部件在didChangeDependencies()流中监听。工作,但我想我错过了一些东西。_onSecondPage我从第一页导航到第二页的频率越高,如果不保留局部变量,第二页将被多次推送到导航堆栈上。

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint("counter: $_counter -Did change dependencies called");
    // This works the first time, after that going back and forth to the second screen is opened several times
    BlocProvider.of(context).bloc.finished.stream.listen((bool isFinished) {
       _handleRouting(isFinished);
    });
  }

  void _handleRouting(bool isFinished) async {
    if (isFinished && !_onSecondPage) {
      _onSecondPage = true;
      debugPrint("counter: $_counter -   finished: $isFinished : ${DateTime.now().toIso8601String()} => NAVIGATE TO OTHER PAGE");
      await Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => SecondRoute()),
      );
      _onSecondPage = false;
    } else {
      debugPrint("counter: $_counter -    finished: $isFinished : ${DateTime.now().toIso8601String()} => not finished, nothing to do now");
    }
  }

  @override
  void dispose() {
    debugPrint("counter: $_counter - disposing my homepage State");
    subscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder(
              stream: BlocProvider.of(context).bloc.counter.stream,
              initialData: 0,
              builder: (context, snapshot) {
                _counter = snapshot.data;
                return Text(
                  "${snapshot.data}",
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

一个简单的集团伪造一些长期运行的工作

///Long Work Bloc
class LongWorkBloc {
  final BehaviorSubject<bool> startLongWork = BehaviorSubject<bool>();
  final BehaviorSubject<bool> finished = BehaviorSubject<bool>();

  int _counter = 0;
  final BehaviorSubject<int> counter = BehaviorSubject<int>();


  LongWorkBloc() {
    startLongWork.stream.listen((bool start) {
      if (start) {
        debugPrint("Start long running work");
        Future.delayed(Duration(seconds: 1), () => {}).then((Map<dynamic, dynamic> reslut) {
          _counter++;
          counter.sink.add(_counter);
          finished.sink.add(true);
          finished.sink.add(false);
        });
      }
    });
  }

  dispose() {
    startLongWork?.close();
    finished?.close();
    counter?.close();
  }
}

更好的工作代码

但是,如果我删除代码以从中访问继承的小部件didChangeDependencies()并收听其中的流,initState()它似乎工作正常。

在这里,我掌握了保持流的继承小部件context.ancestorInheritedElementForWidgetOfExactType()

这样做可以吗?或者在这种情况下,颤振的最佳实践是什么?

  @override
  void initState() {
    super.initState();
    //this works, but I don't know if this is good practice or has any side effects?
    BlocProvider p = context.ancestorInheritedElementForWidgetOfExactType(BlocProvider)?.widget;
    if (p != null) {
      p.bloc.finished.stream.listen((bool isFinished) {
        _handleRouting(isFinished);
      });
    }
  }

标签: flutterdart

解决方案


就个人而言,我没有找到任何理由不听 BLoC 中的状态流initState。只要你记得cancel订阅dispose

如果您BlocProvider正在正确使用,InheritedWidget那么将您的价值放入initState.

像这样

  void initState() {
    super.initState();
    _counterBloc = BlocProvider.of(context);
    _subscription = _counterBloc.stateStream.listen((state) {
      if (state.total > 20) {
        Navigator.push(context,
            MaterialPageRoute(builder: (BuildContext context) {
          return TestPush();
        }));
      }
    });
  }

这是一个很好的 BlocProvider 示例,它应该在任何情况下都可以工作

import 'package:flutter/widgets.dart';

import 'bloc_base.dart';

class BlocProvider<T extends BlocBase> extends StatefulWidget {
  final T bloc;
  final Widget child;

  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }) : super(key: key);

  @override
  _BlocProviderState<T> createState() => _BlocProviderState<T>();

  static T of<T extends BlocBase>(BuildContext context) {
    final type = _typeOf<_BlocProviderInherited<T>>();
    _BlocProviderInherited<T> provider =
        context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
    return provider?.bloc;
  }

  static Type _typeOf<T>() => T;
}

class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<BlocBase>> {
  @override
  Widget build(BuildContext context) {
    return _BlocProviderInherited<T>(
      bloc: widget.bloc,
      child: widget.child,
    );
  }

  @override
  void dispose() {
    widget.bloc?.dispose();
    super.dispose();
  }
}

class _BlocProviderInherited<T> extends InheritedWidget {
  final T bloc;

  _BlocProviderInherited({
    Key key,
    @required Widget child,
    @required this.bloc,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;
}

...最后是 BLoC

import 'dart:async';

import 'bloc_base.dart';

abstract class CounterEventBase {
  final int amount;
  CounterEventBase({this.amount = 1});
}

class CounterIncrementEvent extends CounterEventBase {
  CounterIncrementEvent({amount = 1}) : super(amount: amount);
}

class CounterDecrementEvent extends CounterEventBase {
  CounterDecrementEvent({amount = 1}) : super(amount: amount);
}

class CounterState {
  final int total;
  CounterState(this.total);
}

class CounterBloc extends BlocBase {
  CounterState _state = CounterState(0);

  // Input Streams/Sinks
  final _eventInController = StreamController<CounterEventBase>();
  Sink<CounterEventBase> get events => _eventInController;
  Stream<CounterEventBase> get _eventStream => _eventInController.stream;

  // Output Streams/Sinks
  final _stateOutController = StreamController<CounterState>.broadcast();
  Sink<CounterState> get _states => _stateOutController;
  Stream<CounterState> get stateStream => _stateOutController.stream;

  // Subscriptions
  final List<StreamSubscription> _subscriptions = [];

  CounterBloc() {
    _subscriptions.add(_eventStream.listen(_handleEvent));
  }

  _handleEvent(CounterEventBase event) async {
    if (event is CounterIncrementEvent) {
      _state = (CounterState(_state.total + event.amount));
    } else if (event is CounterDecrementEvent) {
      _state = (CounterState(_state.total - event.amount));
    }
    _states.add(_state);
  }

  @override
  void dispose() {
    _eventInController.close();
    _stateOutController.close();
    _subscriptions.forEach((StreamSubscription sub) => sub.cancel());
  }
}


推荐阅读