Как поддерживать состояние глобального блока Flutter с помощью поставщика при горячей перезагрузке?

#dart #flutter #bloc

#dart #трепетание #блок

Вопрос:

Кажется, я теряю состояние приложения всякий раз, когда выполняю горячую перезагрузку.

Я использую поставщика блоков для хранения состояния приложения. Это передается на уровне приложения в main.dart и используется на дочерней странице. При начальной загрузке представления отображается значение. Я могу перемещаться по приложению, и состояние сохраняется. Однако, когда я выполняю горячую перезагрузку, я теряю значения и, по-видимому, состояние.

Как я могу исправить эту проблему, чтобы состояние сохранялось при горячей перезагрузке?

Поставщик блоков

 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();
  }

}
 

Комментарии:

1. Я думаю, вам следует перейти _applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context); к didChangeDependencies initState методу вместо

2. Спасибо за ваше предложение, оно не сработало. @переопределение аннулирует didChangeDependencies() { super.didChangeDependencies(); _applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(контекст); }

Ответ №1:

Я столкнулся с той же проблемой. Унаследованные виджеты затрудняют использование ресурсов блока. Виджет с отслеживанием состояния, с другой стороны, позволяет удалять, но в используемой вами реализации он не сохраняет блок в состоянии, вызывая потерю состояния при перестроении виджетов.

После некоторых экспериментов я придумал подход, который сочетает в себе эти два:

 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();
  }
}
 

Держатель блока создает блок в createState() и сохраняет его. Он также распоряжается ресурсами блока в dispose().

 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, как следует из названия, отвечает только за предоставление блока вложенным виджетам.

Все блоки расширяют класс BlockBase

 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(),
      ));
    }
 

Комментарии:

1. Спасибо вам за ваше предложение, Алекс! Довольно подробно! Я попытаюсь воспроизвести это.

Ответ №2:

Вы теряете состояние, потому что ваш блок извлекается в _ProfileSettingsState ‘s initState() таким образом, оно не изменится даже при горячей перезагрузке, потому что этот метод вызывается только один раз при создании виджета.

Либо переместите его в build() метод, непосредственно перед возвратом BlocProvider

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

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

или к didUpdateWidget методу, который вызывается всякий раз, когда состояние виджета восстанавливается.

Имейте в виду, что если вы используете не широковещательный поток в своем блоке, вы можете получить исключение, если попытаетесь прослушать поток, который уже прослушивается.

Комментарии:

1. Спасибо за ваши предложения, я попробовал ваше предложение, и, похоже, оно все еще не работает. Блок использует BehaviorSubject. Я сделал следующее -> @override аннулирует didChangeDependencies() { super.didChangeDependencies(); _applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(контекст); }

2. Проблема в BehaviorSubject том, что он не будет запускать какое-либо событие, если сама value ссылка не изменится. Если у вас есть, например, a BehaviorSubject<List<Something>> и если список, над которым вы работаете, изменяет значение, вы добавляете в качестве значения другую ссылку (например. List.from(oldList) ). Убедитесь, что это не ваша проблема и что вы можете вводить в заблуждение чем-то другим.

3. RxDart API указывает, что подписчики автоматически получат последнее значение, добавленное в поток «Специальный StreamController, который фиксирует последний элемент, добавленный в контроллер, и передает его в качестве первого элемента любому новому слушателю. Эта тема позволяет отправлять данные, ошибки и выполненные события слушателю. Последний элемент, который был добавлен в тему, будет отправлен всем новым слушателям темы. После этого любые новые события будут соответствующим образом отправлены слушателям. Можно указать начальное значение, которое будет выдано, если в тему не было добавлено никаких элементов «.

4. Вот именно. Вот о чем я говорю. Ссылка, которую вы даете субъекту поведения, действует как начальное значение, но если ссылка не изменилась, событие не будет запущено, поэтому изменения могут не обновляться.