Bloc: No effects to the UI While Keyboard is active

Created on 25 Apr 2020  路  8Comments  路  Source: felangel/bloc

Describe the bug
I'm working on a comment section for a blog app! and I made an event for adding a comment, But It's not working as it should be, the current behavior is while the keyboard is active the new comment not showing up, But If I closed the keyboard after I made the comment the comment appears on the list

To Reproduce
The Event:

abstract class CommentsEvent extends Equatable {
  const CommentsEvent();

  @override
  List<Object> get props => null;
}

class FetchComments extends CommentsEvent {
  final int articleId;

  FetchComments(this.articleId);
}

class AddComment extends CommentsEvent {
  final String content;

  AddComment(this.content);

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

The Bloc:

class CommentsBloc extends Bloc<CommentsEvent, CommentsState> {
  final CommentsRepository commentsRepository;

  CommentsBloc(this.commentsRepository);

  @override
  CommentsState get initialState => CommentsInitial();

  @override
  Stream<CommentsState> mapEventToState(
    CommentsEvent event,
  ) async* {
    final currentState = state;

    if (event is FetchComments) {
      try {
        yield CommentsLoading();
        final comments =
            await commentsRepository.getCommentsByArticle(event.articleId);
        yield CommentsLoaded(comments);
      } catch (e) {
        yield CommentsFailure(e.toString());
      }
    }

    if ( event is AddComment ) {
      if ( currentState is CommentsLoaded ) {
        final comments = currentState.comments;
        comments.add(Comment(
          content: event.content,
          createdAt: DateTime.now().toString(),
          user: User(
            id: 1,
            image: null,
            username: 'hesham',
            role: 'admin'
          )
        ));

        yield CommentsLoaded(comments);
      }
    }
  }
}

The UI Form

return Material(
  child: Directionality(
      textDirection: TextDirection.rtl,
      child: TextField(
        decoration: InputDecoration(
            hintText: '丕賰鬲亘 鬲毓賱賷賯...',
            prefixIcon: IconButton(
                icon: FaIcon(FontAwesomeIcons.paperPlane),
                onPressed: () {
                  BlocProvider.of<CommentsBloc>(context)
                      .add(AddComment('This is a comment'));
                }),
            suffixIcon: FaIcon(FontAwesomeIcons.solidComment),
            contentPadding: EdgeInsets.all(10),
            border: OutlineInputBorder(
                borderSide: BorderSide(color: Colors.white))),
      )));

Expected behavior
The comments list should be updated while the keyboard is active or whatever on the event been triggered

question

Most helpful comment

yield CommentsLoaded([...currentState.comments, Comment(...)]);

it worked, Thanks for the answer and the explaining.

All 8 comments

Hi @heshaShawky 馃憢
Thanks for opening an issue!

It looks like the problem is your bloc is mutating (modifying the existing list of comments) rather than creating a new instance.

If you update

final comments = currentState.comments;

to

final comments = List.from(currentState.comments);

I think you鈥檒l find the problem will be resolved.

Let me know if that helps 馃憤

The reason why it updates when you close the keyboard is because flutter will trigger a rebuild of your widget when the keyboard is opened/closed automatically.

I have updated the code as you said and pops an error

type 'List<dynamic>' is not a subtype of type 'List<Comment>'

and I fixed the error by putting type to list

final comments = List<Comment>.from(currentState.comments);

The error gone but now the list never changes whatever the keyboard on/off,

I have logged the comments I noticed that after your modifications that it's adding just the first comment to the list then logging the same results.

Before It's adding the data to the list ( been printed on the console ) but the UI not rebuilding after the event been triggered.

Can you please share the link to the github repo? I鈥檓 happy to take a look and open a PR with fixes.

Here a sample ( repo ) I made with the exact problem I have.

Click on the button add a comment The UI not going to rebuild too.

But the new comment has been added to list if you took a look on the console as I'm printing the comments list on the console every time the event been triggered ( By pressing the button )

To see the comments I have added I have to do a hot reload to make the UI rebuild

yield CommentsLoaded([...currentState.comments, Comment(...)]);

I've opened a PR with the fix: https://github.com/heshaShawky/Adding-new-item-to-the-list-issue-repository/pull/1
As a general rule please stay away from mutating existing state and create new objects.

yield CommentsLoaded([...currentState.comments, Comment(...)]);

it worked, Thanks for the answer and the explaining.

yield CommentsLoaded([...currentState.comments, Comment(...)]);

I've opened a PR with the fix: heshaShawky/Adding-new-item-to-the-list-issue-repository#1
As a general rule please stay away from mutating existing state and create new objects.

I'm facing again the same problem, how do I change the data inside the state without this issue happen again.

In my situation now I'm trying to map/edit the list of posts if I like one of them update the array, I know that map makes a new array, but I can't think of another way to do it.

So what is best practice in this?

if (event is LikePost) {
  if ( currentState is PostsLoaded ) {


    yield currentState.copyWith(posts: [...currentState.posts.map((post) {
      if (post.id == event.articleId) {
        post.isLiked = true;
        print(post)
      }

      return post;
    }).toList()]);
  }
} 

@heshaShawky you can do something like:

yield currentState.copyWith(
  posts: currentState.posts.map(
    (post) => post.id == event.articleId ? post.copyWith(liked: true) : post,
  ).toList(),
);

You can also write an extension like:

extension IterableExtension<T> on Iterable<T> {
  Iterable<T> replaceWhere(
      bool Function(T element) predicate, T Function(T value) replace) {
    return this.map(
      (element) => predicate(element) ? replace(element) : element,
    );
  }
}

which would allow you to rewrite the above code like:

yield currentState.copyWith(
  posts: currentState.posts.replaceWhere(
    (post) => post.id == event.articleId,
    (post) => post.copyWith(liked: true),
  ),
)

Hope that helps 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rsnider19 picture rsnider19  路  3Comments

ricktotec picture ricktotec  路  3Comments

1AlexFix1 picture 1AlexFix1  路  3Comments

shawnchan2014 picture shawnchan2014  路  3Comments

komapeb picture komapeb  路  3Comments