Bloc: Updating collections in states makes the lib think it's the same state as previous

Created on 17 Aug 2019  路  7Comments  路  Source: felangel/bloc

I am updating some hash sets in the bloc, but the library thinks that the states are the same, even if I make a new instance of a set.

The EntryIdsCache model does not extend Equatable, and the states provide all the props to the super.

  Stream<FirestoreEntryIdsCacheState> _mapAddMovieIdToWatchToState(
      int movieId) async* {
    try {
      entryIdsCache.addMovieIdToWatch(movieId);

      final toWatchMovieIds = HashSet<int>.from(entryIdsCache.toWatchMovieIds);

      yield WatchlistMovieIdsAvailable(toWatchMovieIds,
          entryIdsCache.watchedMovieIds, entryIdsCache.likedMovieIds);
    } catch (e) {
      printError(e);
      yield FirestoreEntryIdsCacheError();
    }
  }

  Stream<FirestoreEntryIdsCacheState> _mapAddMovieIdToWatchedToState(
      int movieId) async* {
    try {
      entryIdsCache.addMovieIdToAlreadyWatched(movieId);

      final watchedMovieIds = HashSet<int>.from(entryIdsCache.watchedMovieIds);

      yield WatchlistMovieIdsAvailable(entryIdsCache.toWatchMovieIds,
          watchedMovieIds, entryIdsCache.likedMovieIds);
    } catch (e) {
      printError(e);
      yield FirestoreEntryIdsCacheError();
    }
  }

  Stream<FirestoreEntryIdsCacheState> _mapAddMovieIdToLikedToState(
      int movieId) async* {
    try {
      entryIdsCache.addMovieIdToLiked(movieId);

      final likedMovieIds = HashSet<int>.from(entryIdsCache.likedMovieIds);

      yield WatchlistMovieIdsAvailable(entryIdsCache.toWatchMovieIds,
          entryIdsCache.watchedMovieIds, likedMovieIds);
    } catch (e) {
      printError(e);
      yield FirestoreEntryIdsCacheError();
    }
  }

My current implementation makes the first of the triggered methods work just fine, but as soon as I trigger some other method it stops differentiating between states.

Instance of 'AddMovieIdToWatch'
Transition { currentState: [{373571}, {}, {}], event: Instance of 'AddMovieIdToWatch', nextState: [{373571}, {}, {}] }
Instance of 'AddMovieIdToWatch'
Transition { currentState: [{373571}, {}, {}], event: Instance of 'AddMovieIdToWatch', nextState: [{373571, 299534}, {}, {}] }
Instance of 'AddMovieIdToWatched'
Instance of 'RemoveMovieIdFromWatchlist'

Debugging shows that the current state is indeed the same as the next state.

image

question

Most helpful comment

It does work indeed. Thanks!

I guess I should've created copies for all 3 sets instead of only for the one I was mutating:

 final likedMovieIds = HashSet<int>.from(entryIdsCache.likedMovieIds);

      yield WatchlistMovieIdsAvailable(entryIdsCache.toWatchMovieIds,
          entryIdsCache.watchedMovieIds, likedMovieIds);

All 7 comments

Hi @mpivchev 馃憢
Thanks for opening an issue.

You should make sure you鈥檙e not mutating the state and can debug this issue by just checking if currentState == yieldedState before you yield. Right now that鈥檚 evaluating to true which is why the bloc is ignoring the transition. My guess one of your props is not a primitive type and is also not extending Equatable.

If you can provide a link to your repo I can also take a look 馃憤

@felangel
My props are HashSets

abstract class FirestoreEntryIdsCacheState extends Equatable {
  FirestoreEntryIdsCacheState([List props = const []]) : super(props);
}

class FirestoreEntryIdsCacheUninitialized extends FirestoreEntryIdsCacheState {}

class FirestoreEntryIdsCacheError extends FirestoreEntryIdsCacheState {}

class FirestoreEntryIdsCacheReady extends FirestoreEntryIdsCacheState {
  final HashSet<int> toWatchMovieIds;
  final HashSet<int> watchedMovieIds;
  final HashSet<int> likedMovieIds;

  FirestoreEntryIdsCacheReady(this.toWatchMovieIds, this.watchedMovieIds,
      this.likedMovieIds)
      : super([toWatchMovieIds, watchedMovieIds, likedMovieIds]);
}

class WatchlistMovieIdsAvailable extends FirestoreEntryIdsCacheState {
  final HashSet<int> toWatchMovieIds;
  final HashSet<int> watchedMovieIds;
  final HashSet<int> likedMovieIds;

  WatchlistMovieIdsAvailable(this.toWatchMovieIds, this.watchedMovieIds,
      this.likedMovieIds)
      : super([toWatchMovieIds, watchedMovieIds, likedMovieIds]);
}

class WatchlistUngroupedMovieIdsAvailable extends FirestoreEntryIdsCacheState {
  final HashSet<int> allMovies;

  WatchlistUngroupedMovieIdsAvailable(this.allMovies) : super([allMovies]);
}

@felangel
Converting the HashSets to Lists now yields the states properly. My guess is Equatable has some issues comparing HashSets?

Can you provide the link to a sample app which illustrates the issue?

@mpivchev I just commented on the gist with some tweaks that seem to resolve the issue. I think the main problem was you were mutating the HashSet and returning the mutated instance instead of creating a copy.

Hope that helps 馃憤

It does work indeed. Thanks!

I guess I should've created copies for all 3 sets instead of only for the one I was mutating:

 final likedMovieIds = HashSet<int>.from(entryIdsCache.likedMovieIds);

      yield WatchlistMovieIdsAvailable(entryIdsCache.toWatchMovieIds,
          entryIdsCache.watchedMovieIds, likedMovieIds);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

timtraversy picture timtraversy  路  3Comments

tigranhov picture tigranhov  路  3Comments

1AlexFix1 picture 1AlexFix1  路  3Comments

wheel1992 picture wheel1992  路  3Comments

abinvp picture abinvp  路  3Comments