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
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 馃憤
Most helpful comment
it worked, Thanks for the answer and the explaining.