diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 52374239..0b759a53 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -213,7 +213,7 @@ class StreamChatClient { _wsConnectionStatusController.add(status); /// The current status value of the websocket connection - ConnectionStatus? get wsConnectionStatus => + ConnectionStatus get wsConnectionStatus => _wsConnectionStatusController.value; /// This notifies the connection status of the websocket connection. diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 19495809..aefdb44e 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -83,14 +83,14 @@ class ChannelImage extends StatelessWidget { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final channel = this.channel ?? StreamChannel.of(context).channel; - return StreamBuilder>( + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { String? image; final chatThemeData = StreamChatTheme.of(context); - if (snapshot.data!.containsKey('image') == true) { - image = snapshot.data!['image']; + if (snapshot.containsKey('image') == true) { + image = snapshot['image']; } else if (channel.state?.members.length == 2) { final otherMember = channel.state?.members .firstWhere((member) => member.user?.id != streamChat.user?.id); @@ -153,9 +153,7 @@ class ChannelImage extends StatelessWidget { imageUrl: image, errorWidget: (_, __, ___) => Center( child: Text( - snapshot.data?.containsKey('name') ?? false - ? snapshot.data!['name'][0] - : '', + snapshot.containsKey('name') ? snapshot['name'][0] : '', style: TextStyle( color: chatThemeData.colorTheme.white, fontWeight: FontWeight.bold, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index e2067f1b..91e7aceb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -25,13 +25,14 @@ class ChannelInfo extends StatelessWidget { @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; - return StreamBuilder>( - stream: channel.state?.membersStream, + return BetterStreamBuilder>( + stream: channel.state!.membersStream, + initialData: channel.state!.members, builder: (context, snapshot) => ConnectionStatusBuilder( statusBuilder: (context, status) { switch (status) { case ConnectionStatus.connected: - return _buildConnectedTitleState(context, snapshot.data); + return _buildConnectedTitleState(context, snapshot); case ConnectionStatus.connecting: return _buildConnectingTitleState(context); case ConnectionStatus.disconnected: diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 2cd804c9..4d560bed 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -26,11 +26,14 @@ class ChannelName extends StatelessWidget { final client = StreamChat.of(context); final channel = StreamChannel.of(context).channel; - return StreamBuilder>( + return BetterStreamBuilder>( stream: channel.extraDataStream, initialData: channel.extraData, - builder: (context, snapshot) => - _buildName(snapshot.data!, channel.state?.members, client), + builder: (context, snapshot) => _buildName( + snapshot, + channel.state?.members, + client, + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index cfc1b937..9137265f 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -13,14 +13,14 @@ class ConnectionStatusBuilder extends StatelessWidget { const ConnectionStatusBuilder({ Key? key, required this.statusBuilder, - this.initialStatus = ConnectionStatus.disconnected, + this.initialStatus, this.connectionStatusStream, this.errorBuilder, this.loadingBuilder, }) : super(key: key); /// The connection status that will be used to create the initial snapshot. - final ConnectionStatus initialStatus; + final ConnectionStatus? initialStatus; /// The asynchronous computation to which this builder is currently connected. final Stream? connectionStatusStream; @@ -37,23 +37,19 @@ class ConnectionStatusBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final stream = connectionStatusStream ?? - StreamChat.of(context).client.wsConnectionStatusStream; - return StreamBuilder( + final client = StreamChat.of(context).client; + final stream = connectionStatusStream ?? client.wsConnectionStatusStream; + return BetterStreamBuilder( + initialData: initialStatus ?? client.wsConnectionStatus, stream: stream, - builder: (context, snapshot) { - if (snapshot.hasError) { - if (errorBuilder != null) { - return errorBuilder!(context, snapshot.error); - } - return const Offstage(); + loadingBuilder: loadingBuilder, + errorBuilder: (context, error) { + if (errorBuilder != null) { + return errorBuilder!(context, error); } - if (!snapshot.hasData) { - if (loadingBuilder != null) return loadingBuilder!(context); - return const Offstage(); - } - return statusBuilder(context, snapshot.data!); + return const Offstage(); }, + builder: statusBuilder, ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index c13a87a7..25cf9941 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -606,8 +606,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() => StreamBuilder>( stream: Rx.combineLatest2( - streamChannel!.channel.state!.isUpToDateStream, - streamChannel!.channel.state!.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream.distinct(), + streamChannel!.channel.state!.unreadCountStream.distinct(), (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -689,23 +689,18 @@ class _MessageListViewState extends State { final stream = direction == QueryDirection.top ? streamChannel.queryTopMessages : streamChannel.queryBottomMessages; - return StreamBuilder( + return BetterStreamBuilder( key: Key('LOADING-INDICATOR $direction'), stream: stream, initialData: false, + errorBuilder: (context, error) => Container( + color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.2), + child: const Center( + child: Text('Error loading messages'), + ), + ), builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: const Center( - child: Text('Error loading messages'), - ), - ); - } - if (!snapshot.data!) { + if (!snapshot) { if (!_isThreadConversation && direction == QueryDirection.top) { return const SizedBox( height: 52, @@ -1163,14 +1158,14 @@ class _MessageListViewState extends State { Navigator.push( context, MaterialPageRoute( - builder: (_) => StreamBuilder( + builder: (_) => BetterStreamBuilder( stream: streamChannel!.channel.state!.messagesStream.map( (messages) => messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) => StreamChannel( channel: streamChannel!.channel, - child: widget.threadBuilder!(context, snapshot.data), + child: widget.threadBuilder!(context, snapshot), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index b18a0532..97e061ae 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -449,182 +449,189 @@ class _MessageWidgetState extends State final leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - return Portal( - child: GestureDetector( - onTap: () { - widget.onMessageTap!(widget.message); - }, - onLongPress: widget.message.isDeleted && !isFailedState - ? null - : () => onLongPress(context), - child: Padding( - padding: widget.padding ?? const EdgeInsets.all(8), - child: FractionallySizedBox( - alignment: - widget.reverse ? Alignment.centerRight : Alignment.centerLeft, - widthFactor: 0.78, - child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: widget.reverse - ? AlignmentDirectional.bottomEnd - : AlignmentDirectional.bottomStart, - children: [ - Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), - constraints: - const BoxConstraints(maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: - Alignment(widget.reverse ? 1 : -1, -1), - childAnchor: - Alignment(widget.reverse ? -1 : 1, -1), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget.message.reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Container( - // ignore: lines_longer_than_80_chars - margin: EdgeInsets.symmetric( - horizontal: + return Material( + type: MaterialType.transparency, + child: Portal( + child: InkWell( + onTap: () { + widget.onMessageTap!(widget.message); + }, + onLongPress: widget.message.isDeleted && !isFailedState + ? null + : () => onLongPress(context), + child: Padding( + padding: widget.padding ?? const EdgeInsets.all(8), + child: FractionallySizedBox( + alignment: + widget.reverse ? Alignment.centerRight : Alignment.centerLeft, + widthFactor: 0.78, + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + alignment: widget.reverse + ? AlignmentDirectional.bottomEnd + : AlignmentDirectional.bottomStart, + children: [ + Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget + .message + .reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? Container( + // ignore: lines_longer_than_80_chars + margin: EdgeInsets.symmetric( + horizontal: + // ignore: lines_longer_than_80_chars + widget.showUserAvatar == + // ignore: lines_longer_than_80_chars + DisplayWidget + .gone + ? 0 + : 4.0), + child: DeletedMessage( + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + // ignore: lines_longer_than_80_chars - widget.showUserAvatar == - // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == DisplayWidget.gone ? 0 : 4.0), - child: DeletedMessage( - borderRadiusGeometry: - widget.borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + - // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == - DisplayWidget.gone - ? 0 - : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget.borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], + ), ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: widget.reverse ? null : 4, - left: widget.reverse ? 4 : null, - top: -8, - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), + if (showBottomRow) + Padding( + padding: EdgeInsets.only(left: leftPadding), + child: _bottomRow, ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], - ), - if (showBottomRow) - Padding( - padding: EdgeInsets.only(left: leftPadding), - child: _bottomRow, - ), - if (isFailedState) - Positioned( - left: widget.reverse ? 0 : null, - right: widget.reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon.error(size: 20), - ), - ], - ), - ], + if (isFailedState) + Positioned( + left: widget.reverse ? 0 : null, + right: widget.reverse ? null : 0, + bottom: showBottomRow ? 18 : -2, + child: StreamSvgIcon.error(size: 20), + ), + ], + ), + ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 010b2c07..8f974d6e 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users @@ -41,11 +42,12 @@ class TypingIndicator extends StatelessWidget { child: alternativeWidget ?? const Offstage(), ), ); - return StreamBuilder>( + return BetterStreamBuilder>( + initialData: channelState.typingEvents, stream: channelState.typingEventsStream, builder: (context, snapshot) => AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: snapshot.data?.isNotEmpty == true + child: snapshot.isNotEmpty == true ? Padding( padding: padding, child: Align( @@ -61,7 +63,7 @@ class TypingIndicator extends StatelessWidget { ), Text( // ignore: lines_longer_than_80_chars - ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', + ' ${snapshot[0].name}${snapshot.length == 1 ? '' : ' and ${snapshot.length - 1} more'} ${snapshot.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: style, ), diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 9c3a5f88..8b235187 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -17,7 +17,7 @@ class UnreadIndicator extends StatelessWidget { Widget build(BuildContext context) { final client = StreamChat.of(context).client; return IgnorePointer( - child: StreamBuilder( + child: BetterStreamBuilder( stream: cid != null ? client.state.channels[cid]?.state?.unreadCountStream : client.state.totalUnreadCountStream, @@ -25,8 +25,8 @@ class UnreadIndicator extends StatelessWidget { ? client.state.channels[cid]?.state?.unreadCount : client.state.totalUnreadCount, builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == 0) { - return const SizedBox(); + if (snapshot == null || snapshot == 0) { + return const Offstage(); } return Material( borderRadius: BorderRadius.circular(8), @@ -42,7 +42,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data! > 99 ? '99+' : snapshot.data}', + '${snapshot > 99 ? '99+' : snapshot}', style: const TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 5a86d4ef..644ea99f 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart new file mode 100644 index 00000000..bf50966b --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +class BetterStreamBuilder extends StatefulWidget { + const BetterStreamBuilder({ + required this.stream, + required this.initialData, + required this.builder, + this.loadingBuilder, + this.errorBuilder, + this.comparator, + Key? key, + }) : super(key: key); + + final Stream? stream; + final T initialData; + final bool Function(T?, T)? comparator; + final Widget Function(BuildContext context, T data) builder; + final Widget Function(BuildContext context)? loadingBuilder; + final Widget Function(BuildContext context, Object error)? errorBuilder; + + @override + _BetterStreamBuilderState createState() => _BetterStreamBuilderState(); +} + +class _BetterStreamBuilderState extends State> { + Widget? _child; + T? _lastEvent; + StreamSubscription? _subscription; + Object? _lastError; + + @override + Widget build(BuildContext context) => _child ?? const Offstage(); + + bool _firstTime = true; + @override + void didChangeDependencies() { + if (_firstTime) { + if (widget.initialData == null && widget.loadingBuilder != null) { + _child = widget.loadingBuilder!(context); + } else { + _onEvent(widget.initialData); + } + _lastEvent = widget.initialData; + _subscription = widget.stream?.listen( + _onEvent, + onError: _onError, + ); + + _firstTime = false; + } + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(covariant BetterStreamBuilder oldWidget) { + if (_lastError != null && oldWidget.errorBuilder != widget.errorBuilder) { + _onError(_lastError); + } else if (oldWidget.builder != widget.builder) { + _onEvent(_lastEvent); + } + + if (oldWidget.stream != widget.stream) { + _subscription?.cancel(); + _subscription = widget.stream?.listen( + _onEvent, + onError: _onError, + ); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } + + void _onError(error) { + if (widget.errorBuilder != null && error != _lastError) { + setState(() { + _child = widget.errorBuilder!(context, error); + }); + _lastError = error; + } + } + + void _onEvent(event) { + _lastError = null; + if (widget.comparator != null + ? widget.comparator!(_lastEvent, event) + : event != _lastEvent) { + setState(() { + _child = widget.builder(context, event); + }); + _lastEvent = event; + } + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 9b0d3d38..0da733bf 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/better_stream_builder.dart'; import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; @@ -127,6 +129,10 @@ class MessageListCoreState extends State { .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel!.channel.state?.messagesStream; + final initialData = _isThreadConversation + ? _streamChannel!.channel.state?.threads[widget.parentMessage!.id] + : _streamChannel!.channel.state?.messages; + bool defaultFilter(Message m) { final isMyMessage = m.user?.id == _currentUser?.id; final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true; @@ -134,30 +140,27 @@ class MessageListCoreState extends State { return true; } - return StreamBuilder?>( - stream: messagesStream?.map( + return BetterStreamBuilder?>( + initialData: initialData, + comparator: const ListEquality().equals, + stream: messagesStream!.map( (messages) => messages?.where(widget.messageFilter ?? defaultFilter).toList( growable: false, ), ), + errorBuilder: widget.errorWidgetBuilder, + loadingBuilder: widget.loadingBuilder, builder: (context, snapshot) { - if (snapshot.hasError) { - return widget.errorWidgetBuilder(context, snapshot.error!); - } else if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } else { - final messageList = - snapshot.data?.reversed.toList(growable: false) ?? []; - if (messageList.isEmpty && !_isThreadConversation) { - if (_upToDate) { - return widget.emptyBuilder(context); - } - } else { - _messages = messageList; + final messageList = snapshot?.reversed.toList(growable: false) ?? []; + if (messageList.isEmpty && !_isThreadConversation) { + if (_upToDate) { + return widget.emptyBuilder(context); } - return widget.messageListBuilder(context, _messages); + } else { + _messages = messageList; } + return widget.messageListBuilder(context, _messages); }, ); } diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 01463de0..dba20f40 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -2,6 +2,7 @@ library stream_chat_flutter_core; export 'package:stream_chat/stream_chat.dart'; +export 'src/better_stream_builder.dart'; export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart';