I have any issue stack with customScrollView

40 Views Asked by At

I'm encountering an issue with CustomScrollView when incorporating asynchronous items into it. I want the old items to remain at the top without any animated movement when new items are added asynchronously at the bottom. Please note that I've set the reverse property of CustomScrollView to true because I'm implementing a chat interface. I meaning like telegram chat

Here is my example:

Widget _buildChatBody(BuildContext context, ChatRoomState state,
  String avatarUrl, String nameUser) {
return Expanded(
  child: Stack(
    children: [
      NotificationListener<ScrollNotification>(
        onNotification: (notification) {
          view.savedScrollPosition = notification.metrics.pixels;
          if (notification.metrics.pixels > 300) {
            state.showFloatingButtonListenable.value = true;
          } else {
            if (mounted) {
              state.unreadMessageCount = 0;
            }
            state.chatMeMessageExposer.emitSeenMessage();
            state.showFloatingButtonListenable.value = false;
            state.notify();
          }
          return true;
        },
        child: CustomScrollView(
          key: PageStorageKey(widget.chatroom.roomIdParams),
          cacheExtent: 20,
          physics: const BouncingScrollPhysics(),
          controller: view.chatScrollController,
          reverse: true,
          shrinkWrap: true,
          slivers: [
            const SizedBox(
              height: 20,
            ).sliver,
            SliverPadding(
              padding: const EdgeInsets.only(left: 16.0, bottom: 8.0),
              sliver: state.isTyping
                  ? SliverToBoxAdapter(
                      child: SizedBox(
                        height: 50,
                        child: Row(
                          key: view.isTypingWidgetKey,
                          children: [
                            ClipRRect(
                              borderRadius: BorderRadius.circular(42.0),
                              child: Container(
                                width: 42.0,
                                height: 42.0,
                                decoration: BoxDecoration(
                                  color: Colors.transparent,
                                  borderRadius: BorderRadius.circular(42.0),
                                ),
                                child: ImageBuilder(
                                  fit: BoxFit.cover,
                                  errorBuilder: (_, __, ___) {
                                    return ImageBuilder()
                                        .asset(AppStrings.noProfile);
                                  },
                                ).network(
                                  avatarUrl,
                                ),
                              ),
                            ),
                            const SizedBox(width: 8),
                            Container(
                              alignment: Alignment.centerLeft,
                              width: 50.0,
                              child: const MpayLottie(
                                lottieAsset: AppStrings.typing,
                                width: 50,
                                repeat: true,
                              ),
                            ),
                          ],
                        ),
                      ),
                    )
                  : const SliverToBoxAdapter(),
            ),
            _buildMessageList(state, avatarUrl, nameUser),
            SizedBox(key: view.scrollTopKey, height: 15).sliver,
            if (state.chatMeMessageExposer.hasMorePage)
              AnimatedContainer(
                duration: const Duration(milliseconds: 400),
                alignment: Alignment.center,
                width: appState.screenSize.width,
                child: const CupertinoActivityIndicator(),
              ).sliver,
            const SizedBox(height: 10).sliver,
          ],
        ),
      ),
      Positioned(
        bottom: 10.0,
        right: 10,
        child: ValueListenableBuilder(
          valueListenable: state.showFloatingButtonListenable,
          builder: (context, showFloatingButton, child) {
            if (!showFloatingButton) {
              return nothing;
            }
            return child!;
          },
          child: Builder(builder: (context) {
            final countUnread = state.getUnreadMessageCount(ref);
            return Stack(
              alignment: Alignment.topRight,
              children: [
                FloatingActionButton(
                  backgroundColor: AppColors.white,
                  onPressed: () {
                    scrollToBottom();
                    state.notify();
                  },
                  child: const Icon(
                    Icons.keyboard_arrow_down_outlined,
                    size: 32.0,
                    color: AppColors.black,
                  ),
                ),
                if (countUnread > 0)
                  Container(
                    alignment: Alignment.center,
                    width: 22.0,
                    height: 22.0,
                    decoration: BoxDecoration(
                      color: Colors.red,
                      borderRadius: BorderRadius.circular(22.0),
                    ),
                    child: Text(
                      countUnread.toString(),
                      style: const TextStyle(color: AppColors.white),
                    ),
                  )
              ],
            );
          }),
        ),
      ),
    ],
  ),
);

}

1

There are 1 best solutions below

3
Shawon On

To achieve the desired behavior of keeping old items fixed at the top without any animated movement when new items are added asynchronously at the bottom of a CustomScrollView with the reverse property set to true, you can use the SliverList widget together with a ValueKey for each item in the list. This ensures that Flutter identifies each item individually and does not animate them when additional things are added.

Here's how to tweak the _buildMessageList method to do this:

 Widget _buildMessageList(ChatRoomState state, String avatarUrl, String nameUser) {
  final List<Message> messages = state.messages; // Assuming you have a list of messages in your state

  return SliverList(
    delegate: SliverChildBuilderDelegate(
      (BuildContext context, int index) {
        final Message message = messages[index];
        // Use a ValueKey to uniquely identify each message
        return _buildMessageItem(message, avatarUrl, nameUser, ValueKey(message.id));
      },
      childCount: messages.length,
    ),
  );
}

Widget _buildMessageItem(Message message, String avatarUrl, String nameUser, ValueKey<String> key) {
  // Your logic to build a single message item
  // You can use the provided message object, avatarUrl, nameUser, and key
}

With this configuration, Flutter will not animate current items when new ones are added to the CustomScrollView's bottom. Instead, fresh things will display at the bottom. Make sure that each message in your list has a distinct identity (e.g., id) that you can use as the ValueKey for each item.