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

Routing completed after action completion #144

Closed pitazzo closed 1 year ago

pitazzo commented 1 year ago

Hi there, I came up with a situation where a routing action dispatched in another action's reducer is being completed after the original action end, despite the routing action is marked to be awaited. I would like to know whether that behavior is normal, and if not, whether it is caused by some bad practices I could have committed.

My AppState class includes a nullable field:

class AppState {
  String? title;
  ...
}

Also, we've a connector which depends on that field. That connector should only be routed while title is not null. The connector is used in a. widget routed by a named route. Let's focus on its factory:

...
class _Factory extends ...{
  @override
  _ViewModel fromStore() {
   print('A');
    return _ViewModel.build(title: state.title!, otherFields: ...);
  }
}
...

Finally, we have two actions:

class FirstAction extends ... {
  @override
  Future<AppState> reduce() async {
    return state.copy(title: Nullable<string>('Hello'));
  }

  @override
  void after() {
    dispatch(NavigateAction.pushNamedAndRemoveAll('/route-which-uses-the-connector'))
  }
}
class SecondAction extends ... {
  @override
  Future<AppState> reduce() async {
    print('B');
    await dispatch(NavigateAction.pushNamedAndRemoveAll('/route-which-NOT-uses-the-connector'))
    print('C');
    return state.copy(title: Nullable<string>(null));
  }
}

With this setup, we get an exception in the _Factory builder because of title being null, which does not make sense to us, as it it is set as null after the routing, and therefore at that moment the connector should not be longer rendered. Moreover, I added some nasty print() calls and the result is as follows: BCA. Also, I tried adding a wait of 100 milliseconds after the routing dispatch, and no error happened, which makes me think about some race condition.

marcglasberg commented 1 year ago

Hi @Pitazzo, I don't think Flutter routes work as synchronously as you think.

You tell Flutter to do some navigation, but it's not going to do it immediately, it can take some time. Flutter has some 60 frames per second, in the best case scenario.

Since state.title is nullable, it's best that you don't ever depend on it not being null. There are 2 easy fixes for you:

class _Factory extends ...{
  @override
  _ViewModel fromStore() {
    return _ViewModel.build(title: state.title? "-", otherFields: ...);
  }
}

Or you can return a null Vm:

class _Factory extends ...{
  @override
  _ViewModel fromStore() {
    String title = state.title;
    if (title == null) return null;
    else  return _ViewModel.build(title: title, otherFields: ...);
  }
}

And then the StoreConnector detects the Vm is null and returns SizedBox() or an appropriate shimmed or placeholder: