Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to maintain Flutter Global BloC state using Provider on Hot Reload?

Tags:

flutter

dart

bloc

I seem to lose application state whenever I perform a hot reload.

I am using a BloC provider to store application state. This is passed at the App level in the main.dart and consumed on a child page. On the initial load of the view, the value is shown. I can navigate around the application and the state persists. However, when I perform a hot reload, I lose the values and seemingly the state.

How can I fix this issue so that state is preserved on Hot Reload?

Bloc Provider

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

}
like image 283
KennyP Avatar asked Nov 17 '22 11:11

KennyP


1 Answers

I was facing the same problem. Inherited widgets make it hard disposing bloc's resources. Stateful widget, on the other hand, allows disposing, but in the implementation you're using it doesn't persist the bloc in the state causing state loss on widgets rebuild.

After some experimenting I came up with an approach that combines the two:

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 holder creates bloc in createState() and persists it. It also disposes bloc's resources in 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, as the name suggests, is only responsible for providing the bloc to nested widgets.

All the blocs extend BlocBase class

abstract class BlocBase {
  void dispose();
}

Here's a usage example:

class RouteHome extends MaterialPageRoute<ScreenHome> {
 RouteHome({List<ModelCategory> categories, int position}): super(builder: 
    (BuildContext ctx) => BlocHolder(
        createBloc: () => BlocMain(ApiMain()),
        child: ScreenHome(),
      ));
    }
like image 187
AlexKost Avatar answered Dec 21 '22 18:12

AlexKost