felangel/equatable

List of object dosen't work correctly

MajdSallora opened this issue Β· 12 comments

Bloc with equatable

I have List and change one property of in the list
then emiting in bloc state not update
if i removed equatable from state class it work

ex:

class Posts  extends Equatable{
final String id;
final String name;
final bool isLiked;
Posts ({
requierd this.id,
requierd this.name,
requierd this.isLiked,
});
  @override
  List<Object?> get props => [id,name,isLiked];
}

class PostState extends Equatable {
  final List<Posts> posts;

  const PostState({
    this.posts = const [],
  });

  PostState copyWith({List<Posts>? posts}) {
    PostState(posts: posts ?? this.posts,);
  }

  @override
  List<Object?> get props => [posts];
}

makePostLiked(MakePostLiked event, Emitter emit){
    var list = state.posts;
    var itemToEdit = state.posts[event.index];
    itemToEdit.isLike = true;
    list.removeAt(event.index);
    list.insert(event.index,itemToEdit);
    emit(
      state.copyWith(posts:list)
    );
  }

what i else tried

    emit(
      state.copyWith(posts:[...list])
    );

    emit(
      state.copyWith(posts: List.of(list))
    );

equatable: ^2.0.5

flutter_bloc: ^8.1.1

flutter_sdkVersion : 3.7.1

any solution?

I have the same issue when trying to emit new state after updating item in list

Hello, your Posts also need to extend Equatable

Hi @MajdSallora πŸ‘‹
Can you please provide a link to a minimal reproduction sample? As @yshean pointed out, a class which extends Equatable should also have all its properties also extend Equatable in order to equality comparisons to work properly.

Hi @MajdSallora πŸ‘‹ Can you please provide a link to a minimal reproduction sample? As @yshean pointed out, a class which extends Equatable should also have all its properties also extend Equatable in order to equality comparisons to work properly.

I make class Posts extands with equatable but same thing when emit in bloc
if do like this its work

    emit(state.copyWith(posts:[]) );
    emit(state.copyWith(posts:list) );

but it will build twice!

Hello, your Posts also need to extend Equatable

in example the class Posts extend with Equatable but same thing

class PostState extends Equatable {
  final List<Posts> posts;
  final int timestamp;

  const PostState({
    this.posts = const [],
    this.timestamp = 0,
  });

  PostState copyWith({List<Posts>? posts, int? timestamp}) {
    PostState(posts: posts ?? this.posts, timestamp: timestamp ?? 0);
  }

  @override
  List<Object?> get props => [posts, timestamp];
}

@MajdSallora The same bug occurred, so I added a timestamp property to handle the hash value changing.
@felangel I hope that bug will be fixed.

@felangel

wait for fix... have same problem

I'm not able to help without a minimal reproduction sample. Can anyone who is still experiencing this issue please provide a minimal reproduction sample that clearly illustrates the issue? Thanks!

So because you are working with a list of posts, it gets a little trickier. The way Bloc works is that it compares the old state with the new state to figure out whether it needs to build again. By default, dart checks for instances to see if 2 classes are equal, hence why it works if you remove Equatable. (You create a new instance when you use copyWith and then you emit)

List<Object?> get props => posts;

Now, another issue is that you're modifying the posts directly. You should never modify any field in the state, instead you should create a copy of the state and replace it. For example, if you want to add a new post do the following:

// working
  void addPost(Post post) {
    /// create a copy of the current list
    final list = state.posts.toList();

    /// modify it however you want
    list.add(post);

    /// add it to the new state
    emit(state.copyWith(posts: list));

    /// this works because you are basically replacing the old list with a
    /// completely new one, not modifying the old list
  }

  // not working
  void addPostTheWrongWay(Post post) {
    /// use the reference to the list
    final list = state.posts;

    /// modify it however you want
    list.add(post);

    /// add it to the new state
    emit(state.copyWith(posts: list));

    /// this doesn't work because you are modifying the old list.
    /// When Bloc then checks for changes, it will see that the old list and the
    /// "new" list (it's actually the old list) are equal
  }

The state file:


class PostState extends Equatable {
  final List<Post> posts;

  const PostState({
    this.posts = const [],
  });

  PostState copyWith({List<Post>? posts}) {
    return PostState(
      posts: posts ?? this.posts,
    );
  }

  @override
  /// posts is made up of elements that extend Equatable
  List<Object?> get props => [posts];
}

class Post extends Equatable {
  final String id;
  final String name;
  final bool isLiked;
  const Post({
    required this.id,
    required this.name,
    required this.isLiked,
  });
  @override
  List<Object?> get props => [id, name, isLiked];
}

If you want to like a post:

void likePost(int index) {
    final posts = state.posts.toList();

    /// this is when something like copyWith comes in handy
    posts[index] = Post(
      id: posts[index].id,
      name: posts[index].name,
      isLiked: true,
    );
    emit(state.copyWith(posts: posts));
  }

By the way, toList creates a copy of the list.

You can try this

@override
  bool operator ==(Object other) => identical(
        posts.hashCode,
        other.hashCode,
      );

I usually do this, when i need to update List in an instance of state.

var lastPostsState = state.posts;

emit(state.copyWith(posts : [...lastPostsState],);

Closing this since there aren’t any actionable steps I can take without a minimal reproduction sample.