如何使用InheritedWidget的流处理导航?

时间:2019-06-10 19:21:54

标签: flutter dart

我正在使用继承的窗口小部件来访问具有某些长期运行任务(例如搜索)的Bloc。 我想在第1页上触发搜索,并在完成后继续下一页。因此,我正在侦听流,等待结果发生,然后导航到结果页面。 现在,由于使用继承的窗口小部件来访问Bloc,因此在context.inheritFromWidgetOfExactType()期间我无法使用initState()访问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),
      ),
    );
  }
}

一个简单的Bloc伪造了一些长期运行的工作

///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 个答案:

答案 0 :(得分:0)

我个人并没有发现不听initState中BLoC状态流的任何原因。只要您记得在canceldispose订阅

如果您的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());
  }
}