robert-luoqing / flutter_list_view

MIT License
47 stars 17 forks source link

I have a problem with chat view, help please #14

Closed endison1986 closed 1 year ago

endison1986 commented 2 years ago

first, very thanks for this project, I try to implement chat view, and load message from the top down, but I failure, some problems always occur with me. I use your project and implement chat functional basically. but when I create a new chat view, and send message one by one, the chat view always has a brief flicker and the animation is not very smooth, I don't known how to config can avoid this problem, help me please. GIF 2022-8-19 17-04-41

robert-luoqing commented 2 years ago

@endison1986 I tested example/lib/chat2.dart in iOS environment. It is very smooth. Could you please send to code fragment to me to identify the problem and tell me which platform you tested?

endison1986 commented 2 years ago

@robert-luoqing thanks for reply and I apologize for not carrying more information I test in Android environment,MIUI 10.3.2 basic Android 9, but I don't have an Apple developer account, so I never tested in iOS environment. image

MessageDetailView.dart

const MessageDetailView({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: GetBuilder<MessageDetailState>(
        init: MessageDetailState(context),
        builder: (state) {
          var theme = state.theme;
          return Scaffold(
            backgroundColor: theme.primaryColorDark,
            bottomNavigationBar: _buildBottomNavigationBar(state),
            appBar: _buildAppBar(state),
            body: _buildMessageDetailList(state),
          );
        },
      ),
    );
  }

  _buildMessageDetailList(MessageDetailState state) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 24.h),
      child: EasyRefresh(
        controller: state.easyRefreshController,
        footer: ClassicalFooter(),
        child: FlutterListView(
          reverse: true,
          physics: const BouncingScrollPhysics(),
          controller: state.listViewController,
          delegate: FlutterListViewDelegate(
            (BuildContext context, int index) => _buildTextMessageDetail(state, index),
            childCount: state.messageCount,
            onItemKey: (index) => state.messageList[index].id.toString(),
            keepPosition: false,
            initOffsetBasedOnBottom: true,
            firstItemAlign: FirstItemAlign.end,
          ),
        ),
        onLoad: state.doLoadAction,
      ),
    );
  }

  Widget _buildTextMessageDetail(MessageDetailState state, int index) {
    final message = state.messageList[index];
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      // crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Center(
          child: Text(
            formatMessageTime(message),
            style: state.theme.lightTextStyle,
          ),
        ),
        Container(
          padding: EdgeInsets.all(24.h),
          margin: EdgeInsets.symmetric(vertical: 18.h),
          decoration: BoxDecoration(color: state.theme.backgroundColor, borderRadius: const BorderRadius.all(Radius.circular(6.66))),
          child: state.renderMessageContent(message),
        )
      ],
    );
  }

MessageDetailState.dart

  @override
  onInit() {
    super.onInit();
    subscription = layoutState.eventBus.on<SaveMessagePayload>().listen((event) {
      if (event.appId == currentMessageDetail.app.appId) {
        update();
        jumpToLast();
      }
    });
  }

  @override
  onReady() {
    super.onReady();
    jumpToLast();
  }

  jumpToLast() {
    listViewController.sliverController.jumpToIndex(0);
  }

  Future<void> doLoadAction() async {
    currentMessageDetail.loadHistory().then((value) {
      update();
      easyRefreshController.finishLoad(success: true, noMore: value <= 10);
    });
  }
endison1986 commented 2 years ago

I tested char2.dart, as you say, it's smooth.

robert-luoqing commented 2 years ago

I have simplify your code into https://github.com/robert-luoqing/flutter_list_view/blob/master/example/lib/chat3.dart. But I still can' reproduce your issue. I doubt you misuse the getx state to empty messageList then set messageList again

endison1986 commented 2 years ago

@robert-luoqing thanks for your help. I tested for a long time, and I found the reason. because I use StyledText.selectable(styled_text: ^5.1.0) to create content widget, and this widget caused the above problems.

Widget renderMessageContent(Message message) {
// ....
return StyledText.selectable(
      text: '11111'
}
}

These codes alone can cause lag

but I only use ListView and jumpTo, this problem never occur, Is there anything that can be optimized

robert-bitguild commented 2 years ago

Please add onIsPermanent: (keyOrIndex) => true) into FlutterListViewDelegate to resolve the issue.

        delegate: FlutterListViewDelegate(
            (BuildContext context, int index) => _buildTextMessageDetail(index),
            childCount: messageList.length,
            onItemKey: (index) => messageList[index]["id"],
            keepPosition: false,
            initOffsetBasedOnBottom: true,
            firstItemAlign: FirstItemAlign.end,
            onIsPermanent: (keyOrIndex) => true),

The issue caused by the first item is always generating a new render and StyledText.selectable will cause two renders, the first render height and second render height is different.

endison1986 commented 2 years ago

Thank you very mush @robert-bitguild and @robert-luoqing , It works. 非常感谢 But I have a new problem, and looks like it. when I use onIsPermanent: (keyOrIndex) => true in the widget, the old problem fixed, but when I drag the message list and to load history message, when I insert message into the message list, the problem occur again. But old code is works when I load history message.

Future<void> doLoadAction() async {
  currentMessageDetail.loadHistory().then((value) {
    update();
    easyRefreshController.finishLoad(success: true, noMore: value <= 10);
  });
}

Future<int> loadHistory() async {
    if (messageSize == 0) return 0;

    final last = messageList.last;
    final historyCount = await messageProvider.getHistoryMessageCount(appId, 10, last.id);
    if (historyCount > 0) {
      final historyMessage = await messageProvider.getMessageList(appId, 10, last.id);
      for (var element in historyMessage) {
        _messageList.add(element);
      }
    }
    return historyCount;
  }

without onIsPermanent: (keyOrIndex) => true aaa with onIsPermanent: (keyOrIndex) => true bbb

robert-bitguild commented 2 years ago

I can't reproduce your issue when I mockup your code. Please change some code in below to reproduce your issue and send me back.

import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_list_view/flutter_list_view.dart';
import 'package:styled_text/styled_text.dart';

class Chat3 extends StatefulWidget {
  const Chat3({Key? key}) : super(key: key);

  @override
  State<Chat3> createState() => _Chat3State();
}

class _Chat3State extends State<Chat3> {
  final easyRefreshController = EasyRefreshController();
  final listViewController = FlutterListViewController();
  var messageList = <Map<String, dynamic>>[];
  _generateMsg() {
    var time = DateTime.now();
    var newMsg = {
      "id": time.microsecondsSinceEpoch.toString(),
      "time": time.toString(),
      "msg": time.toString()
    };
    messageList.insert(0, newMsg);
    setState(() {});
  }

  _generateMsgs() {
    var time = DateTime.now();
    for (int i = 0; i < 10; i++) {
      time = time.add(const Duration(milliseconds: 1));
      var newMsg = {
        "id": time.microsecondsSinceEpoch.toString(),
        "time": time.toString(),
        "msg": time.toString()
      };
      messageList.add(newMsg);
    }
  }

  @override
  void initState() {
    _generateMsgs();
    super.initState();
  }

  Future<void> doLoadAction() async {
    await Future.delayed(const Duration(seconds: 1));
    _generateMsgs();
    easyRefreshController.finishLoad(success: true, noMore: false);
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Chat"),
        actions: [
          TextButton(
              onPressed: _generateMsg,
              child: const Text(
                "Mock To Receive",
                style: TextStyle(color: Colors.white),
              ))
        ],
      ),
      body: _buildMessageDetailList(),
    );
  }

  _buildMessageDetailList() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 24),
      child: EasyRefresh(
        controller: easyRefreshController,
        footer: ClassicalFooter(),
        child: FlutterListView(
          reverse: true,
          physics: const BouncingScrollPhysics(),
          controller: listViewController,
          delegate: FlutterListViewDelegate(
            (BuildContext context, int index) => _buildTextMessageDetail(index),
            childCount: messageList.length,
            onItemKey: (index) => messageList[index]["id"],
            keepPosition: false,
            initOffsetBasedOnBottom: true,
            firstItemAlign: FirstItemAlign.end,
            onIsPermanent: (key) => true,
          ),
        ),
        onLoad: doLoadAction,
      ),
    );
  }

  Widget _buildTextMessageDetail(int index) {
    final message = messageList[index];
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Center(
          child: StyledText.selectable(text: message["time"].toString()),
        ),
        Container(
          padding: const EdgeInsets.all(24),
          margin: const EdgeInsets.symmetric(vertical: 18),
          decoration: const BoxDecoration(
              color: Colors.yellow,
              borderRadius: BorderRadius.all(Radius.circular(6.66))),
          child: StyledText.selectable(text: message["msg"]),
        )
      ],
    );
  }
}