flutter_architecture_samples icon indicating copy to clipboard operation
flutter_architecture_samples copied to clipboard

What would cause a Flutter Redux StoreConnector to not rebuild.

Open Joematpal opened this issue 5 years ago • 5 comments

I try to follow the issues religiously on the flutter_architecture_sample. Thank you, to all of you that contribute and ask questions. It helps a lot. I have not seen an issue like this and I could not find the right search query to bring up anything close to my problem. I would really appreciate some help if anyone has an idea as to why this StoreBuilder is not rebuilding. The underlying state of the reducer is two Map<String, Whatevers>'s. I tried to add code without overwhelming the post.

    #View
    return ListView.builder(
                itemCount: eventIDs.length,
                itemBuilder: (BuildContext context, int i) {
                  return StoreConnector<AppState, EventsPageViewModel>(
                      rebuildOnChange: true,
                      onDidChange: (EventsPageViewModel events) {print('${events}');},
                      onInit: (store) =>
                          store.dispatch(FetchEventAction(id: eventIDs[i])),
                      converter: (store) => EventsPageViewModel.fromStore(store),
                      builder: (BuildContext context, EventsPageViewModel events) {
                        print("build ListItem $i ${eventIDs[i]}");
                        if (events.events.containsKey('${eventIDs[i]}')) {
                          return EventCard(
                            event: events.events[eventIDs[i]],
                          );
                        }
                        return Container(
                          child: Card(
                            child: Container(
                              height: 120.0,
                            ),
                          ),
                        );
                      });
                },
              );
    #State
    @immutable
    class Events {
      Events({
        this.loadingStatus,
        this.events,
      });

      factory Events.initial() {
        return Events(
          loadingStatus: Map<String, LoadingStatus>(),
          events: Map<String, EventModel>(),
        );
      }

      final Map<String, LoadingStatus> loadingStatus;
      final Map<String, EventModel> events;

      Events copyWith({
        Map<String, LoadingStatus> loadingStatus,
        Map<String, EventModel> events,
      }) {
        return Events(
          loadingStatus: loadingStatus ?? this.loadingStatus,
          events: events ?? this.events,
        );
      }

      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is Events &&
              runtimeType == other.runtimeType &&
              loadingStatus == other.loadingStatus &&
              const MapEquality().equals(events, other.events);

     @override
     int get hashCode => loadingStatus.hashCode ^ const MapEquality().hash(events);
    }


    #ViewModel
    class EventsPageViewModel {
      EventsPageViewModel({
       @required this.status,
       @required this.events,
     });

     final Map<String, LoadingStatus> status;
     final Map<String, EventModel> events;

     static EventsPageViewModel fromStore(
       Store<AppState> store,
     ) {
       return EventsPageViewModel(
         status: store.state.events.loadingStatus,
         events: store.state.events.events,
       );
     }

     @override
     bool operator ==(Object other) =>
         identical(this, other) ||
         other is EventsPageViewModel &&
             runtimeType == other.runtimeType &&
             status == other.status &&
             const MapEquality().equals(events, other.events);

     @override
     int get hashCode => status.hashCode ^ const MapEquality().hash(events);
    }

I'm trying to get a view to rebuild after it fire off an async action and it return a value to a Map. I don't know if i'm doing the equality check properly

I checked to see if the middleware was firing off the GET Request. It is. I checked to see if the GET request was returning information. It is. I have another async action that is firing. That one returns a List of string and does trigger a rebuild on its StoreConnector.

Does anyone have any clues as to why the StoreConnector does not detect a diff?

Joematpal avatar Oct 19 '18 04:10 Joematpal

Hey there -- hrm, this is a funny one. I can't quite spot anything suspicious in the code that you've pasted, it looks pretty "standard" to me. Do you happen to have a full project you can share, or perhaps sharing some of the Middleware / Reducer code as well?

brianegan avatar Oct 19 '18 08:10 brianegan

https://github.com/Joematpal/trial-flutter-app

Thank you for looking at this.

Joematpal avatar Oct 19 '18 14:10 Joematpal

@Joematpal The link above is no longer working. Were you able to resolve this? If so please close this issue. Otherwise feel free to add to this issue.

mmcc007 avatar Feb 28 '19 07:02 mmcc007

@Joematpal I have the same issue as yours, Were you able to resolve the problem?

akdu12 avatar Mar 20 '19 07:03 akdu12

I could be wrong, but I believe this is because Maps (as well as Lists) need to return a new Map (or List) on update from the reducer. You cannot update the old map because the Redux package will not understand that it needs to rebuild. I assume this is due to the internals of the built in Map and List objects being unable to accurately create a hashCode/equals due to the generics, but I'm not 100% sure.

DFreds avatar May 13 '19 20:05 DFreds