如何使用来自inheritedWidget 的流处理导航?

Posted

技术标签:

【中文标题】如何使用来自inheritedWidget 的流处理导航?【英文标题】:How to handle navigation using stream from inheritedWidget? 【发布时间】:2019-10-25 04:58:40 【问题描述】:

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

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

我现在找到了How to call a method from InheritedWidget only once?,这对我的情况很有帮助,我可以通过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);
      );
    
  

【问题讨论】:

【参考方案1】:

就个人而言,我没有找到任何理由不收听initState 中的 BLoC 状态流。只要你记得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());
  


【讨论】:

以上是关于如何使用来自inheritedWidget 的流处理导航?的主要内容,如果未能解决你的问题,请参考以下文章

VST 音频插件如何检测来自 VST 主机的流中断?

Flutter:我应该如何使用 InheritedWidget?

kafka sql入门

在 initState 中获取 InheritedWidget 参数

Flutter核心类分析深入理解数据共享InheritedWidget

Flutter核心类分析深入理解数据共享InheritedWidget