Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested State does not update the UI #81

Closed
BerndWessels opened this issue Jun 14, 2018 · 2 comments
Closed

Nested State does not update the UI #81

BerndWessels opened this issue Jun 14, 2018 · 2 comments

Comments

@BerndWessels
Copy link

Hi

I have an AppState and a nested MenuState.
Actions on the AppState trigger the reducer and that then triggers the UI to update.
Actions on the nested MenuState trigger the nested reducer BUT do not trigger the UI to update.

Is this a bug or am I doing something wrong?

If there is nothing obvious in my following code, would you be so kind and provide an example with a nested action and reducer that actually triggers an update using the flutter_built_redux StoreConnection?

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel dev, v0.5.4, on Microsoft Windows [Version 10.0.17134.112], locale en-NZ)
[√] Android toolchain - develop for Android devices (Android SDK 26.0.2)
[√] Android Studio (version 3.1)
[√] IntelliJ IDEA Community Edition (version 2017.3)

store

Store<AppState, AppStateBuilder, AppActions> createStore() {
  return new Store<AppState, AppStateBuilder, AppActions>(
    appReducerBuilder.build(),
    new AppState.loading(),
    new AppActions(),
    middleware: [
      createFirebaseMiddleware(),
    ],
  );
}

middleware

final Firestore firestore = Firestore.instance;

Middleware<AppState, AppStateBuilder, AppActions> createFirebaseMiddleware() {
  return (new MiddlewareBuilder<AppState, AppStateBuilder, AppActions>()
        ..add(AppActionsNames.connectFirestoreAction, connectFirestore))
      .build();
}

connectFirestore(MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
    ActionHandler next, Action<Null> action) {
  firestore
      .collection("menus/P1N09rgDMAgc5u2KrIIT/items")
      .orderBy("sortOrder")
      .snapshots()
      .listen((QuerySnapshot snapshot) {
    BuiltList<MenuItem> menuItems =
        documentsToBuiltList<MenuItem>(snapshot.documents, MenuItem.serializer);
    api.actions.menu.menuItemsLoadedSuccessAction(menuItems);
  });
  next(action);
}

app_state

part 'app_state.g.dart';

abstract class AppState implements Built<AppState, AppStateBuilder> {
  static Serializer<AppState> get serializer => _$appStateSerializer;

  factory AppState([updates(AppStateBuilder b)]) = _$AppState;

  AppState._();

  BuiltList<Shop> get shops;

  @nullable
  MenuState get menuState;

menu_state

part 'menu_state.g.dart';

abstract class MenuState implements Built<MenuState, MenuStateBuilder> {
  static Serializer<MenuState> get serializer => _$menuStateSerializer;

  factory MenuState([updates(MenuStateBuilder b)]) = _$MenuState;

  MenuState._();

  @nullable
  String get id;

  BuiltList<MenuItem> get menuItems;

  @nullable
  String get title;
}

menu_item

part 'menu_item.g.dart';

abstract class MenuItem implements Built<MenuItem, MenuItemBuilder> {
  static Serializer<MenuItem> get serializer => _$menuItemSerializer;

  factory MenuItem([updates(MenuItemBuilder b)]) = _$MenuItem;

  MenuItem._();

  @nullable
  String get currency;

  @nullable
  String get description;

  String get id;

  @nullable
  double get price;

  int get sortOrder;

  String get title;

  int get type;
}

app_actions

part 'app_actions.g.dart';

abstract class AppActions extends ReduxActions {
  ActionDispatcher<Null> connectFirestoreAction;
  ActionDispatcher<BuiltList<Shop>> shopsLoadedSuccessAction;

  factory AppActions() => new _$AppActions();

  AppActions._();

  MenuActions get menu;
}

menu_actions

part 'menu_actions.g.dart';

abstract class MenuActions extends ReduxActions {
  ActionDispatcher<BuiltList<MenuItem>> menuItemsLoadedSuccessAction;

  factory MenuActions() => new _$MenuActions();

  MenuActions._();
}

app_reducer

final appReducerBuilder = new ReducerBuilder<AppState, AppStateBuilder>()
  ..add(AppActionsNames.shopsLoadedSuccessAction, shopsLoadedSuccess)
  ..combineNested<MenuState, MenuStateBuilder>(menuReducerBuilder);

menu_reducer

final menuReducerBuilder =
    new NestedReducerBuilder<AppState, AppStateBuilder, MenuState, MenuStateBuilder>(
        (s) => s.menuState,
        (b) => b.menuState) // maps from the main state object to the nested state
      ..add(MenuActionsNames.menuItemsLoadedSuccessAction, menuItemsLoadedSuccess);

void menuItemsLoadedSuccess(MenuState state, Action<BuiltList<MenuItem>> action,
    MenuStateBuilder builder) {
  builder.menuItems.replace(action.payload);
}

menu_widget

class MenuWidget extends StatefulWidget {
  @override
  MenuWidgetState createState() => MenuWidgetState();
}

class MenuWidgetState extends State<MenuWidget>
    with SingleTickerProviderStateMixin {

// ...

  @override
  Widget build(BuildContext context) {
    return StoreConnection<AppState, AppActions, BuiltList<MenuItem>>(
        connect: (state) => state.menuState.menuItems,
        builder: (BuildContext context, BuiltList<MenuItem> menuItems,
            AppActions actions) {
          print(menuItems); // not printed when nested action triggers nested reducer :(
          return Scaffold(

// ...

            floatingActionButton: FloatingActionButton(
              child: Icon(Icons.control_point),
              onPressed: () {
                actions.connectFirestoreAction();
              },
            ),

// ...
@davidmarne
Copy link
Contributor

Sorry for the late response, I'm sure you've given up or fixed it by now :P
But just in case you've been patiently waiting, I can try and help. Everything looks good to me tho, this sounds like a really weird issue. The only way i could see this happening is if

  1. The list of items returned from firebase contains the same items already in your store, in that case no update will take place.
  2. You do not have a StoreProvider as a parent in the widget tree

Seeing as you said updates from app trigger ui updates, i doubt 2 is true.
If 1 isn't true I would try just setting up a listener to the store and seeing if it triggers when your action is dispatched, just to make sure the store recognized the change.

@giorgiogross
Copy link

I encountered a similar problem: As my app grew more complex at some point reducer calls stopped updating the app state (I added a listener to store changes to see this). I investigated further and figured out that everything works as expected when I dispatch a simple action with no reducer or middleware attached to it during start up; yet this can't be the final solution, so I tried to rebuild the app architecture in a simple prototype and added more tests. Currently all my tests are passing and the simplified prototype works as well, so I'm not able to reproduce the problem. In the meantime, I'm encountering the issue despite my small workaround in my prod code again.

Can you please tell me under which circumstances it might happen that store updates by a reducer are not reflected into the app state? I guess I'm using the api wrong somewhere in my code and this information would be helpful to find the right spot :)

A bit context information: I'm using async middlewares and epics in a flutter app along with built_redux.
I think my problem is related to this issue, but let me know if I should open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants