marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
230 stars 41 forks source link

State lost after hot reload - regression? #101

Closed jonasbark closed 3 years ago

jonasbark commented 3 years ago

Hi there,

Today I upgraded from 3.0.5 to 9.0.0 - so far a seamless experience until I tried hot reload. It seems that the state is lost and calls to e.g. onInit are no longer happening.

I could narrow it down to this simple example:

import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class AppStore {
  final int hello;

  AppStore(this.hello);
}

class IncreaseAction extends ReduxAction<AppStore> {
  @override
  AppStore reduce() {
    return AppStore(state.hello + 1);
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: StoreProvider(
        store: Store(initialState: AppStore(0)),
        child: Scaffold(
          appBar: AppBar(
            title: Text('Async'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                AsyncTest()
              ],
            ),
          ),
          floatingActionButton: Builder(
            builder: (c) => FloatingActionButton(
              onPressed: () {
                StoreProvider.dispatch(c, IncreaseAction());
              },
              tooltip: 'Increment',
              child: Icon(Icons.add),
            ),
          ),
        ),
      ),
    );
  }
}

class AsyncTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppStore, int>(
      converter: (store) => store.state.hello,
      onInit: (store) {
        print('HELLO');
      },
      builder: (_, state) => Text(state.toString()),
    );
  }
}

Expected:

I double checked - it works as expected in 3.0.5.

Edit: Following observation:

Which leaves me thinking that the onInit 'issue' is expected behavior, yet I don't understand why the state is lost.

jonasbark commented 3 years ago

Thanks to @SunlightBro I understood it's due to the fact that the store is recreated as it's not a static field (like in the official samples). It still leaves me thinking why it worked with e.g. 3.0.5.

In my app, I'd like to work with independent stores that do not share data. The store could even be dynamically created e.g. when opening a detail page so I wouldn't want to store a reference to it in a field.

For now I'm storing it in a field and resetting in in the initState of the widget that contains the StoreProvider. So the question really is: How do I handle multiple and even dynamically created stores?

marcglasberg commented 3 years ago

You can't create the store inside of the build method, otherwise it will create a new store each time the build method is called. Flutter calls build methods all the time. You shouldn't use it to create state, no matter the state management solution you use. It's even worse if it's in a StatelessWidget.

I guess you can save the store into a field of the State of a StatefulWidget.

I have no idea why it worked in such an old version.

Also, I suggest you call the state AppState, not AppStore.