test: add tests for stream chat flutter core (#354)

* test(core): add tests for channels bloc

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): refactor some tests due to changes in channels bloc

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for channel list core

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for users bloc, minor improvements

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for user list core, minor improvements

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for message search bloc, refactor

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for message search list core, refactor

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for lazy load scroll view

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for stream chat core

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for message list core

Signed-off-by: Sahil Kumar <[email protected]>

* test(core): add tests for stream channel

Signed-off-by: Sahil Kumar <[email protected]>

* Fix merge conflicts

Signed-off-by: Sahil Kumar <[email protected]>

* flutter format

Signed-off-by: Sahil Kumar <[email protected]>

* fixes

Signed-off-by: Sahil Kumar <[email protected]>

* update workflow

Signed-off-by: Sahil Kumar <[email protected]>

* chore(core): fix analyzer issues

Signed-off-by: Sahil Kumar <[email protected]>

* chore(ui-kit): fix analyzer issues

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2021-03-30 10:16:12 +02:00
committed by GitHub
parent cba0b1a391
commit 420d6384c4
35 changed files with 4867 additions and 413 deletions
@@ -130,10 +130,11 @@ class ChannelListCore extends StatefulWidget {
final PaginationParams pagination;
@override
_ChannelListCoreState createState() => _ChannelListCoreState();
ChannelListCoreState createState() => ChannelListCoreState();
}
class _ChannelListCoreState extends State<ChannelListCore> {
/// The current state of the [ChannelListCore].
class ChannelListCoreState extends State<ChannelListCore> {
@override
Widget build(BuildContext context) {
final channelsBloc = ChannelsBloc.of(context);
@@ -148,7 +149,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
stream: channelsBlocState.channelsStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return _buildErrorWidget(snapshot, context, channelsBlocState);
return widget.errorBuilder(context, snapshot.error);
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
@@ -161,18 +162,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
},
);
Widget _buildErrorWidget(
AsyncSnapshot<List<Channel>> snapshot,
BuildContext context,
ChannelsBlocState channelsBlocState,
) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
return widget.errorBuilder(context, snapshot.error);
}
/// Fetches initial channels and updates the widget
Future<void> loadData() {
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
@@ -183,6 +173,7 @@ class _ChannelListCoreState extends State<ChannelListCore> {
);
}
/// Fetches more channels with updated pagination and updates the widget
Future<void> paginateData() {
final channelsBloc = ChannelsBloc.of(context);
return channelsBloc.queryChannels(
@@ -223,9 +214,9 @@ class _ChannelListCoreState extends State<ChannelListCore> {
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
widget.options?.toString() != oldWidget.options?.toString() ||
widget.pagination?.toJson()?.toString() !=
oldWidget.pagination?.toJson()?.toString() ||
widget.options?.toString() != oldWidget.options?.toString()) {
oldWidget.pagination?.toJson()?.toString()) {
loadData();
}
}
@@ -74,10 +74,9 @@ class ChannelsBlocState extends State<ChannelsBloc>
/// The current channel list as a stream
Stream<List<Channel>> get channelsStream => _channelsController.stream;
final BehaviorSubject<bool> _queryChannelsLoadingController =
BehaviorSubject.seeded(false);
final _queryChannelsLoadingController = BehaviorSubject.seeded(false);
final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject();
final _channelsController = BehaviorSubject<List<Channel>>();
/// The stream notifying the state of queryChannel call
Stream<bool> get queryChannelsLoading =>
@@ -91,15 +90,14 @@ class ChannelsBlocState extends State<ChannelsBloc>
List<SortOption<ChannelModel>> sortOptions,
PaginationParams paginationParams,
Map<String, dynamic> options,
bool onlyOffline = false,
}) async {
final client = StreamChatCore.of(context).client;
if (client.state?.user == null ||
_queryChannelsLoadingController.value == true) {
return;
if (_queryChannelsLoadingController.value == true) return;
if (_channelsController.hasValue) {
_queryChannelsLoadingController.add(true);
}
_queryChannelsLoadingController.sink.add(true);
try {
final clear = paginationParams == null ||
@@ -115,15 +113,20 @@ class ChannelsBlocState extends State<ChannelsBloc>
if (clear) {
_channelsController.add(channels);
} else {
final l = oldChannels + channels;
_channelsController.add(l);
final temp = oldChannels + channels;
_channelsController.add(temp);
}
if (_channelsController.hasValue &&
_queryChannelsLoadingController.value) {
_queryChannelsLoadingController.sink.add(false);
}
_queryChannelsLoadingController.sink.add(false);
}
} catch (err, stackTrace) {
print(err);
print(stackTrace);
_queryChannelsLoadingController.addError(err, stackTrace);
} catch (e, stk) {
if (_channelsController.hasValue) {
_queryChannelsLoadingController.addError(e, stk);
} else {
_channelsController.addError(e, stk);
}
}
}
@@ -139,15 +142,14 @@ class ChannelsBlocState extends State<ChannelsBloc>
_subscriptions.add(client.on(EventType.messageNew).listen((e) {
final newChannels = List<Channel>.from(channels ?? []);
final index = newChannels.indexWhere((c) => c.cid == e.cid);
if (index > -1) {
if (index != -1) {
if (index > 0) {
final channel = newChannels.removeAt(index);
newChannels.insert(0, channel);
}
} else if (widget.shouldAddChannel != null &&
widget.shouldAddChannel(e)) {
} else if (widget.shouldAddChannel?.call(e) == true) {
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
if (hiddenIndex > -1) {
if (hiddenIndex != -1) {
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex);
} else {
@@ -1,8 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
// ignore: constant_identifier_names
enum _LoadingStatus { LOADING, STABLE }
enum _LoadingStatus { loading, stable }
/// Wrapper around a [Scrollable] which triggers [onEndOfPage]/[onStartOfPage] the Scrollable
/// reaches to the start or end of the view extent.
@@ -47,16 +46,16 @@ class LazyLoadScrollView extends StatefulWidget {
}
class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
_LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE;
double _scrollPosition = 0;
_LoadingStatus _loadMoreStatus = _LoadingStatus.stable;
@override
Widget build(BuildContext context) => NotificationListener(
Widget build(BuildContext context) =>
NotificationListener<ScrollNotification>(
onNotification: _onNotification,
child: widget.child,
);
bool _onNotification(Notification notification) {
bool _onNotification(ScrollNotification notification) {
if (notification is ScrollStartNotification) {
if (widget.onPageScrollStart != null) {
widget.onPageScrollStart();
@@ -73,7 +72,7 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
final pixels = notification.metrics.pixels;
final maxScrollExtent = notification.metrics.maxScrollExtent;
final minScrollExtent = notification.metrics.minScrollExtent;
final scrollOffset = widget.scrollOffset;
final scrollOffset = widget.scrollOffset ?? 0;
if (pixels > (minScrollExtent + scrollOffset) &&
pixels < (maxScrollExtent - scrollOffset)) {
@@ -85,28 +84,15 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
final extentBefore = notification.metrics.extentBefore;
final extentAfter = notification.metrics.extentAfter;
final scrollingDown = _scrollPosition < pixels;
if (scrollOffset == null || scrollOffset == 0) {
if (extentAfter == 0) {
_onEndOfPage();
}
if (extentBefore == 0) {
_onStartOfPage();
}
} else {
if (scrollingDown) {
if (extentAfter <= scrollOffset) {
_onEndOfPage();
}
} else {
if (extentBefore <= scrollOffset) {
_onStartOfPage();
}
}
if (extentAfter <= scrollOffset) {
_onEndOfPage();
return true;
}
if (extentBefore <= scrollOffset) {
_onStartOfPage();
return true;
}
_scrollPosition = pixels;
return true;
}
if (notification is OverscrollNotification) {
if (notification.overscroll > 0) {
@@ -121,22 +107,22 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
}
void _onEndOfPage() {
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
_loadMoreStatus = _LoadingStatus.LOADING;
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) {
_loadMoreStatus = _LoadingStatus.loading;
if (widget.onEndOfPage != null) {
widget.onEndOfPage().whenComplete(() {
_loadMoreStatus = _LoadingStatus.STABLE;
_loadMoreStatus = _LoadingStatus.stable;
});
}
}
}
void _onStartOfPage() {
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
_loadMoreStatus = _LoadingStatus.LOADING;
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) {
_loadMoreStatus = _LoadingStatus.loading;
if (widget.onStartOfPage != null) {
widget.onStartOfPage().whenComplete(() {
_loadMoreStatus = _LoadingStatus.STABLE;
_loadMoreStatus = _LoadingStatus.stable;
});
}
}
@@ -114,28 +114,28 @@ class MessageListCore extends StatefulWidget {
final bool Function(Message) messageFilter;
@override
_MessageListCoreState createState() => _MessageListCoreState();
MessageListCoreState createState() => MessageListCoreState();
}
class _MessageListCoreState extends State<MessageListCore> {
StreamChannelState streamChannel;
/// The current state of the [MessageListCore].
class MessageListCoreState extends State<MessageListCore> {
StreamChannelState _streamChannel;
bool get _upToDate => streamChannel.channel.state.isUpToDate;
bool get _upToDate => _streamChannel.channel.state.isUpToDate;
bool get _isThreadConversation => widget.parentMessage != null;
OwnUser get _currentUser => streamChannel.channel.client.state.user;
OwnUser get _currentUser => _streamChannel.channel.client.state.user;
List<Message> messages = <Message>[];
bool initialMessageHighlightComplete = false;
var _messages = <Message>[];
@override
Widget build(BuildContext context) {
final messagesStream = _isThreadConversation
? streamChannel.channel.state.threadsStream
? _streamChannel.channel.state.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id))
.map((threads) => threads[widget.parentMessage.id])
: streamChannel.channel.state?.messagesStream;
: _streamChannel.channel.state?.messagesStream;
bool defaultFilter(Message m) {
final isMyMessage = m.user.id == _currentUser.id;
@@ -148,10 +148,10 @@ class _MessageListCoreState extends State<MessageListCore> {
stream: messagesStream?.map((messages) =>
messages?.where(widget.messageFilter ?? defaultFilter)?.toList()),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
} else if (snapshot.hasError) {
if (snapshot.hasError) {
return widget.errorWidgetBuilder(context, snapshot.error);
} else if (!snapshot.hasData) {
return widget.loadingBuilder(context);
} else {
final messageList = snapshot.data?.reversed?.toList() ?? [];
if (messageList.isEmpty && !_isThreadConversation) {
@@ -159,29 +159,32 @@ class _MessageListCoreState extends State<MessageListCore> {
return widget.emptyBuilder(context);
}
} else {
messages = messageList;
_messages = messageList;
}
return widget.messageListBuilder(context, messages);
return widget.messageListBuilder(context, _messages);
}
},
);
}
/// Fetches more messages with updated pagination and updates the widget.
///
/// Optionally pass the fetch direction, defaults to [QueryDirection.bottom]
Future<void> paginateData(
{QueryDirection direction = QueryDirection.bottom}) {
if (!_isThreadConversation) {
return streamChannel.queryMessages(direction: direction);
return _streamChannel.queryMessages(direction: direction);
} else {
return streamChannel.getReplies(widget.parentMessage.id);
return _streamChannel.getReplies(widget.parentMessage.id);
}
}
@override
void initState() {
streamChannel = StreamChannel.of(context);
_streamChannel = StreamChannel.of(context);
if (_isThreadConversation) {
streamChannel.getReplies(widget.parentMessage.id);
_streamChannel.getReplies(widget.parentMessage.id);
}
if (widget.messageListController != null) {
@@ -194,7 +197,7 @@ class _MessageListCoreState extends State<MessageListCore> {
@override
void dispose() {
if (!_upToDate) {
streamChannel.reloadChannel();
_streamChannel.reloadChannel();
}
super.dispose();
}
@@ -203,5 +206,5 @@ class _MessageListCoreState extends State<MessageListCore> {
/// Controller used for paginating data in [ChannelListView]
class MessageListController {
/// Call this function to load further data
Function({QueryDirection direction}) paginateData;
Future<void> Function({QueryDirection direction}) paginateData;
}
@@ -48,17 +48,16 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
Stream<List<GetMessageResponse>> get messagesStream =>
_messageResponses.stream;
final BehaviorSubject<List<GetMessageResponse>> _messageResponses =
BehaviorSubject();
final _messageResponses = BehaviorSubject<List<GetMessageResponse>>();
final BehaviorSubject<bool> _queryMessagesLoadingController =
BehaviorSubject.seeded(false);
final _queryMessagesLoadingController = BehaviorSubject.seeded(false);
/// The stream notifying the state of queryUsers call
Stream<bool> get queryMessagesLoading =>
_queryMessagesLoadingController.stream;
/// Calls [StreamChatClient.search] updating [messageResponses] stream
/// Calls [StreamChatClient.search] updating
/// [messagesStream] and [queryMessagesLoading] stream
Future<void> search({
Map<String, dynamic> filter,
Map<String, dynamic> messageFilter,
@@ -66,33 +65,13 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
String query,
PaginationParams pagination,
}) async {
_messageResponses.add(null);
try {
final messages = await _search(
filter: filter,
messageFilter: messageFilter,
sort: sort,
query: query,
pagination: pagination,
);
_messageResponses.add(messages.results);
} catch (err, stk) {
_messageResponses.addError(err, stk);
}
}
final client = StreamChatCore.of(context).client;
/// Calls [StreamChatClient.search] updating [queryMessagesLoading] stream
Future<void> loadMore({
Map<String, dynamic> filter,
Map<String, dynamic> messageFilter,
List<SortOption> sort,
String query,
PaginationParams pagination,
}) async {
if (_queryMessagesLoadingController.value == true) {
return;
if (_queryMessagesLoadingController.value == true) return;
if (_messageResponses.hasValue) {
_queryMessagesLoadingController.add(true);
}
_queryMessagesLoadingController.add(true);
try {
final clear = pagination == null ||
pagination.offset == null ||
@@ -100,12 +79,12 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
final messages = await _search(
filter: filter,
messageFilter: messageFilter,
final messages = await client.search(
filter,
sort: sort,
query: query,
pagination: pagination,
paginationParams: pagination,
messageFilters: messageFilter,
);
if (clear) {
@@ -114,30 +93,18 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
final temp = oldMessages + messages.results;
_messageResponses.add(temp);
}
_queryMessagesLoadingController.add(false);
} catch (err, stackTrace) {
_queryMessagesLoadingController.addError(err, stackTrace);
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) {
_queryMessagesLoadingController.add(false);
}
} catch (e, stk) {
if (_messageResponses.hasValue) {
_queryMessagesLoadingController.addError(e, stk);
} else {
_messageResponses.addError(e, stk);
}
}
}
Future<SearchMessagesResponse> _search({
Map<String, dynamic> filter,
Map<String, dynamic> messageFilter,
List<SortOption> sort,
String query,
PaginationParams pagination,
}) {
final client = StreamChatCore.of(context).client;
return client.search(
filter,
sort: sort,
query: query,
paginationParams: pagination,
messageFilters: messageFilter,
);
}
@override
Widget build(BuildContext context) {
super.build(context);
@@ -104,21 +104,15 @@ class MessageSearchListCore extends StatefulWidget {
final WidgetBuilder loadingBuilder;
@override
_MessageSearchListCoreState createState() => _MessageSearchListCoreState();
MessageSearchListCoreState createState() => MessageSearchListCoreState();
}
class _MessageSearchListCoreState extends State<MessageSearchListCore> {
/// The current state of the [MessageSearchListCore].
class MessageSearchListCoreState extends State<MessageSearchListCore> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
MessageSearchBloc.of(context).search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
loadData();
if (widget.messageSearchListController != null) {
widget.messageSearchListController.loadData = loadData;
widget.messageSearchListController.paginateData = paginateData;
@@ -136,29 +130,23 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
stream: messageSearchBloc.messagesStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
return widget.errorBuilder(context, snapshot.error);
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
}
final items = snapshot.data;
if (items.isEmpty) {
return widget.emptyBuilder(context);
}
return widget.childBuilder(snapshot.data);
},
);
void loadData() {
MessageSearchBloc.of(context).search(
/// Fetches initial messages and updates the widget
Future<void> loadData() {
final messageSearchBloc = MessageSearchBloc.of(context);
return messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
@@ -167,10 +155,10 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
);
}
void paginateData() {
/// Fetches more messages with updated pagination and updates the widget
Future<void> paginateData() {
final messageSearchBloc = MessageSearchBloc.of(context);
messageSearchBloc.loadMore(
return messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
@@ -186,18 +174,12 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
super.didUpdateWidget(oldWidget);
if (widget.filters?.toString() != oldWidget.filters?.toString() ||
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
widget.paginationParams?.toJson()?.toString() !=
oldWidget.paginationParams?.toJson()?.toString() ||
widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() ||
widget.messageFilters?.toString() !=
oldWidget.messageFilters?.toString()) {
MessageSearchBloc.of(context).search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
oldWidget.messageFilters?.toString() ||
widget.paginationParams?.toJson()?.toString() !=
oldWidget.paginationParams?.toJson()?.toString()) {
loadData();
}
}
}
@@ -205,8 +187,8 @@ class _MessageSearchListCoreState extends State<MessageSearchListCore> {
/// Controller used for paginating data in [ChannelListView]
class MessageSearchListController {
/// Call this function to reload data
VoidCallback loadData;
AsyncCallback loadData;
/// Call this function to load further data
VoidCallback paginateData;
AsyncCallback paginateData;
}
@@ -207,15 +207,14 @@ class StreamChannelState extends State<StreamChannel> {
int after = 20,
bool preferOffline = false,
}) =>
queryAtMessage(
_queryAtMessage(
messageId: messageId,
before: before,
after: after,
preferOffline: preferOffline,
);
///
Future<void> queryAtMessage({
Future<void> _queryAtMessage({
String messageId,
int before = 20,
int after = 20,
@@ -297,7 +296,7 @@ class StreamChannelState extends State<StreamChannel> {
}
/// Reloads the channel with latest message
Future<void> reloadChannel() => queryAtMessage(before: 30);
Future<void> reloadChannel() => _queryAtMessage(before: 30);
List<Future<bool>> _futures;
@@ -305,8 +304,7 @@ class StreamChannelState extends State<StreamChannel> {
try {
await loadChannelAtMessage(initialMessageId);
return true;
} catch (e, stk) {
print('Error: $e\nStack: $stk');
} catch (_) {
rethrow;
}
}
@@ -349,9 +347,6 @@ class StreamChannelState extends State<StreamChannel> {
],
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
@@ -361,9 +356,7 @@ class StreamChannelState extends State<StreamChannel> {
message = 'Check your connection and retry';
}
}
return Center(
child: Text(message),
);
return Center(child: Text(message));
}
final initialized = snapshot.data[0];
// ignore: avoid_bool_literals_in_conditional_expressions
@@ -93,10 +93,10 @@ class StreamChatCoreState extends State<StreamChatCore>
Widget build(BuildContext context) => widget.child;
/// The current user
User get user => widget.client.state.user;
User get user => client.state?.user;
/// The current user as a stream
Stream<User> get userStream => widget.client.state.userStream;
Stream<User> get userStream => client.state?.userStream;
@override
void initState() {
@@ -108,21 +108,25 @@ class StreamChatCoreState extends State<StreamChatCore>
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (client.state?.user != null) {
if (user != null) {
if (state == AppLifecycleState.paused) {
if (widget.onBackgroundEventReceived != null) {
_eventSubscription =
client.on().listen(widget.onBackgroundEventReceived);
_disconnectTimer = Timer(
widget.backgroundKeepAlive,
client.disconnect,
);
} else {
if (widget.onBackgroundEventReceived == null) {
client.disconnect();
return;
}
_eventSubscription = client.on().listen(
widget.onBackgroundEventReceived,
);
void onTimerComplete() {
_eventSubscription.cancel();
client.disconnect();
}
_disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete);
} else if (state == AppLifecycleState.resumed) {
_eventSubscription?.cancel();
if (_disconnectTimer?.isActive == true) {
_eventSubscription.cancel();
_disconnectTimer.cancel();
} else {
if (client.wsConnectionStatus == ConnectionStatus.disconnected) {
@@ -80,7 +80,7 @@ class UserListCore extends StatefulWidget {
final UserListController userListController;
/// The builder that will be used in case of error
final Widget Function(Error error) errorBuilder;
final Widget Function(Object error) errorBuilder;
/// The builder that will be used to build the list
final Widget Function(BuildContext context, List<ListItem> users) listBuilder;
@@ -120,21 +120,16 @@ class UserListCore extends StatefulWidget {
final bool groupAlphabetically;
@override
_UserListCoreState createState() => _UserListCoreState();
UserListCoreState createState() => UserListCoreState();
}
class _UserListCoreState extends State<UserListCore>
/// The current state of the [UserListCore].
class UserListCoreState extends State<UserListCore>
with WidgetsBindingObserver {
@override
void didChangeDependencies() {
super.didChangeDependencies();
UsersBloc.of(context).queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
loadData();
if (widget.userListController != null) {
widget.userListController.loadData = loadData;
widget.userListController.paginateData = paginateData;
@@ -144,11 +139,10 @@ class _UserListCoreState extends State<UserListCore>
@override
Widget build(BuildContext context) {
final _usersBloc = UsersBloc.of(context);
return _buildListView(_usersBloc);
}
bool get isListAlreadySorted =>
bool get _isListAlreadySorted =>
widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
Stream<List<ListItem>> _buildUserStream(
@@ -158,7 +152,7 @@ class _UserListCoreState extends State<UserListCore>
(users) {
if (widget.groupAlphabetically) {
var temp = users;
if (!isListAlreadySorted) {
if (!_isListAlreadySorted) {
temp = users
..sort((curr, next) => curr.name.compareTo(next.name));
}
@@ -186,33 +180,23 @@ class _UserListCoreState extends State<UserListCore>
stream: _buildUserStream(usersBlocState),
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
return widget.errorBuilder(snapshot.error);
}
if (!snapshot.hasData) {
return widget.loadingBuilder(context);
}
final items = snapshot.data;
if (items.isEmpty) {
return widget.emptyBuilder(context);
}
if (items.isEmpty) {
return widget.emptyBuilder(context);
}
return widget.listBuilder(context, items);
},
);
void loadData() {
UsersBloc.of(context).queryUsers(
// ignore: public_member_api_docs
Future<void> loadData() {
final _usersBloc = UsersBloc.of(context);
return _usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
@@ -220,10 +204,10 @@ class _UserListCoreState extends State<UserListCore>
);
}
void paginateData() {
// ignore: public_member_api_docs
Future<void> paginateData() {
final _usersBloc = UsersBloc.of(context);
_usersBloc.queryUsers(
return _usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination.copyWith(
@@ -238,15 +222,10 @@ class _UserListCoreState extends State<UserListCore>
super.didUpdateWidget(oldWidget);
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
widget.options?.toString() != oldWidget.options?.toString() ||
widget.pagination?.toJson()?.toString() !=
oldWidget.pagination?.toJson()?.toString() ||
widget.options?.toString() != oldWidget.options?.toString()) {
UsersBloc.of(context).queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
oldWidget.pagination?.toJson()?.toString()) {
loadData();
}
}
}
@@ -255,11 +234,11 @@ class _UserListCoreState extends State<UserListCore>
/// Header items are prefixed with the key `HEADER` While users are prefixed
/// with `USER`.
abstract class ListItem {
// ignore: public_member_api_docs
/// Unique key per list item
String get key {
if (this is ListHeaderItem) {
final header = (this as ListHeaderItem).heading;
return 'HEADER-$header';
return 'HEADER-${header.toLowerCase()}';
}
if (this is ListUserItem) {
final user = (this as ListUserItem).user;
@@ -268,7 +247,8 @@ abstract class ListItem {
return null;
}
// ignore: public_member_api_docs
/// Helper function to build widget based on ListItem type
// ignore: missing_return
Widget when({
@required Widget Function(String heading) headerItem,
@required Widget Function(User user) userItem,
@@ -279,33 +259,32 @@ abstract class ListItem {
if (this is ListUserItem) {
return userItem((this as ListUserItem).user);
}
return const SizedBox();
}
}
// ignore: public_member_api_docs
/// Header Item
class ListHeaderItem extends ListItem {
// ignore: public_member_api_docs
/// Constructs a new [ListHeaderItem]
ListHeaderItem(this.heading);
// ignore: public_member_api_docs
/// Heading used to build the item.
final String heading;
}
// ignore: public_member_api_docs
/// User Item
class ListUserItem extends ListItem {
// ignore: public_member_api_docs
/// Constructs a new [ListUserItem]
ListUserItem(this.user);
// ignore: public_member_api_docs
/// [User] used to build the item.
final User user;
}
/// Controller used for paginating data in [ChannelListView]
class UserListController {
/// Call this function to reload data
VoidCallback loadData;
AsyncCallback loadData;
/// Call this function to load further data
VoidCallback paginateData;
AsyncCallback paginateData;
}
@@ -51,10 +51,9 @@ class UsersBlocState extends State<UsersBloc>
/// The current users list as a stream
Stream<List<User>> get usersStream => _usersController.stream;
final BehaviorSubject<List<User>> _usersController = BehaviorSubject();
final _usersController = BehaviorSubject<List<User>>();
final BehaviorSubject<bool> _queryUsersLoadingController =
BehaviorSubject.seeded(false);
final _queryUsersLoadingController = BehaviorSubject.seeded(false);
/// The stream notifying the state of queryUsers call
Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream;
@@ -70,11 +69,12 @@ class UsersBlocState extends State<UsersBloc>
}) async {
final client = StreamChatCore.of(context).client;
if (client.state?.user == null ||
_queryUsersLoadingController.value == true) {
return;
if (_queryUsersLoadingController.value == true) return;
if (_usersController.hasValue) {
_queryUsersLoadingController.add(true);
}
_queryUsersLoadingController.add(true);
try {
final clear = pagination == null ||
pagination.offset == null ||
@@ -95,10 +95,15 @@ class UsersBlocState extends State<UsersBloc>
final temp = oldUsers + usersResponse.users;
_usersController.add(temp);
}
_queryUsersLoadingController.add(false);
} catch (err, stackTrace) {
_queryUsersLoadingController.addError(err, stackTrace);
if (_usersController.hasValue && _queryUsersLoadingController.value) {
_queryUsersLoadingController.add(false);
}
} catch (e, stk) {
if (_usersController.hasValue) {
_queryUsersLoadingController.addError(e, stk);
} else {
_usersController.addError(e, stk);
}
}
}
@@ -2,14 +2,14 @@ library stream_chat_flutter_core;
export 'package:stream_chat/stream_chat.dart';
export 'src/channel_list_core.dart';
export 'src/channel_list_core.dart' hide ChannelListCoreState;
export 'src/channels_bloc.dart';
export 'src/lazy_load_scroll_view.dart';
export 'src/message_list_core.dart';
export 'src/message_list_core.dart' hide MessageListCoreState;
export 'src/message_search_bloc.dart';
export 'src/message_search_list_core.dart';
export 'src/message_search_list_core.dart' hide MessageSearchListCoreState;
export 'src/stream_channel.dart';
export 'src/stream_chat_core.dart';
export 'src/typedef.dart';
export 'src/user_list_core.dart';
export 'src/user_list_core.dart' hide UserListCoreState;
export 'src/users_bloc.dart';