merge origin/develop into ref/segregate-api-layer

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2021-06-15 17:32:09 +05:30
62 changed files with 2287 additions and 1620 deletions
@@ -1,3 +1,10 @@
## 2.0.0-nullsafety.5
* Update llc dependency
* Minor fixes and improvements
* Performance improvements
* Monitor connection using `connectivity_plus` package
## 2.0.0-nullsafety.3
* Update llc dependency
@@ -0,0 +1,109 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
/// A more efficient [StreamBuilder]
/// It requires [initialData] and will rebuild
/// only when the new data is different than the current data
/// The [comparator] is used to check if the new data is different
class BetterStreamBuilder<T> extends StatefulWidget {
/// Creates a new BetterStreamBuilder
const BetterStreamBuilder({
required this.stream,
required this.initialData,
required this.builder,
this.loadingBuilder,
this.errorBuilder,
this.comparator,
Key? key,
}) : super(key: key);
/// The stream to listen to
final Stream<T>? stream;
/// The initial data available
final T initialData;
/// Comparator used to check if the new data is different than the last one
final bool Function(T?, T)? comparator;
/// Builder that builds based on the new snapshot
final Widget Function(BuildContext context, T data) builder;
/// Builder that builds when the data is null
final Widget Function(BuildContext context)? loadingBuilder;
/// Builder used when there is an error
final Widget Function(BuildContext context, Object error)? errorBuilder;
@override
_BetterStreamBuilderState createState() => _BetterStreamBuilderState<T>();
}
class _BetterStreamBuilderState<T> extends State<BetterStreamBuilder<T>> {
T? _lastEvent;
StreamSubscription? _subscription;
Object? _lastError;
@override
Widget build(BuildContext context) {
if (_lastError != null) {
return widget.errorBuilder!(context, _lastError!);
}
if (_lastEvent == null) {
return widget.loadingBuilder?.call(context) ?? const Offstage();
}
return widget.builder(context, _lastEvent ?? widget.initialData);
}
@override
void initState() {
_lastEvent = widget.initialData;
_subscription = widget.stream?.listen(
_onEvent,
onError: _onError,
);
super.initState();
}
@override
void didUpdateWidget(covariant BetterStreamBuilder<T> oldWidget) {
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) {
if (mounted) {
setState(() {});
}
_lastError = error;
}
}
void _onEvent(T event) {
_lastError = null;
final isEqual = widget.comparator != null
? widget.comparator!(_lastEvent, event)
: event == _lastEvent;
if (!isEqual) {
if (mounted) {
setState(() {});
}
_lastEvent = event;
}
}
}
@@ -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<MessageListCore> {
.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,28 +140,27 @@ class MessageListCoreState extends State<MessageListCore> {
return true;
}
return StreamBuilder<List<Message>?>(
stream: messagesStream?.map((messages) =>
messages?.where(widget.messageFilter ?? defaultFilter).toList(
growable: false,
)),
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;
return BetterStreamBuilder<List<Message>?>(
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, data) {
final messageList = data?.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);
},
);
}
@@ -3,6 +3,7 @@ library stream_chat_flutter_core;
export 'package:connectivity_plus/connectivity_plus.dart';
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';
@@ -1,7 +1,7 @@
name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 2.0.0-nullsafety.3
version: 2.0.0-nullsafety.5
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -16,7 +16,7 @@ dependencies:
sdk: flutter
meta: ^1.3.0
rxdart: ^0.27.0
stream_chat: ^2.0.0-nullsafety.2
stream_chat: ^2.0.0-nullsafety.5
dependency_overrides:
stream_chat:
@@ -100,6 +100,7 @@ void main() {
when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value([]));
when(() => mockChannel.state.messages).thenReturn([]);
await tester.pumpWidget(
StreamChannel(
@@ -133,6 +134,7 @@ void main() {
when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value([]));
when(() => mockChannel.state.messages).thenReturn([]);
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
await tester.pumpWidget(
@@ -174,6 +176,7 @@ void main() {
when(() => mockChannel.state.messages).thenReturn(messages);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages));
when(() => mockChannel.state.messages).thenReturn(messages);
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
await tester.pumpWidget(
@@ -220,6 +223,7 @@ void main() {
const error = 'Error! Error! Error!';
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.error(error));
when(() => mockChannel.state.messages).thenReturn([]);
await tester.pumpWidget(
Directionality(
@@ -259,6 +263,7 @@ void main() {
const messages = <Message>[];
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages));
when(() => mockChannel.state.messages).thenReturn(messages);
await tester.pumpWidget(
Directionality(
@@ -305,6 +310,7 @@ void main() {
const messages = <Message>[];
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages));
when(() => mockChannel.state.messages).thenReturn(messages);
await tester.pumpWidget(
Directionality(
@@ -349,6 +355,7 @@ void main() {
final messages = _generateMessages();
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages));
when(() => mockChannel.state.messages).thenReturn(messages);
await tester.pumpWidget(
Directionality(
@@ -4,6 +4,10 @@ import 'package:stream_chat/stream_chat.dart';
class MockLogger extends Mock implements Logger {}
class MockClient extends Mock implements StreamChatClient {
MockClient() {
when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected);
}
@override
final Logger logger = MockLogger();
@@ -235,6 +235,14 @@ void main() {
final mockClient = MockClient();
const streamChatCoreKey = Key('streamChatCore');
const childKey = Key('child');
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(() => mockClient.disconnect()).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore(
key: streamChatCoreKey,
client: mockClient,
@@ -247,13 +255,6 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(mockClient.disconnect).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
final streamChatCoreState = tester.state<StreamChatCoreState>(
find.byKey(streamChatCoreKey),
);
@@ -323,6 +324,14 @@ void main() {
const childKey = Key('child');
final _connectivityController =
BehaviorSubject.seeded(ConnectivityResult.none);
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(() => mockClient.disconnect()).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore(
key: streamChatCoreKey,
client: mockClient,
@@ -335,13 +344,6 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(mockClient.disconnect).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
_connectivityController.add(ConnectivityResult.mobile);
await Future.delayed(const Duration(seconds: 1));
@@ -397,6 +399,14 @@ void main() {
const childKey = Key('child');
final _connectivityController =
BehaviorSubject.seeded(ConnectivityResult.none);
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(() => mockClient.disconnect()).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore(
key: streamChatCoreKey,
client: mockClient,
@@ -409,13 +419,6 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget);
final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(() => mockClient.connect()).thenAnswer((_) async => event);
when(mockClient.disconnect).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected);
final streamChatCoreState = tester.state<StreamChatCoreState>(
find.byKey(streamChatCoreKey),
);