草庐IT

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

coder 2023-07-22 原文

我正在使用继承的 Widget 来访问具有一些长时间运行任务(例如搜索)的 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),
      ),
    );
  }
}

一个简单的 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);
      });
    }
  }

最佳答案

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

关于flutter - 如何使用来自 inheritedWidget 的流处理导航?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56532375/

有关flutter - 如何使用来自 inheritedWidget 的流处理导航?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐