如何在 Hot Reload 上使用 Provider 维护 Flutter Global BloC 状态?

Posted

技术标签:

【中文标题】如何在 Hot Reload 上使用 Provider 维护 Flutter Global BloC 状态?【英文标题】:How to maintain Flutter Global BloC state using Provider on Hot Reload? 【发布时间】:2019-09-07 02:41:47 【问题描述】:

每当我执行热重载时,我似乎都会丢失应用程序状态。

我正在使用 BloC 提供程序来存储应用程序状态。这在 main.dart 中的 App 级别传递并在子页面上使用。在视图的初始加载时,会显示该值。我可以在应用程序中导航并且状态仍然存在。但是,当我执行热重载时,我会丢失值和看似状态。

如何解决此问题,以便在热重载时保留状态?

区块提供者

abstract class BlocBase 
  void dispose();


class BlocProvider<T extends BlocBase> extends StatefulWidget 
  BlocProvider(
    Key key,
    @required this.child,
    @required this.bloc,
  ): super(key: key);

  final T bloc;
  final Widget child;

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

  static T of<T extends BlocBase>(BuildContext context)
    final type = _typeOf<BlocProvider<T>>();
    BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  

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


class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>
  @override
  void dispose()
    widget.bloc.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context)
    return widget.child;
  

class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return BlocProvider<ApplicationStateBloc>(
      bloc: ApplicationStateBloc(),
      child: MaterialApp(
        title: 'Handshake',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: LoadingPage(),
      )
    );
  

class ProfileSettings extends StatefulWidget 
  @override
  _ProfileSettingsState createState() => _ProfileSettingsState();


class _ProfileSettingsState extends State<ProfileSettings>
  ApplicationStateBloc _applicationStateBloc;

  @override
  void initState() 
    super.initState();
    _applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
  

  @override
  void dispose() 
    _applicationStateBloc?.dispose();
    super.dispose();
  

  Widget emailField() 
    return StreamBuilder<UserAccount>(
      stream: _applicationStateBloc.getUserAccount,
      builder: (context, snapshot)
        if (snapshot.hasData) 
          return Text(snapshot.data.displayName, style: TextStyle(color: Color(0xFF151515), fontSize: 16.0),);
        
        return Text('');
      ,
    );
  

  @override
  Widget build(BuildContext context) 

    return BlocProvider<ApplicationStateBloc>(
      bloc: _applicationStateBloc,
      child: Scaffold(
        backgroundColor: Colors.white,
        body: SafeArea(
          child: Column(
            children: <Widget>[
              emailField(),
              .... // rest of code
class ApplicationStateBloc extends BlocBase 

  var userAccountController = BehaviorSubject<UserAccount>();
  Function(UserAccount) get updateUserAccount => userAccountController.sink.add;
  Stream<UserAccount> get getUserAccount => userAccountController.stream;

  @override
  dispose() 
    userAccountController.close();
  


【问题讨论】:

我认为你应该将_applicationStateBloc = BlocProvider.of&lt;ApplicationStateBloc&gt;(context); 移动到didChangeDependencies 而不是initState 方法 感谢您的建议,没有成功。 @override void didChangeDependencies() super.didChangeDependencies(); _applicationStateBloc = BlocProvider.of(context); 【参考方案1】:

我也遇到了同样的问题。继承的小部件使处理 bloc 的资源变得困难。 另一方面,有状态小部件允许处理,但在您使用的实现中,它不会将块保持在状态中,从而导致小部件重建时状态丢失。

经过一些试验,我想出了一种结合两者的方法:

class BlocHolder<T extends BlocBase> extends StatefulWidget 
  final Widget child;
  final T Function() createBloc;

  BlocHolder(
    @required this.child,
    @required this.createBloc
  );

  @override
  _BlocHolderState createState() => _BlocHolderState();


class _BlocHolderState<T extends BlocBase> extends State<BlocHolder> 
  T _bloc;

  Function hello;

  @override
  void initState() 
    super.initState();
    _bloc = widget.createBloc();
  

  @override
  Widget build(BuildContext context) 
    return BlocProvider(
      child: widget.child,
      bloc: _bloc,
    );
  

  @override
  void dispose() 
    _bloc.dispose();
    super.dispose();
  

Bloc 持有者在 createState() 中创建 bloc 并将其持久化。它还在 dispose() 中处理 bloc 的资源。

class BlocProvider<T extends BlocBase> extends InheritedWidget 
  final T bloc;

  const BlocProvider(
    Key key,
    @required Widget child,
    @required T bloc,
  )
      : assert(child != null),
        bloc = bloc,
        super(key: key, child: child);

  static T of<T extends BlocBase>(BuildContext context) 
    final provider = context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider;
    return provider.bloc;
  

  @override
  bool updateShouldNotify(BlocProvider old) => false;

BlocProvider,顾名思义,只负责将块提供给嵌套的小部件。

所有的块都扩展了 BlocBase 类

abstract class BlocBase 
  void dispose();

这是一个用法示例:

class RouteHome extends MaterialPageRoute<ScreenHome> 
 RouteHome(List<ModelCategory> categories, int position): super(builder: 
    (BuildContext ctx) => BlocHolder(
        createBloc: () => BlocMain(ApiMain()),
        child: ScreenHome(),
      ));
    

【讨论】:

感谢您的建议,亚历克斯!很详细!我会尝试重现这个。【参考方案2】:

您正在丢失状态,因为您的 bloc 正在 _ProfileSettingsStateinitState() 中检索,因此,即使您热重载它也不会改变,因为该方法只调用一次 em> 构建小部件时。

在返回 BlocProvider 之前将其移至 build() 方法

@override
Widget build(BuildContext context) 
  _applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);

  return BlocProvider<ApplicationStateBloc>(
    bloc: _applicationStateBloc,
    child: Scaffold(
      backgroundColor: Colors.white,
     ....

didUpdateWidget 方法,该方法在小部件状态重建时调用。

请记住,如果您在集团中使用非广播流,如果您尝试收听已被收听的流,则可能会遇到异常。

【讨论】:

感谢您的建议,我已经尝试了您的建议,但似乎仍然不起作用。该块使用 BehaviorSubject。我已经完成了以下操作 -> @override void didChangeDependencies() super.didChangeDependencies(); _applicationStateBloc = BlocProvider.of(context); BehaviorSubject 的问题是它不会触发任何事件,除非value 引用本身发生变化。例如,如果您有一个BehaviorSubject&lt;List&lt;Something&gt;&gt;,并且如果您正在处理的列表更改了一个值,您可以添加另一个引用作为值(例如List.from(oldList))。确保这不是您的问题,并且您可能会误导其他内容。 rxDart API 表示订阅者将自动获取添加到流中的最后一个值“一个特殊的 StreamController,它捕获已添加到控制器的最新项目,并将其作为第一个项目发送给任何新的侦听器。此主题允许向侦听器发送数据、错误和完成事件。已添加到主题的最新项目将发送给该​​主题的任何新侦听器。之后,任何新事件将适当地发送给侦听器. 如果没有向主题添加任何项目,则可以提供一个种子值。" 没错。我正是这个意思。您给行为主体的引用充当种子,但如果引用未更改,则不会触发任何事件,因此更改可能不会更新。

以上是关于如何在 Hot Reload 上使用 Provider 维护 Flutter Global BloC 状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 .NET 6.0 Hot Reload 在最简单的“Hello, World”控制台应用程序上工作?

Webpack:如何设置 webpack-dev-server 和 Hot Reload

Android 的 Instant Run vs Flutter Hot Reload 和 React Native Hot Reload 的区别?

Webpack / react hot reload重新加载整个页面?

Hot Reload 在 react-native android 中不起作用

微软认真聆听了开源 .NET 开发社区的炮轰: 通过CLI 支持 Hot Reload 功能