Merge pull request #633 from GetStream/ref/bsb.loadingBuilder

refactor(core, ui)!: Refactor BetterStreamBuilder.
This commit is contained in:
Salvatore Giordano
2021-08-20 14:46:49 +02:00
committed by GitHub
10 changed files with 60 additions and 62 deletions
+4 -1
View File
@@ -42,9 +42,12 @@ breakdown:
* `MessageTheme` is now `MessageThemeData` * `MessageTheme` is now `MessageThemeData`
* `UserListViewTheme` is now `UserListViewThemeData` * `UserListViewTheme` is now `UserListViewThemeData`
- Updated core dependency.
🐞 Fixed 🐞 Fixed
- Fixed `MessageInput` textField case where `input` is not enabled if the file picked from the camera is null. - Fixed `MessageInput` textField case where `input` is not enabled if the file picked from the
camera is null.
- Fixed date dividers position/alignment in non reversed `MessageListView`. - Fixed date dividers position/alignment in non reversed `MessageListView`.
## 2.1.2 ## 2.1.2
@@ -94,13 +94,13 @@ class ChannelPreview extends StatelessWidget {
textStyle: channelPreviewTheme.titleStyle, textStyle: channelPreviewTheme.titleStyle,
), ),
), ),
BetterStreamBuilder<List<Member>?>( BetterStreamBuilder<List<Member>>(
stream: channel.state?.membersStream, stream: channel.state?.membersStream,
initialData: channel.state?.members, initialData: channel.state?.members,
comparator: const ListEquality().equals, comparator: const ListEquality().equals,
builder: (context, members) { builder: (context, members) {
if (members?.isEmpty == true || if (members.isEmpty ||
members?.any((Member e) => members.any((Member e) =>
e.user!.id == e.user!.id ==
channel.client.state.currentUser?.id) != channel.client.state.currentUser?.id) !=
true) { true) {
@@ -153,13 +153,10 @@ class ChannelPreview extends StatelessWidget {
)); ));
} }
Widget _buildDate(BuildContext context) => BetterStreamBuilder<DateTime?>( Widget _buildDate(BuildContext context) => BetterStreamBuilder<DateTime>(
stream: channel.lastMessageAtStream, stream: channel.lastMessageAtStream,
initialData: channel.lastMessageAt, initialData: channel.lastMessageAt,
builder: (context, data) { builder: (context, data) {
if (data == null) {
return const Offstage();
}
final lastMessageAt = data.toLocal(); final lastMessageAt = data.toLocal();
String stringDate; String stringDate;
@@ -213,12 +210,12 @@ class ChannelPreview extends StatelessWidget {
Widget _buildLastMessage(BuildContext context) => Align( Widget _buildLastMessage(BuildContext context) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: BetterStreamBuilder<List<Message>?>( child: BetterStreamBuilder<List<Message>>(
stream: channel.state!.messagesStream, stream: channel.state!.messagesStream,
initialData: channel.state!.messages, initialData: channel.state!.messages,
builder: (context, data) { builder: (context, data) {
final lastMessage = data final lastMessage =
?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); data.lastWhereOrNull((m) => !m.shadowed && !m.isDeleted);
if (lastMessage == null) { if (lastMessage == null) {
return const SizedBox(); return const SizedBox();
} }
@@ -38,7 +38,7 @@ class ConnectionStatusBuilder extends StatelessWidget {
return BetterStreamBuilder<ConnectionStatus>( return BetterStreamBuilder<ConnectionStatus>(
initialData: client.wsConnectionStatus, initialData: client.wsConnectionStatus,
stream: stream, stream: stream,
loadingBuilder: loadingBuilder, noDataBuilder: loadingBuilder,
errorBuilder: (context, error) { errorBuilder: (context, error) {
if (errorBuilder != null) { if (errorBuilder != null) {
return errorBuilder!(context, error); return errorBuilder!(context, error);
@@ -17,7 +17,7 @@ class UnreadIndicator extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
return IgnorePointer( return IgnorePointer(
child: BetterStreamBuilder<int?>( child: BetterStreamBuilder<int>(
stream: cid != null stream: cid != null
? client.state.channels[cid]?.state?.unreadCountStream ? client.state.channels[cid]?.state?.unreadCountStream
: client.state.totalUnreadCountStream, : client.state.totalUnreadCountStream,
@@ -25,7 +25,7 @@ class UnreadIndicator extends StatelessWidget {
? client.state.channels[cid]?.state?.unreadCount ? client.state.channels[cid]?.state?.unreadCount
: client.state.totalUnreadCount, : client.state.totalUnreadCount,
builder: (context, data) { builder: (context, data) {
if (data == null || data == 0) { if (data == 0) {
return const Offstage(); return const Offstage();
} }
return Material( return Material(
@@ -1,5 +1,11 @@
## Upcoming ## Upcoming
🛑️ Breaking Changes from `2.1.1`
- Renamed `BetterStreamBuilder.loadingBuilder` to `.noDataBuilder`
🔄 Changed
- `BetterStreamBuilder.initialData` is now nullable/not-required.
🐞 Fixed 🐞 Fixed
- [#612](https://github.com/GetStream/stream-chat-flutter/issues/612) `ChannelListView` pagination doesn't work after refresh - [#612](https://github.com/GetStream/stream-chat-flutter/issues/612) `ChannelListView` pagination doesn't work after refresh
@@ -6,23 +6,23 @@ import 'package:flutter/widgets.dart';
/// It requires [initialData] and will rebuild /// It requires [initialData] and will rebuild
/// only when the new data is different than the current data /// only when the new data is different than the current data
/// The [comparator] is used to check if the new data is different /// The [comparator] is used to check if the new data is different
class BetterStreamBuilder<T> extends StatefulWidget { class BetterStreamBuilder<T extends Object> extends StatefulWidget {
/// Creates a new BetterStreamBuilder /// Creates a new BetterStreamBuilder
const BetterStreamBuilder({ const BetterStreamBuilder({
required this.stream, required this.stream,
required this.initialData,
required this.builder, required this.builder,
this.loadingBuilder, this.initialData,
this.noDataBuilder,
this.errorBuilder, this.errorBuilder,
this.comparator, this.comparator,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
/// The stream to listen to /// The stream to listen to
final Stream<T>? stream; final Stream<T?>? stream;
/// The initial data available /// The initial data available
final T initialData; final T? initialData;
/// Comparator used to check if the new data is different than the last one /// Comparator used to check if the new data is different than the last one
final bool Function(T?, T?)? comparator; final bool Function(T?, T?)? comparator;
@@ -31,7 +31,7 @@ class BetterStreamBuilder<T> extends StatefulWidget {
final Widget Function(BuildContext context, T data) builder; final Widget Function(BuildContext context, T data) builder;
/// Builder that builds when the data is null /// Builder that builds when the data is null
final Widget Function(BuildContext context)? loadingBuilder; final Widget Function(BuildContext context)? noDataBuilder;
/// Builder used when there is an error /// Builder used when there is an error
final Widget Function(BuildContext context, Object error)? errorBuilder; final Widget Function(BuildContext context, Object error)? errorBuilder;
@@ -40,21 +40,26 @@ class BetterStreamBuilder<T> extends StatefulWidget {
_BetterStreamBuilderState createState() => _BetterStreamBuilderState<T>(); _BetterStreamBuilderState createState() => _BetterStreamBuilderState<T>();
} }
class _BetterStreamBuilderState<T> extends State<BetterStreamBuilder<T>> { class _BetterStreamBuilderState<T extends Object>
extends State<BetterStreamBuilder<T>> {
T? _lastEvent; T? _lastEvent;
StreamSubscription? _subscription; StreamSubscription<T?>? _subscription;
Object? _lastError; Object? _lastError;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_lastError != null) { final error = _lastError;
return widget.errorBuilder!(context, _lastError!); if (error != null) {
final errorBuilder = widget.errorBuilder;
if (errorBuilder != null) {
return errorBuilder(context, error);
}
} }
final event = _lastEvent;
if (_lastEvent == null) { if (event == null) {
return widget.loadingBuilder?.call(context) ?? const Offstage(); return widget.noDataBuilder?.call(context) ?? const Offstage();
} }
return widget.builder(context, _lastEvent ?? widget.initialData); return widget.builder(context, event);
} }
@override @override
@@ -87,22 +92,22 @@ class _BetterStreamBuilderState<T> extends State<BetterStreamBuilder<T>> {
void _onError(error) { void _onError(error) {
if (widget.errorBuilder != null && error != _lastError) { if (widget.errorBuilder != null && error != _lastError) {
_lastError = error;
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
_lastError = error;
} }
} }
void _onEvent(T event) { void _onEvent(T? event) {
_lastError = null; _lastError = null;
final isEqual = final isEqual =
widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent;
if (!isEqual) { if (!isEqual) {
_lastEvent = event;
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
_lastEvent = event;
} }
} }
} }
@@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.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/channels_bloc.dart'; import 'package:stream_chat_flutter_core/src/channels_bloc.dart';
import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; import 'package:stream_chat_flutter_core/src/stream_chat_core.dart';
import 'package:stream_chat_flutter_core/src/typedef.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart';
@@ -137,19 +138,14 @@ class ChannelListCoreState extends State<ChannelListCore> {
@override @override
Widget build(BuildContext context) => _buildListView(_channelsBloc); Widget build(BuildContext context) => _buildListView(_channelsBloc);
StreamBuilder<List<Channel>> _buildListView( BetterStreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState, ChannelsBlocState channelsBlocState,
) => ) =>
StreamBuilder<List<Channel>>( BetterStreamBuilder<List<Channel>>(
stream: channelsBlocState.channelsStream, stream: channelsBlocState.channelsStream,
builder: (context, snapshot) { errorBuilder: widget.errorBuilder,
if (snapshot.hasError) { noDataBuilder: widget.loadingBuilder,
return widget.errorBuilder(context, snapshot.error!); builder: (context, channels) {
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
}
final channels = snapshot.data!;
if (channels.isEmpty) { if (channels.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
@@ -138,7 +138,7 @@ class MessageListCoreState extends State<MessageListCore> {
return true; return true;
} }
return BetterStreamBuilder<List<Message>?>( return BetterStreamBuilder<List<Message>>(
initialData: initialData, initialData: initialData,
comparator: const ListEquality().equals, comparator: const ListEquality().equals,
stream: messagesStream!.map( stream: messagesStream!.map(
@@ -148,9 +148,9 @@ class MessageListCoreState extends State<MessageListCore> {
), ),
), ),
errorBuilder: widget.errorBuilder, errorBuilder: widget.errorBuilder,
loadingBuilder: widget.loadingBuilder, noDataBuilder: widget.loadingBuilder,
builder: (context, data) { builder: (context, data) {
final messageList = data?.reversed.toList(growable: false) ?? []; final messageList = data.reversed.toList(growable: false);
if (messageList.isEmpty && !_isThreadConversation) { if (messageList.isEmpty && !_isThreadConversation) {
if (_upToDate) { if (_upToDate) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.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/message_search_bloc.dart'; import 'package:stream_chat_flutter_core/src/message_search_bloc.dart';
import 'package:stream_chat_flutter_core/src/typedef.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart';
@@ -132,16 +133,11 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
Widget build(BuildContext context) => _buildListView(_messageSearchBloc!); Widget build(BuildContext context) => _buildListView(_messageSearchBloc!);
Widget _buildListView(MessageSearchBlocState messageSearchBloc) => Widget _buildListView(MessageSearchBlocState messageSearchBloc) =>
StreamBuilder<List<GetMessageResponse>>( BetterStreamBuilder<List<GetMessageResponse>>(
stream: messageSearchBloc.messagesStream, stream: messageSearchBloc.messagesStream,
builder: (context, snapshot) { errorBuilder: widget.errorBuilder,
if (snapshot.hasError) { noDataBuilder: widget.loadingBuilder,
return widget.errorBuilder(context, snapshot.error!); builder: (context, items) {
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
}
final items = snapshot.data!;
if (items.isEmpty) { if (items.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
@@ -176,16 +176,11 @@ class UserListCoreState extends State<UserListCore>
}, },
); );
StreamBuilder<List<ListItem>> _buildListView() => StreamBuilder( BetterStreamBuilder<List<ListItem>> _buildListView() => BetterStreamBuilder(
stream: _buildUserStream(), stream: _buildUserStream(),
builder: (context, snapshot) { errorBuilder: widget.errorBuilder,
if (snapshot.hasError) { noDataBuilder: widget.loadingBuilder,
return widget.errorBuilder(context, snapshot.error!); builder: (context, items) {
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
}
final items = snapshot.data!;
if (items.isEmpty) { if (items.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }