fix(core): Replaced all StreamController.add() with .safeAdd() to fix bad state errors.

Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
Sahil Kumar
2022-01-24 16:22:28 +05:30
committed by xsahil03x
parent c4ea134af7
commit bb3f48d42f
6 changed files with 58 additions and 39 deletions
@@ -3,6 +3,7 @@
🐞 Fixed 🐞 Fixed
- Do not move a channel to top if the new message is from a thread. - Do not move a channel to top if the new message is from a thread.
- [[#848]](https://github.com/GetStream/stream-chat-flutter/issues/848) Fixed "Bad state: Cannot add new events after calling close" by replacing all `.add` methods with a new `.safeAdd`.
## 3.3.1 ## 3.3.1
@@ -1,11 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/src/channel_list_core.dart'; import 'package:stream_chat_flutter_core/src/channel_list_core.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/stream_controller_extension.dart';
/// Widget dedicated to the management of a channel list with pagination /// Widget dedicated to the management of a channel list with pagination
/// [ChannelsBloc] is used together with [ChannelListCore] to manage a list of /// [ChannelsBloc] is used together with [ChannelListCore] to manage a list of
@@ -116,7 +116,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
} }
if (_channelsController.hasValue) { if (_channelsController.hasValue) {
_queryChannelsLoadingController.add(true); _queryChannelsLoadingController.safeAdd(true);
} }
try { try {
@@ -135,14 +135,14 @@ class ChannelsBlocState extends State<ChannelsBloc>
)) { )) {
newChannels = channels; newChannels = channels;
if (clear) { if (clear) {
_channelsController.add(channels); _channelsController.safeAdd(channels);
} else { } else {
final temp = oldChannels + channels; final temp = oldChannels + channels;
_channelsController.add(temp); _channelsController.safeAdd(temp);
} }
if (_channelsController.hasValue && if (_channelsController.hasValue &&
_queryChannelsLoadingController.value) { _queryChannelsLoadingController.value) {
_queryChannelsLoadingController.sink.add(false); _queryChannelsLoadingController.safeAdd(false);
} }
} }
if (newChannels.isEmpty || newChannels.length < paginationParams.limit) { if (newChannels.isEmpty || newChannels.length < paginationParams.limit) {
@@ -150,11 +150,11 @@ class ChannelsBlocState extends State<ChannelsBloc>
} }
} catch (e, stk) { } catch (e, stk) {
// reset loading controller // reset loading controller
_queryChannelsLoadingController.sink.add(false); _queryChannelsLoadingController.safeAdd(false);
if (_channelsController.hasValue) { if (_channelsController.hasValue) {
_queryChannelsLoadingController.addError(e, stk); _queryChannelsLoadingController.safeAddError(e, stk);
} else { } else {
_channelsController.addError(e, stk); _channelsController.safeAddError(e, stk);
} }
} }
} }
@@ -200,7 +200,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
if (widget.channelsComparator != null) { if (widget.channelsComparator != null) {
newChannels.sort(widget.channelsComparator); newChannels.sort(widget.channelsComparator);
} }
_channelsController.add(newChannels); _channelsController.safeAdd(newChannels);
})); }));
} }
@@ -212,7 +212,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
if (channelIndex > -1) { if (channelIndex > -1) {
final channel = newChannels.removeAt(channelIndex); final channel = newChannels.removeAt(channelIndex);
_hiddenChannels.add(channel); _hiddenChannels.add(channel);
_channelsController.add(newChannels); _channelsController.safeAdd(newChannels);
} }
})) }))
..add(client ..add(client
@@ -222,7 +222,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
) )
.listen((e) { .listen((e) {
final channel = e.channel; final channel = e.channel;
_channelsController.add(List.from( _channelsController.safeAdd(List.from(
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid), (channels ?? [])..removeWhere((c) => c.cid == channel?.cid),
)); ));
})); }));
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.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/stream_controller_extension.dart';
/// [MessageSearchBloc] is used to manage a list of messages with pagination. /// [MessageSearchBloc] is used to manage a list of messages with pagination.
/// This class can be used to load messages, perform queries, etc. /// This class can be used to load messages, perform queries, etc.
@@ -95,7 +96,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
} }
if (_messageResponses.hasValue) { if (_messageResponses.hasValue) {
_queryMessagesLoadingController.add(true); _queryMessagesLoadingController.safeAdd(true);
} }
try { try {
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []); final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
@@ -120,24 +121,24 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
final newMessages = response.results; final newMessages = response.results;
if (clear) { if (clear) {
_messageResponses.add(newMessages); _messageResponses.safeAdd(newMessages);
} else { } else {
final temp = oldMessages + newMessages; final temp = oldMessages + newMessages;
_messageResponses.add(temp); _messageResponses.safeAdd(temp);
} }
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { if (_messageResponses.hasValue && _queryMessagesLoadingController.value) {
_queryMessagesLoadingController.add(false); _queryMessagesLoadingController.safeAdd(false);
} }
if (newMessages.isEmpty || newMessages.length < pagination.limit) { if (newMessages.isEmpty || newMessages.length < pagination.limit) {
_paginationEnded = true; _paginationEnded = true;
} }
} catch (e, stk) { } catch (e, stk) {
// reset loading controller // reset loading controller
_queryMessagesLoadingController.add(false); _queryMessagesLoadingController.safeAdd(false);
if (_messageResponses.hasValue) { if (_messageResponses.hasValue) {
_queryMessagesLoadingController.addError(e, stk); _queryMessagesLoadingController.safeAddError(e, stk);
} else { } else {
_messageResponses.addError(e, stk); _messageResponses.safeAddError(e, stk);
} }
} }
} }
@@ -1,10 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/src/stream_controller_extension.dart';
/// Specifies query direction for pagination /// Specifies query direction for pagination
enum QueryDirection { enum QueryDirection {
@@ -92,10 +92,10 @@ class StreamChannelState extends State<StreamChannel> {
channel.state == null) { channel.state == null) {
return; return;
} }
_queryTopMessagesController.add(true); _queryTopMessagesController.safeAdd(true);
if (channel.state!.messages.isEmpty) { if (channel.state!.messages.isEmpty) {
return _queryTopMessagesController.add(false); return _queryTopMessagesController.safeAdd(false);
} }
final oldestMessage = channel.state!.messages.first; final oldestMessage = channel.state!.messages.first;
@@ -109,9 +109,9 @@ class StreamChannelState extends State<StreamChannel> {
if (state.messages.isEmpty || state.messages.length < limit) { if (state.messages.isEmpty || state.messages.length < limit) {
_topPaginationEnded = true; _topPaginationEnded = true;
} }
_queryTopMessagesController.add(false); _queryTopMessagesController.safeAdd(false);
} catch (e, stk) { } catch (e, stk) {
_queryTopMessagesController.addError(e, stk); _queryTopMessagesController.safeAddError(e, stk);
} }
} }
@@ -123,10 +123,10 @@ class StreamChannelState extends State<StreamChannel> {
_queryBottomMessagesController.value || _queryBottomMessagesController.value ||
channel.state == null || channel.state == null ||
channel.state!.isUpToDate) return; channel.state!.isUpToDate) return;
_queryBottomMessagesController.add(true); _queryBottomMessagesController.safeAdd(true);
if (channel.state!.messages.isEmpty) { if (channel.state!.messages.isEmpty) {
return _queryBottomMessagesController.add(false); return _queryBottomMessagesController.safeAdd(false);
} }
final recentMessage = channel.state!.messages.last; final recentMessage = channel.state!.messages.last;
@@ -140,9 +140,9 @@ class StreamChannelState extends State<StreamChannel> {
if (state.messages.isEmpty || state.messages.length < limit) { if (state.messages.isEmpty || state.messages.length < limit) {
_bottomPaginationEnded = true; _bottomPaginationEnded = true;
} }
_queryBottomMessagesController.add(false); _queryBottomMessagesController.safeAdd(false);
} catch (e, stk) { } catch (e, stk) {
_queryBottomMessagesController.addError(e, stk); _queryBottomMessagesController.safeAddError(e, stk);
} }
} }
@@ -166,7 +166,7 @@ class StreamChannelState extends State<StreamChannel> {
if (_topPaginationEnded || if (_topPaginationEnded ||
_queryTopMessagesController.value || _queryTopMessagesController.value ||
channel.state == null) return; channel.state == null) return;
_queryTopMessagesController.add(true); _queryTopMessagesController.safeAdd(true);
Message? message; Message? message;
if (channel.state!.threads.containsKey(parentId)) { if (channel.state!.threads.containsKey(parentId)) {
@@ -188,9 +188,9 @@ class StreamChannelState extends State<StreamChannel> {
if (response.messages.isEmpty || response.messages.length < limit) { if (response.messages.isEmpty || response.messages.length < limit) {
_topPaginationEnded = true; _topPaginationEnded = true;
} }
_queryTopMessagesController.add(false); _queryTopMessagesController.safeAdd(false);
} catch (e, stk) { } catch (e, stk) {
_queryTopMessagesController.addError(e, stk); _queryTopMessagesController.safeAddError(e, stk);
} }
} }
@@ -0,0 +1,18 @@
import 'dart:async';
/// Extension on [StreamController] to safely add events and errors.
extension StreamControllerX<T> on StreamController<T> {
/// Safely adds the event to the controller,
/// Returns early if the controller is closed.
void safeAdd(T event) {
if (isClosed) return;
add(event);
}
/// Safely adds the error to the controller,
/// Returns early if the controller is closed.
void safeAddError(Object error, [StackTrace? stackTrace]) {
if (isClosed) return;
addError(error, stackTrace);
}
}
@@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/stream_controller_extension.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget dedicated to the management of a users list with pagination. /// Widget dedicated to the management of a users list with pagination.
@@ -82,7 +81,7 @@ class UsersBlocState extends State<UsersBloc>
} }
if (_usersController.hasValue) { if (_usersController.hasValue) {
_queryUsersLoadingController.add(true); _queryUsersLoadingController.safeAdd(true);
} }
try { try {
@@ -97,24 +96,24 @@ class UsersBlocState extends State<UsersBloc>
final newUsers = usersResponse.users; final newUsers = usersResponse.users;
if (clear) { if (clear) {
_usersController.add(usersResponse.users); _usersController.safeAdd(usersResponse.users);
} else { } else {
final temp = oldUsers + usersResponse.users; final temp = oldUsers + usersResponse.users;
_usersController.add(temp); _usersController.safeAdd(temp);
} }
if (_usersController.hasValue && _queryUsersLoadingController.value) { if (_usersController.hasValue && _queryUsersLoadingController.value) {
_queryUsersLoadingController.add(false); _queryUsersLoadingController.safeAdd(false);
} }
if (newUsers.isEmpty || newUsers.length < pagination.limit) { if (newUsers.isEmpty || newUsers.length < pagination.limit) {
_paginationEnded = true; _paginationEnded = true;
} }
} catch (e, stk) { } catch (e, stk) {
// reset loading controller // reset loading controller
_queryUsersLoadingController.add(false); _queryUsersLoadingController.safeAdd(false);
if (_usersController.hasValue) { if (_usersController.hasValue) {
_queryUsersLoadingController.addError(e, stk); _queryUsersLoadingController.safeAddError(e, stk);
} else { } else {
_usersController.addError(e, stk); _usersController.safeAddError(e, stk);
} }
} }
} }