I’m using GroupedListView
(which is based on ListView.builder
) to implement a chat.
I have a ChatBloc
bloc that is responsible to handle new messages from the DataLayer. Whenever new messages are added, I’m emitting a new state of elements, and rebuild the whole ListView.
Here is my code:
child: BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) {
final userId = context.read<AppBloc>().state.user.userId;
return GroupedListView<Message, DateTime>(
controller: scrollController,
elements: state.messages,
reverse: true,
cacheExtent: 9999,
order: GroupedListOrder.DESC,
itemComparator: (element1, element2) =>
element1.timestamp.compareTo(element2.timestamp),
itemBuilder: (context, element) =>
element.messageToWidget(userId, state.senders),
groupBy: (element) => DateTime(
element.timestamp.year,
element.timestamp.month,
element.timestamp.day,
),
groupHeaderBuilder: (element) => Center(
child: TitleMessage(
text: dateFormat03.format(element.timestamp),
),
),
);
},
)
The problem is that I don’t want to rebuild everything again with every new message that is added to the list.
I want to build new messages only.
How do I do that using Bloc state management?
Update
According to @Harsimran Singh
ListView.builder
does not recycle its elements on refresh (even due adding a unique Key to each element).
Using ListView.custom
with SliverChildBuilderDelegate
seems not to solve the issue:
Here is my code:
return ListView.custom(
physics: const AlwaysScrollableScrollPhysics(),
childrenDelegate: SliverChildBuilderDelegate(
(context, index) {
final message = state.messages.elementAt(index);
return message.messageToWidget(
userId: userId,
senders: state.senders,
key: Key(message.toString()),
);
},
childCount: state.messages.length,
findChildIndexCallback: (key) {
return state.messages
.indexWhere((e) => Key(e.toString()) == key);
},
),
);
I debugged the code and the findChildIndexCallback
works just fine and returns the correct index. But according to the above video, you can see that the elements are still rendered.
Why is that?
4
Answers
Don’t wrap your ListView with a bloc builder in that case, use BlocListener
You can listen to your blocStream in initState and use animated list instead of your current list so you can insert items in real-time
It can be done by defining a global key like this:
and then insert the value like:
and if you want to remove an item with animations you can:
I think you can define and use chat object like this "context.read().state.user.userId;" instead when using from state. It will update only the widget not the build method.
Actually the problem is not related to bloc at all. In fact
ListView.builder
will always rebuild all of its children, independentlyto setting a key for them.
The solution I found is to use a
ListView.custom
with aSliverChildBuilderDelegate
. Then assign a key to its children andretrieving the key from the index works as expected and the child is not
rebuilt.
Note that
childCount
andfindChildIndexCallback
are parametersof the sliver and not the list itself.
Here is an example: