Merge remote-tracking branch 'origin/develop' into fix/channel-image

# Conflicts:
#	packages/stream_chat_flutter/test/src/channel_image_test.dart
This commit is contained in:
xsahil03x
2021-07-16 14:46:29 +05:30
47 changed files with 1163 additions and 1051 deletions
-2
View File
@@ -1,10 +1,8 @@
analyzer: analyzer:
exclude: exclude:
- packages/*/lib/**/*.g.dart - packages/*/lib/**/*.g.dart
- packages/*/example/**
- packages/*/lib/src/emoji - packages/*/lib/src/emoji
- packages/*/lib/**/*.freezed.dart - packages/*/lib/**/*.freezed.dart
- packages/*/test/**
linter: linter:
rules: rules:
+76 -80
View File
@@ -13,7 +13,7 @@ Future<void> main() async {
await client.connectUser( await client.connectUser(
User( User(
id: 'cool-shadow-7', id: 'cool-shadow-7',
extraData: { extraData: const {
'image': 'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow', 'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
}, },
@@ -57,12 +57,10 @@ class StreamExample extends StatelessWidget {
final Channel channel; final Channel channel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( title: 'Stream Chat Dart Example',
title: 'Stream Chat Dart Example', home: HomeScreen(channel: channel),
home: HomeScreen(channel: channel), );
);
}
} }
/// Main screen of our application. The layout is comprised of an [AppBar] /// Main screen of our application. The layout is comprised of an [AppBar]
@@ -167,83 +165,81 @@ class _MessageViewState extends State<MessageView> {
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Column(
return Column( children: [
children: [ Expanded(
Expanded( child: ListView.builder(
child: ListView.builder( controller: _scrollController,
controller: _scrollController, itemCount: _messages.length,
itemCount: _messages.length, reverse: true,
reverse: true, itemBuilder: (BuildContext context, int index) {
itemBuilder: (BuildContext context, int index) { final item = _messages[index];
final item = _messages[index]; if (item.user?.id == widget.channel.client.uid) {
if (item.user?.id == widget.channel.client.uid) { return Align(
return Align( alignment: Alignment.centerRight,
alignment: Alignment.centerRight, child: Padding(
child: Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Text(item.text ?? ''),
child: Text(item.text ?? ''), ),
), );
); } else {
} else { return Align(
return Align( alignment: Alignment.centerLeft,
alignment: Alignment.centerLeft, child: Padding(
child: Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Text(item.text ?? ''),
child: Text(item.text ?? ''), ),
), );
); }
} },
}, ),
), ),
), Padding(
Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Row(
child: Row( children: [
children: [ Expanded(
Expanded( child: TextField(
child: TextField( controller: _controller,
controller: _controller, decoration: const InputDecoration(
decoration: const InputDecoration( hintText: 'Enter your message',
hintText: 'Enter your message',
),
),
),
Material(
type: MaterialType.circle,
color: Colors.blue,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
// We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
), ),
), ),
), ),
) Material(
], type: MaterialType.circle,
), color: Colors.blue,
) clipBehavior: Clip.hardEdge,
], child: InkWell(
); onTap: () async {
} // We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
),
)
],
),
)
],
);
} }
/// Helper extension for quickly retrieving /// Helper extension for quickly retrieving
@@ -555,7 +555,7 @@ void main() {
}); });
test( test(
'should directly update the state with message as deleted if the state is sending or failed', '''should directly update the state with message as deleted if the state is sending or failed''',
() async { () async {
const messageId = 'test-message-id'; const messageId = 'test-message-id';
final message = Message( final message = Message(
@@ -981,7 +981,7 @@ void main() {
); );
test( test(
'should override previous reaction if present and `enforceUnique` is true', '''should override previous reaction if present and `enforceUnique` is true''',
() async { () async {
const userId = 'test-user-id'; const userId = 'test-user-id';
const messageId = 'test-message-id'; const messageId = 'test-message-id';
@@ -1875,7 +1875,7 @@ void main() {
}); });
test( test(
'should send `typingStart` event if there is not already a typingEvent or the difference between the two is >= 2 seconds', '''should send `typingStart` event if there is not already a typingEvent or the difference between the two is >= 2 seconds''',
() async { () async {
final typingEvent = Event(type: EventType.typingStart); final typingEvent = Event(type: EventType.typingStart);
@@ -47,12 +47,6 @@ void main() {
final user = User(id: 'test-user-id'); final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue; final token = Token.development(user.id).rawValue;
final event = Event(
type: EventType.healthCheck,
connectionId: 'fake-connection-id',
me: OwnUser.fromUser(user),
);
expectLater( expectLater(
// skipping first seed status -> ConnectionStatus.disconnected // skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1), client.wsConnectionStatusStream.skip(1),
@@ -74,12 +68,6 @@ void main() {
return Token.development(userId).rawValue; return Token.development(userId).rawValue;
} }
final event = Event(
type: EventType.healthCheck,
connectionId: 'fake-connection-id',
me: OwnUser.fromUser(user),
);
expectLater( expectLater(
// skipping first seed status -> ConnectionStatus.disconnected // skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1), client.wsConnectionStatusStream.skip(1),
@@ -106,12 +94,6 @@ void main() {
..accessToken = token, ..accessToken = token,
); );
final event = Event(
type: EventType.healthCheck,
connectionId: 'fake-connection-id',
me: OwnUser.fromUser(user),
);
expectLater( expectLater(
// skipping first seed status -> ConnectionStatus.disconnected // skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1), client.wsConnectionStatusStream.skip(1),
@@ -431,7 +413,7 @@ void main() {
}); });
test( test(
'`.connectUser` should connect successfully if persistence contains event', '''`.connectUser` should connect successfully if persistence contains event''',
() async { () async {
final user = User(id: 'test-user-id'); final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue; final token = Token.development(user.id).rawValue;
@@ -452,7 +434,7 @@ void main() {
); );
test( test(
'`.connectUserWithProvider` should connect successfully if persistence contains event', '''`.connectUserWithProvider` should connect successfully if persistence contains event''',
() async { () async {
final user = User(id: 'test-user-id'); final user = User(id: 'test-user-id');
Future<String> tokenProvider(String userId) async { Future<String> tokenProvider(String userId) async {
@@ -476,7 +458,7 @@ void main() {
); );
test( test(
'`.connectGuestUser` should connect successfully if persistence contains event', '''`.connectGuestUser` should connect successfully if persistence contains event''',
() async { () async {
final user = User(id: 'test-user-id'); final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue; final token = Token.development(user.id).rawValue;
@@ -507,7 +489,7 @@ void main() {
); );
test( test(
'`.connectAnonymousUser` should connect successfully if persistence contains event', '''`.connectAnonymousUser` should connect successfully if persistence contains event''',
() async { () async {
final user = User(id: 'test-user-id'); final user = User(id: 'test-user-id');
@@ -561,7 +543,7 @@ void main() {
group('`.sync`', () { group('`.sync`', () {
test( test(
'should update persistence connectionInfo and lastSync when sync succeeds', '''should update persistence connectionInfo and lastSync when sync succeeds''',
() async { () async {
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now(); final lastSyncAt = DateTime.now();
@@ -735,7 +717,7 @@ void main() {
); );
test( test(
'should never rethrow network call if persistence already emitted some channels', '''should never rethrow network call if persistence already emitted some channels''',
() async { () async {
final persistentChannelStates = List.generate( final persistentChannelStates = List.generate(
3, 3,
@@ -943,7 +925,7 @@ void main() {
}); });
test( test(
'should rethrow if `.queryChannelsOnline` throws and persistence channels are empty', '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''',
() async { () async {
when(() => api.channel.queryChannels( when(() => api.channel.queryChannels(
filter: any(named: 'filter'), filter: any(named: 'filter'),
@@ -57,7 +57,7 @@ void main() {
}); });
test( test(
'connectionIdInterceptor should be added if connectionIdManager is provided', '''connectionIdInterceptor should be added if connectionIdManager is provided''',
() { () {
const apiKey = 'api-key'; const apiKey = 'api-key';
final client = StreamHttpClient( final client = StreamHttpClient(
@@ -45,7 +45,7 @@ void main() {
}); });
test( test(
'`setTokenOrProvider` should throw if both token and provider is not provided', '''`setTokenOrProvider` should throw if both token and provider is not provided''',
() async { () async {
expect(tokenManager.userId, isNull); expect(tokenManager.userId, isNull);
@@ -192,7 +192,7 @@ void main() {
test('custom with no operator', () { test('custom with no operator', () {
const key = 'testKey'; const key = 'testKey';
const values = ['testValue']; const values = ['testValue'];
final filter = Filter.custom(key: key, value: values); const filter = Filter.custom(key: key, value: values);
final encoded = json.encode(filter); final encoded = json.encode(filter);
expect( expect(
encoded, encoded,
@@ -13,7 +13,7 @@ void main() {
expect(reaction.type, 'wow'); expect(reaction.type, 'wow');
expect( expect(
reaction.user?.toJson(), reaction.user?.toJson(),
User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const {
'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'image': 'https://randomuser.me/api/portraits/women/45.jpg',
'name': 'Daisy Morgan' 'name': 'Daisy Morgan'
}).toJson(), }).toJson(),
@@ -28,7 +28,8 @@ void main() {
messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04', messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04',
createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'), createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'),
type: 'wow', type: 'wow',
user: User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: { user:
User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const {
'image': 'https://randomuser.me/api/portraits/women/45.jpg', 'image': 'https://randomuser.me/api/portraits/women/45.jpg',
'name': 'Daisy Morgan' 'name': 'Daisy Morgan'
}), }),
@@ -154,7 +154,6 @@ void main() {
}); });
test('updateChannelState', () async { test('updateChannelState', () async {
const cid = 'test:cid';
final channelState = ChannelState(); final channelState = ChannelState();
persistenceClient.updateChannelState(channelState); persistenceClient.updateChannelState(channelState);
}); });
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart';
/// A chat-persisted StreamChatClient
final chatPersistentClient = StreamChatPersistenceClient( final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO, logLevel: Level.INFO,
); );
@@ -9,73 +10,84 @@ final chatPersistentClient = StreamChatPersistenceClient(
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
/// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// Create a new instance of [StreamChatClient] passing the apikey obtained
/// project dashboard. /// from your project dashboard.
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient; )..chatPersistenceClient = chatPersistentClient;
/// Set the current user and connect the websocket. In a production scenario, this should be done using /// Set the current user and connect the websocket. In a production
/// a backend to generate a user token using our server SDK. /// scenario, this should be done using a backend to generate a user token
/// using our server SDK.
///
/// Please see the following for more information: /// Please see the following for more information:
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
); );
final channel = client.channel('messaging', id: 'godevs'); final channel = client.channel('messaging', id: 'godevs');
await channel.watch(); await channel.watch();
runApp(MyApp(client, channel)); runApp(
MyApp(
client: client,
channel: channel,
),
);
} }
/// Example application using Stream Chat Flutter widgets. /// Example application using Stream Chat Flutter widgets.
/// Stream Chat Flutter is a set of Flutter widgets which provide full chat functionalities ///
/// for building Flutter applications using Stream. /// Stream Chat Flutter is a set of Flutter widgets which provide full chat
/// If you'd prefer using minimal wrapper widgets for your app, please see our other /// functionalities for building Flutter applications using Stream. If you'd
/// prefer using minimal wrapper widgets for your app, please see our other
/// package, `stream_chat_flutter_core`. /// package, `stream_chat_flutter_core`.
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
/// Example using Stream's Flutter package.
///
/// If you'd prefer using minimal wrapper widgets for your app, please see
/// our other package, `stream_chat_flutter_core`.
const MyApp({
Key? key,
required this.client,
required this.channel,
}) : super(key: key);
/// Instance of Stream Client. /// Instance of Stream Client.
/// Stream's [StreamChatClient] can be used to connect to our servers and set the default ///
/// user for the application. Performing these actions trigger a websocket connection /// Stream's [StreamChatClient] can be used to connect to our servers and
/// allowing for real-time updates. /// set the default user for the application. Performing these actions
/// trigger a websocket connection allowing for real-time updates.
final StreamChatClient client; final StreamChatClient client;
/// Instance of the Channel /// Instance of the Channel
final Channel channel; final Channel channel;
/// Example using Stream's Flutter package.
/// If you'd prefer using minimal wrapper widgets for your app, please see our other
/// package, `stream_chat_flutter_core`.
MyApp(this.client, this.channel);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( theme: ThemeData.light(),
theme: ThemeData.light(), darkTheme: ThemeData.dark(),
darkTheme: ThemeData.dark(), builder: (context, widget) => StreamChat(
themeMode: ThemeMode.system,
builder: (context, widget) {
return StreamChat(
client: client, client: client,
child: widget, child: widget,
); ),
}, home: StreamChannel(
home: StreamChannel( channel: channel,
channel: channel, child: const ChannelPage(),
child: ChannelPage(), ),
), );
);
}
} }
/// A list of messages sent in the current channel. /// A list of messages sent in the current channel.
/// ///
/// This is implemented using [MessageListView], a widget that provides query functionalities /// This is implemented using [MessageListView], a widget that provides query
/// fetching the messages from the api and showing them in a listView /// functionalities fetching the messages from the api and showing them in a
/// listView.
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
/// Creates the page that shows the list of messages /// Creates the page that shows the list of messages
const ChannelPage({ const ChannelPage({
@@ -83,17 +95,15 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Scaffold(
return Scaffold( appBar: const ChannelHeader(),
appBar: ChannelHeader(), body: Column(
body: Column( children: const <Widget>[
children: <Widget>[ Expanded(
Expanded( child: MessageListView(),
child: MessageListView(), ),
), MessageInput(),
MessageInput(), ],
], ),
), );
);
}
} }
@@ -10,30 +10,39 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
MyApp(this.client);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( builder: (context, child) => StreamChat(
builder: (context, child) => StreamChat( client: client,
client: client, child: child,
child: child, ),
), home: const SplitView(),
home: SplitView(), );
);
}
} }
class SplitView extends StatefulWidget { class SplitView extends StatefulWidget {
const SplitView({
Key? key,
}) : super(key: key);
@override @override
_SplitViewState createState() => _SplitViewState(); _SplitViewState createState() => _SplitViewState();
} }
@@ -42,69 +51,67 @@ class _SplitViewState extends State<SplitView> {
Channel? selectedChannel; Channel? selectedChannel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Flex(
return Flex( direction: Axis.horizontal,
direction: Axis.horizontal, children: <Widget>[
children: <Widget>[ Flexible(
Flexible( child: ChannelListPage(
flex: 1, onTap: (channel) {
child: ChannelListPage( setState(() {
onTap: (channel) { selectedChannel = channel;
setState(() { });
selectedChannel = channel; },
}); ),
},
), ),
), Flexible(
Flexible( flex: 2,
flex: 2, child: Scaffold(
child: Scaffold( body: selectedChannel != null
body: selectedChannel != null ? StreamChannel(
? StreamChannel( key: ValueKey(selectedChannel!.cid),
key: ValueKey(selectedChannel!.cid), channel: selectedChannel!,
channel: selectedChannel!, child: const ChannelPage(),
child: ChannelPage(), )
) : Center(
: Center( child: Text(
child: Text( 'Pick a channel to show the messages 💬',
'Pick a channel to show the messages 💬', style: Theme.of(context).textTheme.headline5,
style: Theme.of(context).textTheme.headline5, ),
), ),
), ),
), ),
), ],
], );
);
}
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
this.onTap,
}) : super(key: key);
final void Function(Channel)? onTap; final void Function(Channel)? onTap;
ChannelListPage({this.onTap});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Scaffold(
return Scaffold( body: ChannelsBloc(
body: ChannelsBloc( child: ChannelListView(
child: ChannelListView( onChannelTap: onTap != null
onChannelTap: onTap != null ? (channel, _) {
? (channel, _) { onTap!(channel);
onTap!(channel); }
} : null,
: null, filter: Filter.in_(
filter: Filter.in_( 'members',
'members', [StreamChat.of(context).user!.id],
[StreamChat.of(context).user!.id], ),
), sort: const [SortOption('last_message_at')],
sort: [SortOption('last_message_at')], pagination: const PaginationParams(
pagination: PaginationParams( limit: 20,
limit: 20, ),
), ),
), ),
), );
);
}
} }
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
@@ -113,27 +120,21 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Navigator(
return Navigator( onGenerateRoute: (settings) => MaterialPageRoute(
onGenerateRoute: (settings) { builder: (context) => Scaffold(
return MaterialPageRoute( appBar: const ChannelHeader(
builder: (context) { showBackButton: false,
return Scaffold( ),
appBar: ChannelHeader( body: Column(
showBackButton: false, children: const <Widget>[
), Expanded(
body: Column( child: MessageListView(),
children: <Widget>[ ),
Expanded( MessageInput(),
child: MessageListView(), ],
), ),
MessageInput(), ),
], ),
), );
);
},
);
},
);
}
} }
@@ -4,25 +4,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// First step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// First step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// There are three important things to notice that are common to all Flutter application using StreamChat: /// There are three important things to notice that are common to all Flutter
/// application using StreamChat:
/// ///
/// 1. The Dart API [StreamChatClient] is initialized with your API Key /// 1. The Dart API [StreamChatClient] is initialized with your API Key
/// 2. The current user is set by calling [StreamChatClient.connectUser] /// 2. The current user is set by calling [StreamChatClient.connectUser]
/// 3. The client is then passed to the top-level [StreamChat] widget /// 3. The client is then passed to the top-level [StreamChat] widget
/// [StreamChat] is an inherited widget and must be the parent of all Chat related widgets. /// [StreamChat] is an inherited widget and must be the parent of all
/// Chat related widgets.
/// ///
/// Please note that while Flutter can be used to build both mobile and web applications; /// Please note that while Flutter can be used to build both mobile and web
/// in this tutorial we focus on mobile, make sure when running the app you use a mobile device. /// applications, in this tutorial we focus on mobile. Make sure when running
/// the app that you use a mobile device.
/// ///
/// Let's have a look at what we've built: /// Let's have a look at what we've built:
/// ///
/// - We set up the Chat [StreamChatClient] with the API key /// - We set up the Chat [StreamChatClient] with the API key
/// ///
/// - We set the the current user for Chat with [StreamChatClient.connectUser] and a pre-generated user token /// - We set the the current user for Chat with [StreamChatClient.connectUser]
/// and a pre-generated user token
/// ///
/// - We make [StreamChat] the root Widget of our application /// - We make [StreamChat] the root Widget of our application
/// ///
/// - We create a single [ChannelPage] widget under [StreamChat] with three widgets: [ChannelHeader], [MessageListView] and [MessageInput] /// - We create a single [ChannelPage] widget under [StreamChat] with three
/// widgets: [ChannelHeader], [MessageListView] and [MessageInput]
/// ///
/// If you now run the simulator you will see a single channel UI. /// If you now run the simulator you will see a single channel UI.
void main() async { void main() async {
@@ -33,26 +38,38 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
final channel = client.channel('messaging', id: 'godevs'); final channel = client.channel('messaging', id: 'godevs');
// ignore: unawaited_futures // ignore: unawaited_futures, cascade_invocations
channel.watch(); channel.watch();
runApp(MyApp(client, channel)); runApp(
MyApp(
client: client,
channel: channel,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
required this.channel,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
final Channel channel; final Channel channel;
MyApp(this.client, this.channel);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
// ignore: prefer_expression_function_bodies
builder: (context, widget) { builder: (context, widget) {
return StreamChat( return StreamChat(
client: client, client: client,
@@ -61,7 +78,7 @@ class MyApp extends StatelessWidget {
}, },
home: StreamChannel( home: StreamChannel(
channel: channel, channel: channel,
child: ChannelPage(), child: const ChannelPage(),
), ),
); );
} }
@@ -73,11 +90,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: MessageListView(),
), ),
@@ -5,20 +5,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Second step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// Second step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// Most chat applications handle more than just one single conversation. /// Most chat applications handle more than just one single conversation.
/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have multiple one to one and group conversations. /// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have
/// multiple one-to-one and group conversations.
/// ///
/// Lets find out how we can change our application chat screen to display the list of conversations and navigate between them. /// Lets find out how we can change our application chat screen to display
/// the list of conversations and navigate between them.
/// ///
/// > Note: the SDK uses Flutters [Navigator] to move from one route to another, this allows us to avoid any boiler-plate code. /// > Note: the SDK uses Flutters [Navigator] to move from one route to
/// > Of course you can take total control of how navigation works by customizing widgets like [Channel] and [ChannelList]. /// another. This allows us to avoid any boiler-plate code.
/// > Of course, you can take total control of how navigation works by
/// customizing widgets like [Channel] and [ChannelList].
/// ///
/// If you run the application, you will see that the first screen shows a list of conversations, you can open each by tapping and go back to the list. /// If you run the application, you will see that the first screen shows a
/// list of conversations, you can open each by tapping and go back to the list.
/// ///
/// Every single widget involved in this UI can be customized or swapped with your own. /// Every single widget involved in this UI can be customized or swapped
/// with your own.
/// ///
/// The [ChannelListPage] widget retrieves the list of channels based on a custom query and ordering. /// The [ChannelListPage] widget retrieves the list of channels based on a
/// In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message. /// custom query and ordering. In this case we are showing the list of
/// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel. /// channels in which the current user is a member and we order them based
/// on the time they had a new message. [ChannelListView] handles pagination
/// and updates automatically when new channels are created or when a new
/// message is added to a channel.
void main() async { void main() async {
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
@@ -27,31 +36,44 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
MyApp(this.client);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
builder: (context, child) => StreamChat( builder: (context, child) => StreamChat(
client: client, client: client,
child: child, child: child,
), ),
home: ChannelListPage(), home: const ChannelListPage(),
); );
} }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelsBloc( body: ChannelsBloc(
@@ -60,11 +82,11 @@ class ChannelListPage extends StatelessWidget {
'members', 'members',
[StreamChat.of(context).user!.id], [StreamChat.of(context).user!.id],
), ),
sort: [SortOption('last_message_at')], sort: const [SortOption('last_message_at')],
pagination: PaginationParams( pagination: const PaginationParams(
limit: 20, limit: 20,
), ),
channelWidget: ChannelPage(), channelWidget: const ChannelPage(),
), ),
), ),
); );
@@ -77,11 +99,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: MessageListView(),
), ),
@@ -6,22 +6,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Third step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// Third step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// So far youve learned how to use the default widgets. /// So far youve learned how to use the default widgets.
/// The library has been designed with composition in mind and to allow all common customizations to be very easy. /// The library has been designed with composition in mind and to allow all
/// This means that you can change any component in your application by swapping the default widgets with the ones you build yourself. /// common customizations to be very easy.
/// This means that you can change any component in your application by
/// swapping the default widgets with the ones you build yourself.
/// ///
/// Lets see how we can make some changes to the SDKs UI components. /// Lets see how we can make some changes to the SDKs UI components.
/// We start by changing how channel previews are shown in the channel list and include the number of unread messages for each. /// We start by changing how channel previews are shown in the channel list
/// and include the number of unread messages for each.
/// ///
/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder], this will override the default [ChannelPreview] and allows you to create one yourself. /// We're passing a custom widget to [ChannelListView.channelPreviewBuilder];
/// this will override the default [ChannelPreview] and allows you to create
/// one yourself.
/// ///
/// There are a couple interesting things we do in this widget: /// There are a couple interesting things we do in this widget:
/// ///
/// - Instead of creating a whole new style for the channel name, we inherit the text style from the parent theme ([StreamChatTheme.of]) and only change the color attribute /// - Instead of creating a whole new style for the channel name, we inherit
/// the text style from the parent theme ([StreamChatTheme.of]) and only
/// change the color attribute
/// ///
/// - We loop over the list of channel messages to search for the first not deleted message ([Channel.state.messages]) /// - We loop over the list of channel messages to search for the first not
/// deleted message ([Channel.state.messages])
/// ///
/// - We retrieve the count of unread messages from [Channel.state] /// - We retrieve the count of unread messages from [Channel.state]
void main() async { Future<void> main() async {
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
@@ -29,31 +37,44 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
MyApp(this.client);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
builder: (context, child) => StreamChat( builder: (context, child) => StreamChat(
client: client, client: client,
child: child, child: child,
), ),
home: ChannelListPage(), home: const ChannelListPage(),
); );
} }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelsBloc( body: ChannelsBloc(
@@ -64,10 +85,10 @@ class ChannelListPage extends StatelessWidget {
), ),
channelPreviewBuilder: _channelPreviewBuilder, channelPreviewBuilder: _channelPreviewBuilder,
// sort: [SortOption('last_message_at')], // sort: [SortOption('last_message_at')],
pagination: PaginationParams( pagination: const PaginationParams(
limit: 20, limit: 20,
), ),
channelWidget: ChannelPage(), channelWidget: const ChannelPage(),
), ),
), ),
); );
@@ -78,7 +99,7 @@ class ChannelListPage extends StatelessWidget {
(message) => !message.isDeleted, (message) => !message.isDeleted,
); );
final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text!); final subtitle = lastMessage == null ? 'nothing yet' : lastMessage.text!;
final opacity = (channel.state?.unreadCount ?? 0) > 0 ? 1.0 : 0.5; final opacity = (channel.state?.unreadCount ?? 0) > 0 ? 1.0 : 0.5;
return ListTile( return ListTile(
@@ -88,7 +109,7 @@ class ChannelListPage extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (_) => StreamChannel( builder: (_) => StreamChannel(
channel: channel, channel: channel,
child: ChannelPage(), child: const ChannelPage(),
), ),
), ),
); );
@@ -111,7 +132,7 @@ class ChannelListPage extends StatelessWidget {
radius: 10, radius: 10,
child: Text(channel.state!.unreadCount.toString()), child: Text(channel.state!.unreadCount.toString()),
) )
: SizedBox(), : const SizedBox(),
); );
} }
} }
@@ -122,11 +143,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: MessageListView(),
), ),
@@ -4,13 +4,17 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Fourth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// Fourth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// Stream Chat supports message threads out of the box. Threads allows users to create sub-conversations inside the same channel. /// Stream Chat supports message threads out of the box. Threads allows users
/// to create sub-conversations inside the same channel.
/// ///
/// Using threaded conversations is very simple and mostly a matter of plugging the [MessageListView] to another widget that renders the widget. /// Using threaded conversations is very simple and mostly a matter of
/// To make this simple, such a widget only needs to build [MessageListView] with the parent attribute set to the threads root message. /// plugging the [MessageListView] to another widget that renders the widget.
/// To make this simple, such a widget only needs to build [MessageListView]
/// with the parent attribute set to the threads root message.
/// ///
/// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage]. /// Now we can open threads and create new ones as well. If you long-press a
void main() async { /// message, you can tap on "Reply" and it will open the same [ThreadPage].
Future<void> main() async {
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
@@ -18,33 +22,44 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
MyApp(this.client);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
builder: (context, child) => StreamChat( builder: (context, child) => StreamChat(
client: client, client: client,
child: child, child: child,
), ),
home: Container( home: const ChannelListPage(),
child: ChannelListPage(),
),
); );
} }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelsBloc( body: ChannelsBloc(
@@ -53,11 +68,11 @@ class ChannelListPage extends StatelessWidget {
'members', 'members',
[StreamChat.of(context).user!.id], [StreamChat.of(context).user!.id],
), ),
sort: [SortOption('last_message_at')], sort: const [SortOption('last_message_at')],
pagination: PaginationParams( pagination: const PaginationParams(
limit: 20, limit: 20,
), ),
channelWidget: ChannelPage(), channelWidget: const ChannelPage(),
), ),
), ),
); );
@@ -70,21 +85,20 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: MessageListView( child: MessageListView(
threadBuilder: (_, parentMessage) { threadBuilder: (_, parentMessage) => ThreadPage(
return ThreadPage( parent: parentMessage,
parent: parentMessage, ),
);
},
), ),
), ),
MessageInput(), const MessageInput(),
], ],
), ),
); );
@@ -92,14 +106,15 @@ class ChannelPage extends StatelessWidget {
} }
class ThreadPage extends StatelessWidget { class ThreadPage extends StatelessWidget {
final Message? parent; const ThreadPage({
ThreadPage({
Key? key, Key? key,
this.parent, this.parent,
}) : super(key: key); }) : super(key: key);
final Message? parent;
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ThreadHeader( appBar: ThreadHeader(
@@ -4,18 +4,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// Customizing how messages are rendered is another very common use-case that the SDK supports easily. /// Customizing how messages are rendered is another very common use-case that
/// the SDK supports easily.
/// ///
/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget. /// Replacing the built-in message component with your own is done by passing
/// it as a builder function to the [MessageListView] widget.
/// ///
/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list. /// The message builder function will get the usual [BuildContext] argument
/// as well as the [Message] object and its position inside the list.
/// ///
/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way. /// If you look at the code you can see that we use [StreamChat.of] to
/// retrieve the current user so that we can style messages in a different way.
/// ///
/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel], /// Since custom widgets and builders are always children of [StreamChat] or
/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly /// part of a [Channel], you can use [StreamChat.of], [StreamChannel.of],
/// or to retrieve outer scope needed such as messages from the [Channel.state]. /// and [StreamChatTheme.of] to use the API client directly or to retrieve
void main() async { /// outer scope needed such as messages from the [Channel.state].
Future<void> main() async {
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
@@ -23,31 +28,44 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client; final StreamChatClient client;
MyApp(this.client);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
builder: (context, child) => StreamChat( builder: (context, child) => StreamChat(
client: client, client: client,
child: child, child: child,
), ),
home: ChannelListPage(), home: const ChannelListPage(),
); );
} }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelsBloc( body: ChannelsBloc(
@@ -56,11 +74,11 @@ class ChannelListPage extends StatelessWidget {
'members', 'members',
[StreamChat.of(context).user!.id], [StreamChat.of(context).user!.id],
), ),
sort: [SortOption('last_message_at')], sort: const [SortOption('last_message_at')],
pagination: PaginationParams( pagination: const PaginationParams(
limit: 20, limit: 20,
), ),
channelWidget: ChannelPage(), channelWidget: const ChannelPage(),
), ),
), ),
); );
@@ -73,9 +91,10 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@@ -83,7 +102,7 @@ class ChannelPage extends StatelessWidget {
messageBuilder: _messageBuilder, messageBuilder: _messageBuilder,
), ),
), ),
MessageInput(), const MessageInput(),
], ],
), ),
); );
@@ -101,12 +120,14 @@ class ChannelPage extends StatelessWidget {
final color = isCurrentUser ? Colors.blueGrey : Colors.blue; final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
return Padding( return Padding(
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: color, width: 1), border: Border.all(
borderRadius: BorderRadius.all( color: color,
Radius.circular(5.0), ),
borderRadius: const BorderRadius.all(
Radius.circular(5),
), ),
), ),
child: ListTile( child: ListTile(
@@ -115,7 +136,7 @@ class ChannelPage extends StatelessWidget {
textAlign: textAlign, textAlign: textAlign,
), ),
subtitle: Text( subtitle: Text(
message.user!.extraData['name'] as String, message.user!.name,
textAlign: textAlign, textAlign: textAlign,
), ),
), ),
@@ -4,22 +4,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Sixth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) /// Sixth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
/// ///
/// The Flutter SDK comes with a fully designed set of widgets which you can customize to fit with your application style and typography. /// The Flutter SDK comes with a fully designed set of widgets which you can
/// Changing the theme of Chat widgets works in a very similar way that [MaterialApp] and [Theme] do. /// customize to fit with your application style and typography.
/// Changing the theme of Chat widgets works in a very similar way that
/// [MaterialApp] and [Theme] do.
/// ///
/// Out of the box all chat widgets use their own default styling, there are two ways to change the styling: /// All chat widgets use their own default styling out of the box. There are
/// two ways to change the styling:
/// ///
/// 1. Initialize the [StreamChatTheme] from your existing [MaterialApp] style /// 1. Initialize the [StreamChatTheme] from your existing [MaterialApp] style
/// 2. Construct a custom theme and provide all the customizations needed /// 2. Construct a custom theme and provide all the customizations needed
/// ///
/// First we create a new Material [Theme] and pick [Colors.green] as swatch color. The theme is then passed to [MaterialApp] as usual. /// First, we create a new Material [Theme] and pick [Colors.green] as the
/// swatch color. The theme is then passed to [MaterialApp] as usual.
/// ///
/// Then we create a new [StreamChatTheme] from the green theme we just created. /// Then, we create a new [StreamChatTheme] from the green theme we just
/// After saving the app you will see the UI will update several widgets to match with the new color. /// created. After saving the app you will see that several widgets have
/// been updated with the new color.
/// ///
/// We also change the message color posted by the current user. /// We also change the message color posted by the current user.
/// You can perform these more granular style changes using [StreamChatTheme.copyWith]. ///
void main() async { /// You can perform these more granular style changes using
/// [StreamChatTheme.copyWith].
Future<void> main() async {
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 's2dxdhpxd94g',
logLevel: Level.INFO, logLevel: Level.INFO,
@@ -27,16 +34,23 @@ void main() async {
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', '''eyJ0eXAiO«iJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
); );
runApp(MyApp(client)); runApp(
MyApp(
client: client,
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
final StreamChatClient client; const MyApp({
Key? key,
required this.client,
}) : super(key: key);
MyApp(this.client); final StreamChatClient client;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -62,20 +76,23 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
theme: themeData, theme: themeData,
builder: (context, child) { builder: (context, child) => StreamChat(
return StreamChat( client: client,
client: client, streamChatThemeData: customTheme,
streamChatThemeData: customTheme, child: child,
child: child, ),
); home: const ChannelListPage(),
},
home: ChannelListPage(),
); );
} }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ChannelsBloc( body: ChannelsBloc(
@@ -84,11 +101,11 @@ class ChannelListPage extends StatelessWidget {
'members', 'members',
[StreamChat.of(context).user!.id], [StreamChat.of(context).user!.id],
), ),
sort: [SortOption('last_message_at')], sort: const [SortOption('last_message_at')],
pagination: PaginationParams( pagination: const PaginationParams(
limit: 20, limit: 20,
), ),
channelWidget: ChannelPage(), channelWidget: const ChannelPage(),
), ),
), ),
); );
@@ -101,21 +118,20 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ChannelHeader(), appBar: const ChannelHeader(),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: MessageListView( child: MessageListView(
threadBuilder: (_, parentMessage) { threadBuilder: (_, parentMessage) => ThreadPage(
return ThreadPage( parent: parentMessage,
parent: parentMessage, ),
);
},
), ),
), ),
MessageInput(), const MessageInput(),
], ],
), ),
); );
@@ -123,14 +139,15 @@ class ChannelPage extends StatelessWidget {
} }
class ThreadPage extends StatelessWidget { class ThreadPage extends StatelessWidget {
final Message? parent; const ThreadPage({
ThreadPage({
Key? key, Key? key,
this.parent, this.parent,
}) : super(key: key); }) : super(key: key);
final Message? parent;
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: ThreadHeader( appBar: ThreadHeader(
@@ -21,21 +21,21 @@ environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
collection: ^1.15.0
cupertino_icons: ^1.0.3
flutter: flutter:
sdk: flutter sdk: flutter
# stream_chat: # stream_chat:
# path: ../../stream_chat # path: ../../stream_chat
# stream_chat_flutter_core: # stream_chat_flutter_core:
# path: ../../stream_chat_flutter_core # path: ../../stream_chat_flutter_core
stream_chat_flutter: stream_chat_flutter:
path: ../ path: ../
stream_chat_persistence: stream_chat_persistence:
path: ../../stream_chat_persistence path: ../../stream_chat_persistence
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
collection: ^1.15.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -70,6 +70,7 @@ void main() {
StreamChat( StreamChat(
streamChatThemeData: theme, streamChatThemeData: theme,
client: client, client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel( child: StreamChannel(
showLoading: false, showLoading: false,
channel: channel, channel: channel,
@@ -79,7 +80,6 @@ void main() {
), ),
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
), ),
surfaceSize: const Size.square(200), surfaceSize: const Size.square(200),
@@ -123,6 +123,7 @@ void main() {
StreamChat( StreamChat(
streamChatThemeData: theme, streamChatThemeData: theme,
client: client, client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel( child: StreamChannel(
showLoading: false, showLoading: false,
channel: channel, channel: channel,
@@ -132,7 +133,6 @@ void main() {
), ),
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
), ),
surfaceSize: const Size.square(200), surfaceSize: const Size.square(200),
@@ -176,6 +176,7 @@ void main() {
StreamChat( StreamChat(
streamChatThemeData: theme, streamChatThemeData: theme,
client: client, client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel( child: StreamChannel(
showLoading: false, showLoading: false,
channel: channel, channel: channel,
@@ -189,7 +190,6 @@ void main() {
), ),
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
), ),
surfaceSize: const Size.square(200), surfaceSize: const Size.square(200),
@@ -54,12 +54,12 @@ void main() {
) )
])); ]));
when(() => channelState.typingEvents).thenAnswer((i) => { when(() => channelState.typingEvents).thenAnswer((i) => {
User(id: 'other-user', extraData: {'name': 'demo'}): User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart), Event(type: EventType.typingStart),
}); });
when(() => channelState.typingEventsStream) when(() => channelState.typingEventsStream)
.thenAnswer((i) => Stream.value({ .thenAnswer((i) => Stream.value({
User(id: 'other-user', extraData: {'name': 'demo'}): User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart), Event(type: EventType.typingStart),
})); }));
@@ -14,7 +14,7 @@ void main() {
}); });
test( test(
'Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData', '''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''',
() { () {
expect( expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl,
@@ -23,7 +23,7 @@ void main() {
}); });
test( test(
'Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData', '''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''',
() { () {
expect( expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl,
@@ -32,7 +32,7 @@ void main() {
}); });
test( test(
'Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData', '''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''',
() { () {
expect( expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark, const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark,
@@ -14,7 +14,7 @@ void main() {
}); });
test( test(
'Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData', '''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''',
() { () {
expect( expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl,
@@ -23,7 +23,7 @@ void main() {
}); });
test( test(
'Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData', '''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''',
() { () {
expect( expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl,
@@ -32,7 +32,7 @@ void main() {
}); });
test( test(
'Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData', '''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''',
() { () {
expect( expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl, const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

@@ -79,7 +79,8 @@ void main() {
'name': 'test', 'name': 'test',
}); });
final messageText = '''a message. const messageText = '''
a message.
with multiple lines with multiple lines
and a list: and a list:
- a. okasd - a. okasd
@@ -47,6 +47,7 @@ void main() {
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: theme, streamChatThemeData: theme,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: SizedBox( child: SizedBox(
child: ReactionBubble( child: ReactionBubble(
reactions: [ reactions: [
@@ -60,7 +61,6 @@ void main() {
maskColor: theme.ownMessageTheme.reactionsMaskColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
surfaceSize: const Size(100, 100), surfaceSize: const Size(100, 100),
); );
@@ -83,6 +83,7 @@ void main() {
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: StreamChatThemeData.fromTheme(themeData), streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: Container( child: Container(
color: Colors.black, color: Colors.black,
child: ReactionBubble( child: ReactionBubble(
@@ -97,7 +98,6 @@ void main() {
maskColor: theme.ownMessageTheme.reactionsMaskColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
surfaceSize: const Size(100, 100), surfaceSize: const Size(100, 100),
); );
@@ -89,6 +89,7 @@ void main() {
)( )(
StreamChat( StreamChat(
client: client, client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel( child: StreamChannel(
showLoading: false, showLoading: false,
channel: channel, channel: channel,
@@ -100,7 +101,6 @@ void main() {
), ),
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
), ),
surfaceSize: const Size.square(200), surfaceSize: const Size.square(200),
@@ -141,6 +141,7 @@ void main() {
)( )(
StreamChat( StreamChat(
client: client, client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel( child: StreamChannel(
showLoading: false, showLoading: false,
channel: channel, channel: channel,
@@ -152,7 +153,6 @@ void main() {
), ),
), ),
), ),
connectivityStream: Stream.value(ConnectivityResult.mobile),
), ),
), ),
surfaceSize: const Size.square(200), surfaceSize: const Size.square(200),
@@ -54,12 +54,12 @@ void main() {
])); ]));
when(() => channelState.typingEvents).thenAnswer((i) => { when(() => channelState.typingEvents).thenAnswer((i) => {
User(id: 'other-user', extraData: {'name': 'demo'}): User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart), Event(type: EventType.typingStart),
}); });
when(() => channelState.typingEventsStream) when(() => channelState.typingEventsStream)
.thenAnswer((i) => Stream.value({ .thenAnswer((i) => Stream.value({
User(id: 'other-user', extraData: {'name': 'demo'}): User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart), Event(type: EventType.typingStart),
})); }));
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
Future<void> main() async { Future<void> main() async {
/// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// Create a new instance of [StreamChatClient] passing the apikey obtained
/// project dashboard. /// from your project dashboard.
final client = StreamChatClient('b67pax5b2wdq'); final client = StreamChatClient('b67pax5b2wdq');
/// Set the current user. In a production scenario, this should be done using /// Set the current user. In a production scenario, this should be done using
@@ -13,12 +13,13 @@ Future<void> main() async {
await client.connectUser( await client.connectUser(
User( User(
id: 'cool-shadow-7', id: 'cool-shadow-7',
extraData: { extraData: const {
'image': 'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow', 'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
}, },
), ),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9'
'.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
); );
runApp( runApp(
@@ -29,36 +30,36 @@ Future<void> main() async {
} }
/// Example application using Stream Chat core widgets. /// Example application using Stream Chat core widgets.
/// Stream Chat Core is a set of Flutter wrappers which provide basic functionality /// Stream Chat Core is a set of Flutter wrappers which provide basic
/// for building Flutter applications using Stream. /// functionality for building Flutter applications using Stream.
///
/// If you'd prefer using pre-made UI widgets for your app, please see our other /// If you'd prefer using pre-made UI widgets for your app, please see our other
/// package, `stream_chat_flutter`. /// package, `stream_chat_flutter`.
class StreamExample extends StatelessWidget { class StreamExample extends StatelessWidget {
/// Minimal example using Stream's core Flutter package. /// Minimal example using Stream's core Flutter package.
/// If you'd prefer using pre-made UI widgets for your app, please see our other ///
/// package, `stream_chat_flutter`. /// If you'd prefer using pre-made UI widgets for your app, please see our
/// other package, `stream_chat_flutter`.
const StreamExample({ const StreamExample({
Key? key, Key? key,
required this.client, required this.client,
}) : super(key: key); }) : super(key: key);
/// Instance of Stream Client. /// Instance of Stream Client.
/// Stream's [StreamChatClient] can be used to connect to our servers and set the default /// Stream's [StreamChatClient] can be used to connect to our servers and
/// user for the application. Performing these actions trigger a websocket connection /// set the default user for the application. Performing these actions
/// allowing for real-time updates. /// trigger a websocket connection allowing for real-time updates.
final StreamChatClient client; final StreamChatClient client;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( title: 'Stream Chat Core Example',
title: 'Stream Chat Core Example', home: HomeScreen(),
home: HomeScreen(), builder: (context, child) => StreamChatCore(
builder: (context, child) => StreamChatCore( client: client,
client: client, child: child!,
child: child!, ),
), );
);
}
} }
/// Basic layout displaying a list of [Channel]s the user is a part of. /// Basic layout displaying a list of [Channel]s the user is a part of.
@@ -67,104 +68,108 @@ class StreamExample extends StatelessWidget {
/// [ChannelListCore] is a `builder` with callbacks for constructing UIs based /// [ChannelListCore] is a `builder` with callbacks for constructing UIs based
/// on different scenarios. /// on different scenarios.
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
/// Builds a basic layout displaying a list of [Channel]s the user is a
/// part of.
HomeScreen({Key? key}) : super(key: key);
/// Controller used for loading more data and controlling pagination in
/// [ChannelListCore].
final channelListController = ChannelListController(); final channelListController = ChannelListController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Scaffold(
return Scaffold( appBar: AppBar(
appBar: AppBar( title: const Text('Channels'),
title: Text('Channels'), ),
), body: ChannelsBloc(
body: ChannelsBloc( child: ChannelListCore(
child: ChannelListCore( channelListController: channelListController,
channelListController: channelListController, filter: Filter.and([
filter: Filter.and([ Filter.equal('type', 'messaging'),
Filter.equal('type', 'messaging'), Filter.in_('members', [
Filter.in_('members', [ StreamChatCore.of(context).user!.id,
StreamChatCore.of(context).user!.id, ])
]) ]),
]), emptyBuilder: (BuildContext context) => const Center(
emptyBuilder: (BuildContext context) {
return Center(
child: Text('Looks like you are not in any channels'), child: Text('Looks like you are not in any channels'),
); ),
}, loadingBuilder: (BuildContext context) => const Center(
loadingBuilder: (BuildContext context) {
return Center(
child: SizedBox( child: SizedBox(
height: 100.0, height: 100,
width: 100.0, width: 100,
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); ),
}, errorBuilder: (
errorBuilder: ( BuildContext context,
BuildContext context, dynamic error,
dynamic error, ) =>
) { Center(
return Center(
child: Text( child: Text(
'Oh no, something went wrong. Please check your config. ${error}'), 'Oh no, something went wrong. '
); 'Please check your config. $error',
}, ),
listBuilder: ( ),
BuildContext context, listBuilder: (
List<Channel> channels, BuildContext context,
) => List<Channel> channels,
LazyLoadScrollView( ) =>
onEndOfPage: () async { LazyLoadScrollView(
channelListController.paginateData!(); onEndOfPage: () async {
}, channelListController.paginateData!();
child: ListView.builder(
itemCount: channels.length,
itemBuilder: (BuildContext context, int index) {
final _item = channels[index];
return ListTile(
title: Text(_item.name!),
subtitle: StreamBuilder<Message?>(
stream: _item.state!.lastMessageStream,
initialData: _item.state!.lastMessage,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.text!);
}
return SizedBox();
},
),
onTap: () {
/// Display a list of messages when the user taps on an item.
/// We can use [StreamChannel] to wrap our [MessageScreen] screen
/// with the selected channel.
///
/// This allows us to use a built-in inherited widget for accessing
/// our `channel` later on.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: _item,
child: MessageScreen(),
),
),
);
},
);
}, },
child: ListView.builder(
itemCount: channels.length,
itemBuilder: (BuildContext context, int index) {
final _item = channels[index];
return ListTile(
title: Text(_item.name!),
subtitle: StreamBuilder<Message?>(
stream: _item.state!.lastMessageStream,
initialData: _item.state!.lastMessage,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.text!);
}
return const SizedBox();
},
),
onTap: () {
/// Display a list of messages when the user taps on
/// an item. We can use [StreamChannel] to wrap our
/// [MessageScreen] screen with the selected channel.
///
/// This allows us to use a built-in inherited widget
/// for accessing our `channel` later on.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: _item,
child: const MessageScreen(),
),
),
);
},
);
},
),
), ),
), ),
), ),
), );
);
}
} }
/// A list of messages sent in the current channel. /// A list of messages sent in the current channel.
/// When a user taps on a channel in [HomeScreen], a navigator push [MessageScreen] /// When a user taps on a channel in [HomeScreen], a navigator push
/// to display the list of messages in the selected channel. /// [MessageScreen] to display the list of messages in the selected channel.
/// ///
/// This is implemented using [MessageListCore], a convenience builder with /// This is implemented using [MessageListCore], a convenience builder with
/// callbacks for building UIs based on different api results. /// callbacks for building UIs based on different api results.
class MessageScreen extends StatefulWidget { class MessageScreen extends StatefulWidget {
/// Build a MessageScreen
const MessageScreen({Key? key}) : super(key: key);
@override @override
_MessageScreenState createState() => _MessageScreenState(); _MessageScreenState createState() => _MessageScreenState();
} }
@@ -198,8 +203,8 @@ class _MessageScreenState extends State<MessageScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
/// To access the current channel, we can use the `.of()` method on [StreamChannel] /// To access the current channel, we can use the `.of()` method on
/// to fetch the closest instance. /// [StreamChannel] to fetch the closest instance.
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -210,7 +215,7 @@ class _MessageScreenState extends State<MessageScreen> {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return Text('${snapshot.data!.first.name} is typing...'); return Text('${snapshot.data!.first.name} is typing...');
} }
return SizedBox(); return const SizedBox();
}, },
), ),
), ),
@@ -224,57 +229,52 @@ class _MessageScreenState extends State<MessageScreen> {
}, },
child: MessageListCore( child: MessageListCore(
messageListController: messageListController, messageListController: messageListController,
emptyBuilder: (BuildContext context) { emptyBuilder: (BuildContext context) => const Center(
return Center( child: Text('Nothing here yet'),
child: Text('Nothing here yet'), ),
); loadingBuilder: (BuildContext context) => const Center(
}, child: SizedBox(
loadingBuilder: (BuildContext context) { height: 100,
return Center( width: 100,
child: SizedBox( child: CircularProgressIndicator(),
height: 100.0, ),
width: 100.0, ),
child: CircularProgressIndicator(),
),
);
},
messageListBuilder: ( messageListBuilder: (
BuildContext context, BuildContext context,
List<Message> messages, List<Message> messages,
) { ) =>
return ListView.builder( ListView.builder(
controller: _scrollController, controller: _scrollController,
itemCount: messages.length, itemCount: messages.length,
reverse: true, reverse: true,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = messages[index]; final item = messages[index];
final client = StreamChatCore.of(context).client; final client = StreamChatCore.of(context).client;
if (item.user!.id == client.uid) { if (item.user!.id == client.uid) {
return Align( return Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8),
child: Text(item.text!), child: Text(item.text!),
), ),
); );
} else { } else {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8),
child: Text(item.text!), child: Text(item.text!),
), ),
); );
} }
}, },
); ),
},
errorBuilder: (BuildContext context, error) { errorBuilder: (BuildContext context, error) {
print(error.toString()); print(error.toString());
return Center( return const Center(
child: SizedBox( child: SizedBox(
height: 100.0, height: 100,
width: 100.0, width: 100,
child: child:
Text('Oh no, an error occured. Please see logs.'), Text('Oh no, an error occured. Please see logs.'),
), ),
@@ -21,14 +21,14 @@ environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.3
flutter: flutter:
sdk: flutter sdk: flutter
stream_chat_flutter_core: stream_chat_flutter_core:
path: ../ path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -9,26 +9,25 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'mocks.dart'; import 'mocks.dart';
void main() { void main() {
const pagination = PaginationParams(offset: 0, limit: 3); const pagination = PaginationParams(limit: 3);
List<Channel> _generateChannels( List<Channel> _generateChannels(
StreamChatClient client, { StreamChatClient client, {
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return Channel( return Channel(
client, client,
'testType$index', 'testType$index',
'testId$index', 'testId$index',
extraData: {'extra_data_key': 'extra_data_value_$index'}, extraData: {'extra_data_key': 'extra_data_value_$index'},
); );
}, },
); );
}
testWidgets( testWidgets(
'should throw if ChannelListCore is used where ChannelsBloc is not present ' 'should throw if ChannelListCore is used where ChannelsBloc is not present '
@@ -37,10 +36,10 @@ void main() {
const channelListCoreKey = Key('channelListCore'); const channelListCoreKey = Key('channelListCore');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
await tester.pumpWidget(channelListCore); await tester.pumpWidget(channelListCore);
@@ -56,16 +55,16 @@ void main() {
const channelListCoreKey = Key('channelListCore'); const channelListCoreKey = Key('channelListCore');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -88,10 +87,10 @@ void main() {
final controller = ChannelListController(); final controller = ChannelListController();
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
channelListController: controller, channelListController: controller,
); );
@@ -101,7 +100,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -125,9 +124,9 @@ void main() {
const errorWidgetKey = Key('errorWidget'); const errorWidgetKey = Key('errorWidget');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => errorBuilder: (BuildContext context, Object error) =>
Container(key: errorWidgetKey), Container(key: errorWidgetKey),
pagination: pagination, pagination: pagination,
@@ -136,7 +135,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
@@ -177,23 +176,23 @@ void main() {
); );
testWidgets( testWidgets(
'should build empty widget if channelsBlocState.channelsStream emits empty data', '''should build empty widget if channelsBlocState.channelsStream emits empty data''',
(tester) async { (tester) async {
const channelListCoreKey = Key('channelListCore'); const channelListCoreKey = Key('channelListCore');
const emptyWidgetKey = Key('emptyWidget'); const emptyWidgetKey = Key('emptyWidget');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
pagination: pagination, pagination: pagination,
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
const channels = <Channel>[]; const channels = <Channel>[];
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
@@ -234,23 +233,23 @@ void main() {
); );
testWidgets( testWidgets(
'should build list widget if channelsBlocState.channelsStream emits some data', '''should build list widget if channelsBlocState.channelsStream emits some data''',
(tester) async { (tester) async {
const channelListCoreKey = Key('channelListCore'); const channelListCoreKey = Key('channelListCore');
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, __) => Container(key: listWidgetKey), listBuilder: (_, __) => Container(key: listWidgetKey),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
pagination: pagination, pagination: pagination,
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
@@ -298,24 +297,22 @@ void main() {
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
final channelListCore = ChannelListCore( final channelListCore = ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, channels) { listBuilder: (_, channels) => Container(
return Container( key: listWidgetKey,
key: listWidgetKey, child: Text(
child: Text( channels.map((e) => e.cid).join(','),
channels.map((e) => e.cid).join(','), ),
), ),
); loadingBuilder: (BuildContext context) => const Offstage(),
}, emptyBuilder: (BuildContext context) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
pagination: pagination, pagination: pagination,
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
@@ -413,24 +410,23 @@ void main() {
ChannelListCore channelListCoreBuilder(int limit) => ChannelListCore( ChannelListCore channelListCoreBuilder(int limit) => ChannelListCore(
key: channelListCoreKey, key: channelListCoreKey,
listBuilder: (_, channels) { listBuilder: (_, channels) => Container(
return Container( key: listWidgetKey,
key: listWidgetKey, child: Text(
child: Text( channels.map((e) => e.cid).join(','),
channels.map((e) => e.cid).join(','), ),
), ),
); loadingBuilder: (BuildContext context) => const Offstage(),
}, emptyBuilder: (BuildContext context) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) =>
emptyBuilder: (BuildContext context) => Offstage(), const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
pagination: pagination.copyWith(limit: limit), pagination: pagination.copyWith(limit: limit),
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
@@ -17,27 +17,26 @@ void main() {
StreamChatClient client, { StreamChatClient client, {
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return Channel( return Channel(
client, client,
'testType$index', 'testType$index',
'testId$index', 'testId$index',
extraData: {'extra_data_key': 'extra_data_value_$index'}, extraData: {'extra_data_key': 'extra_data_value_$index'},
); );
}, },
); );
}
testWidgets( testWidgets(
'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree', '''should throw if ChannelsBloc is used where StreamChat is not present in the widget tree''',
(tester) async { (tester) async {
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
const childKey = Key('child'); const childKey = Key('child');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -55,7 +54,7 @@ void main() {
(tester) async { (tester) async {
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
const childKey = Key('child'); const childKey = Key('child');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -63,7 +62,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -86,16 +85,14 @@ void main() {
key: channelsBlocKey, key: channelsBlocKey,
child: Builder( child: Builder(
key: childKey, key: childKey,
builder: (context) { builder: (context) => const Offstage(),
return Offstage();
},
), ),
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -157,16 +154,14 @@ void main() {
key: channelsBlocKey, key: channelsBlocKey,
child: Builder( child: Builder(
key: childKey, key: childKey,
builder: (context) { builder: (context) => const Offstage(),
return Offstage();
},
), ),
); );
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -218,7 +213,7 @@ void main() {
'through queryChannelsLoading', 'through queryChannelsLoading',
(tester) async { (tester) async {
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(), child: Offstage(),
); );
@@ -226,7 +221,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -323,7 +318,7 @@ void main() {
'client.queryChannels() throws', 'client.queryChannels() throws',
(tester) async { (tester) async {
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(), child: Offstage(),
); );
@@ -331,7 +326,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -345,7 +340,7 @@ void main() {
); );
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
final paginationParams = const PaginationParams( const paginationParams = PaginationParams(
limit: 3, limit: 3,
); );
@@ -380,7 +375,7 @@ void main() {
paginationParams: paginationParams, paginationParams: paginationParams,
)).called(1); )).called(1);
final error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(() => mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: any(named: 'filter'), filter: any(named: 'filter'),
@@ -424,13 +419,13 @@ void main() {
(tester) async { (tester) async {
final mockClient = MockClient(); final mockClient = MockClient();
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(), child: Offstage(),
); );
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.channelHidden, EventType.channelHidden,
@@ -503,13 +498,13 @@ void main() {
(tester) async { (tester) async {
final mockClient = MockClient(); final mockClient = MockClient();
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(), child: Offstage(),
); );
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.channelDeleted, EventType.channelDeleted,
@@ -589,18 +584,18 @@ void main() {
); );
testWidgets( testWidgets(
'event channel should be moved to top of the list if present when' 'event channel should be moved to top of the list if present when '
'EventType.messageNew event is received', 'EventType.messageNew event is received',
(tester) async { (tester) async {
final mockClient = MockClient(); final mockClient = MockClient();
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( const channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(), child: Offstage(),
); );
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
@@ -678,21 +673,19 @@ void main() {
(tester) async { (tester) async {
final hiddenChannelEventController = StreamController<Event>(); final hiddenChannelEventController = StreamController<Event>();
addTearDown(() { addTearDown(hiddenChannelEventController.close);
hiddenChannelEventController.close();
});
final mockClient = MockClient(); final mockClient = MockClient();
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( final channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(),
shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid), shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid),
child: const Offstage(),
); );
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.channelHidden, EventType.channelHidden,
@@ -789,14 +782,14 @@ void main() {
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( final channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(),
shouldAddChannel: (_) => true, shouldAddChannel: (_) => true,
child: const Offstage(),
); );
when(() => mockClient.state.channels).thenReturn(stateChannels); when(() => mockClient.state.channels).thenReturn(stateChannels);
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
@@ -868,21 +861,21 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
int channelComparator(Channel a, Channel b) { int channelComparator(Channel a, Channel b) {
final aData = a.extraData['extra_data_key'] as String; final aData = a.extraData['extra_data_key'].toString();
final bData = b.extraData['extra_data_key'] as String; final bData = b.extraData['extra_data_key'].toString();
return bData.compareTo(aData); return bData.compareTo(aData);
} }
const channelsBlocKey = Key('channelsBloc'); const channelsBlocKey = Key('channelsBloc');
final channelsBloc = ChannelsBloc( final channelsBloc = ChannelsBloc(
key: channelsBlocKey, key: channelsBlocKey,
child: Offstage(),
shouldAddChannel: (_) => true, shouldAddChannel: (_) => true,
channelsComparator: channelComparator, channelsComparator: channelComparator,
child: const Offstage(),
); );
when(() => mockClient.on(any(), any(), any(), any())) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => const Stream.empty());
when(() => mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
@@ -7,7 +7,7 @@ void main() {
'should render LazyLoadScrollView if child is provided', 'should render LazyLoadScrollView if child is provided',
(tester) async { (tester) async {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
final lazyLoadScrollView = LazyLoadScrollView( const lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
child: Offstage(), child: Offstage(),
); );
@@ -23,7 +23,7 @@ void main() {
(tester) async { (tester) async {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childKey = Key('childKey'); const childKey = Key('childKey');
final lazyLoadScrollView = LazyLoadScrollView( const lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -41,7 +41,7 @@ void main() {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childListViewKey = Key('childListView'); const childListViewKey = Key('childListView');
bool onPageScrollStartCalled = false; var onPageScrollStartCalled = false;
final lazyLoadScrollView = LazyLoadScrollView( final lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
@@ -52,7 +52,7 @@ void main() {
key: childListViewKey, key: childListViewKey,
children: List.generate( children: List.generate(
12, 12,
(index) => Container( (index) => SizedBox(
height: 100, height: 100,
child: Text('Item #$index'), child: Text('Item #$index'),
), ),
@@ -71,7 +71,7 @@ void main() {
expect(find.byKey(childListViewKey), findsOneWidget); expect(find.byKey(childListViewKey), findsOneWidget);
expect(onPageScrollStartCalled, isFalse); expect(onPageScrollStartCalled, isFalse);
await tester.startGesture(const Offset(100.0, 100.0)); await tester.startGesture(const Offset(100, 100));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(onPageScrollStartCalled, isTrue); expect(onPageScrollStartCalled, isTrue);
@@ -85,8 +85,8 @@ void main() {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childListViewKey = Key('childListView'); const childListViewKey = Key('childListView');
bool onPageScrollStartCalled = false; var onPageScrollStartCalled = false;
bool onPageScrollEndCalled = false; var onPageScrollEndCalled = false;
final lazyLoadScrollView = LazyLoadScrollView( final lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
@@ -100,7 +100,7 @@ void main() {
key: childListViewKey, key: childListViewKey,
children: List.generate( children: List.generate(
12, 12,
(index) => Container( (index) => SizedBox(
height: 100, height: 100,
child: Text('Item #$index'), child: Text('Item #$index'),
), ),
@@ -122,7 +122,7 @@ void main() {
final gesture = await tester.createGesture(); final gesture = await tester.createGesture();
await gesture.down(const Offset(100.0, 100.0)); await gesture.down(const Offset(100, 100));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(onPageScrollStartCalled, isTrue); expect(onPageScrollStartCalled, isTrue);
@@ -141,7 +141,7 @@ void main() {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childListViewKey = Key('childListView'); const childListViewKey = Key('childListView');
bool onInBetweenOfPageCalled = false; var onInBetweenOfPageCalled = false;
final lazyLoadScrollView = LazyLoadScrollView( final lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
@@ -152,7 +152,7 @@ void main() {
key: childListViewKey, key: childListViewKey,
children: List.generate( children: List.generate(
12, 12,
(index) => Container( (index) => SizedBox(
height: 100, height: 100,
child: Text('Item #$index'), child: Text('Item #$index'),
), ),
@@ -173,9 +173,9 @@ void main() {
final gesture = await tester.createGesture(); final gesture = await tester.createGesture();
await gesture.down(const Offset(100.0, 100.0)); await gesture.down(const Offset(100, 100));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.moveBy(const Offset(-200.0, -200.0)); await gesture.moveBy(const Offset(-200, -200));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(onInBetweenOfPageCalled, isTrue); expect(onInBetweenOfPageCalled, isTrue);
@@ -189,7 +189,7 @@ void main() {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childListViewKey = Key('childListView'); const childListViewKey = Key('childListView');
bool onStartOfPageCalled = false; var onStartOfPageCalled = false;
final lazyLoadScrollView = LazyLoadScrollView( final lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
@@ -200,7 +200,7 @@ void main() {
key: childListViewKey, key: childListViewKey,
children: List.generate( children: List.generate(
12, 12,
(index) => Container( (index) => SizedBox(
height: 100, height: 100,
child: Text('Item #$index'), child: Text('Item #$index'),
), ),
@@ -221,11 +221,11 @@ void main() {
final gesture = await tester.createGesture(); final gesture = await tester.createGesture();
await gesture.down(const Offset(100.0, 100.0)); await gesture.down(const Offset(100, 100));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.moveBy(const Offset(-200.0, -200.0)); await gesture.moveBy(const Offset(-200, -200));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.moveBy(const Offset(201.0, 201.0)); await gesture.moveBy(const Offset(201, 201));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(onStartOfPageCalled, isTrue); expect(onStartOfPageCalled, isTrue);
@@ -239,7 +239,7 @@ void main() {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView'); const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
const childListViewKey = Key('childListView'); const childListViewKey = Key('childListView');
bool onEndOfPageCalled = false; var onEndOfPageCalled = false;
final lazyLoadScrollView = LazyLoadScrollView( final lazyLoadScrollView = LazyLoadScrollView(
key: lazyLoadScrollViewKey, key: lazyLoadScrollViewKey,
@@ -250,7 +250,7 @@ void main() {
key: childListViewKey, key: childListViewKey,
children: List.generate( children: List.generate(
12, 12,
(index) => Container( (index) => SizedBox(
height: 100, height: 100,
child: Text('Item #$index'), child: Text('Item #$index'),
), ),
@@ -271,9 +271,9 @@ void main() {
final gesture = await tester.createGesture(); final gesture = await tester.createGesture();
await gesture.down(const Offset(100.0, 100.0)); await gesture.down(const Offset(100, 100));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.moveBy(const Offset(-601.0, -601.0)); await gesture.moveBy(const Offset(-601, -601));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(onEndOfPageCalled, isTrue); expect(onEndOfPageCalled, isTrue);
@@ -1,4 +1,3 @@
import 'package:meta/meta.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -8,7 +7,7 @@ Matcher isSameChannelAs(Channel targetChannel) =>
class _IsSameChannelAs extends Matcher { class _IsSameChannelAs extends Matcher {
const _IsSameChannelAs({ const _IsSameChannelAs({
required this.targetChannel, required this.targetChannel,
}) : assert(targetChannel != null, ''); });
final Channel targetChannel; final Channel targetChannel;
@@ -27,13 +26,13 @@ Matcher isSameChannelListAs(List<Channel> targetChannelList) =>
class _IsSameChannelListAs extends Matcher { class _IsSameChannelListAs extends Matcher {
const _IsSameChannelListAs({ const _IsSameChannelListAs({
required this.targetChannelList, required this.targetChannelList,
}) : assert(targetChannelList != null, ''); });
final List<Channel> targetChannelList; final List<Channel> targetChannelList;
@override @override
bool matches(covariant List<Channel> channelList, Map matchState) { bool matches(covariant List<Channel> channelList, Map matchState) {
bool matches = true; var matches = true;
for (var i = 0; i < channelList.length; i++) { for (var i = 0; i < channelList.length; i++) {
final channel = channelList[i]; final channel = channelList[i];
final targetChannel = targetChannelList[i]; final targetChannel = targetChannelList[i];
@@ -1,4 +1,3 @@
import 'package:meta/meta.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -8,7 +7,7 @@ Matcher isSameMessageAs(Message targetMessage) =>
class _IsSameMessageAs extends Matcher { class _IsSameMessageAs extends Matcher {
const _IsSameMessageAs({ const _IsSameMessageAs({
required this.targetMessage, required this.targetMessage,
}) : assert(targetMessage != null, ''); });
final Message targetMessage; final Message targetMessage;
@@ -27,13 +26,13 @@ Matcher isSameMessageListAs(List<Message> targetMessageList) =>
class _IsSameMessageListAs extends Matcher { class _IsSameMessageListAs extends Matcher {
const _IsSameMessageListAs({ const _IsSameMessageListAs({
required this.targetMessageList, required this.targetMessageList,
}) : assert(targetMessageList != null, ''); });
final List<Message> targetMessageList; final List<Message> targetMessageList;
@override @override
bool matches(covariant List<Message> messageList, Map matchState) { bool matches(covariant List<Message> messageList, Map matchState) {
bool matches = true; var matches = true;
for (var i = 0; i < messageList.length; i++) { for (var i = 0; i < messageList.length; i++) {
final message = messageList[i]; final message = messageList[i];
final targetMessage = targetMessageList[i]; final targetMessage = targetMessageList[i];
@@ -1,4 +1,3 @@
import 'package:meta/meta.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -7,7 +6,7 @@ Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser);
class _IsSameUserAs extends Matcher { class _IsSameUserAs extends Matcher {
const _IsSameUserAs({ const _IsSameUserAs({
required this.targetUser, required this.targetUser,
}) : assert(targetUser != null, ''); });
final User targetUser; final User targetUser;
@@ -25,13 +24,13 @@ Matcher isSameUserListAs(List<User> targetUserList) =>
class _IsSameUserListAs extends Matcher { class _IsSameUserListAs extends Matcher {
const _IsSameUserListAs({ const _IsSameUserListAs({
required this.targetUserList, required this.targetUserList,
}) : assert(targetUserList != null, ''); });
final List<User> targetUserList; final List<User> targetUserList;
@override @override
bool matches(covariant List<User> userList, Map matchState) { bool matches(covariant List<User> userList, Map matchState) {
bool matches = true; var matches = true;
for (var i = 0; i < userList.length; i++) { for (var i = 0; i < userList.length; i++) {
final user = userList[i]; final user = userList[i];
final targetUser = targetUserList[i]; final targetUser = targetUserList[i];
@@ -27,10 +27,9 @@ void main() {
type: 'testType', type: 'testType',
user: users[index], user: users[index],
createdAt: DateTime.now(), createdAt: DateTime.now(),
shadowed: false,
replyCount: index, replyCount: index,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'}, extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index', text: 'Dummy text #$index',
pinned: true, pinned: true,
pinnedAt: DateTime.now(), pinnedAt: DateTime.now(),
@@ -48,10 +47,9 @@ void main() {
user: users[index], user: users[index],
parentId: messages[0].id, parentId: messages[0].id,
createdAt: DateTime.now(), createdAt: DateTime.now(),
shadowed: false,
replyCount: index, replyCount: index,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'}, extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index', text: 'Dummy text #$index',
pinned: true, pinned: true,
pinnedAt: DateTime.now(), pinnedAt: DateTime.now(),
@@ -63,16 +61,15 @@ void main() {
} }
testWidgets( testWidgets(
'should throw if MessageListCore is used where StreamChannel is not present ' '''should throw if MessageListCore is used where StreamChannel is not present in the widget tree''',
'in the widget tree',
(tester) async { (tester) async {
const messageListCoreKey = Key('messageListCore'); const messageListCoreKey = Key('messageListCore');
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
await tester.pumpWidget(messageListCore); await tester.pumpWidget(messageListCore);
@@ -88,10 +85,10 @@ void main() {
const messageListCoreKey = Key('messageListCore'); const messageListCoreKey = Key('messageListCore');
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockChannel = MockChannel(); final mockChannel = MockChannel();
@@ -120,10 +117,10 @@ void main() {
final controller = MessageListController(); final controller = MessageListController();
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
messageListController: controller, messageListController: controller,
); );
@@ -150,16 +147,16 @@ void main() {
); );
testWidgets( testWidgets(
'should assign paginateData callback and paginate data correctly if a MessageListController is passed', '''should assign paginateData callback and paginate data correctly if a MessageListController is passed''',
(tester) async { (tester) async {
const messageListCoreKey = Key('messageListCore'); const messageListCoreKey = Key('messageListCore');
final controller = MessageListController(); final controller = MessageListController();
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
messageListController: controller, messageListController: controller,
); );
@@ -207,10 +204,10 @@ void main() {
const errorWidgetKey = Key('errorWidget'); const errorWidgetKey = Key('errorWidget');
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage( errorBuilder: (BuildContext context, Object error) => const Offstage(
key: errorWidgetKey, key: errorWidgetKey,
), ),
); );
@@ -249,10 +246,11 @@ void main() {
const emptyWidgetKey = Key('emptyWidget'); const emptyWidgetKey = Key('emptyWidget');
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(), messageListBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), emptyBuilder: (BuildContext context) =>
errorBuilder: (BuildContext context, Object error) => Offstage(), const Offstage(key: emptyWidgetKey),
errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockChannel = MockChannel(); final mockChannel = MockChannel();
@@ -289,10 +287,10 @@ void main() {
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
final messageListCore = MessageListCore( final messageListCore = MessageListCore(
key: messageListCoreKey, key: messageListCoreKey,
messageListBuilder: (_, __) => Offstage(key: listWidgetKey), messageListBuilder: (_, __) => const Offstage(key: listWidgetKey),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockChannel = MockChannel(); final mockChannel = MockChannel();
@@ -344,9 +342,9 @@ void main() {
messages.reversed.map((it) => it.id).join(','), messages.reversed.map((it) => it.id).join(','),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockChannel = MockChannel(); final mockChannel = MockChannel();
@@ -391,9 +389,9 @@ void main() {
messages.reversed.map((it) => '${it.parentId}-${it.id}').join(','), messages.reversed.map((it) => '${it.parentId}-${it.id}').join(','),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
parentMessage: parentMessage, parentMessage: parentMessage,
); );
@@ -8,34 +8,33 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'matchers/get_message_response_matcher.dart'; import 'matchers/get_message_response_matcher.dart';
import 'mocks.dart'; import 'mocks.dart';
final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); const testFilter = Filter.custom(operator: '\$test', value: 'testValue');
void main() { void main() {
List<GetMessageResponse> _generateMessages({ List<GetMessageResponse> _generateMessages({
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return GetMessageResponse() return GetMessageResponse()
..message = Message( ..message = Message(
id: 'testId$index', id: 'testId$index',
text: 'testTextData$index', text: 'testTextData$index',
) )
..channel = ChannelModel( ..channel = ChannelModel(
cid: 'testCid:id', cid: 'testCid:id',
); );
}, },
); );
}
testWidgets( testWidgets(
'messageSearchBlocState.search() should throw if used where ' 'messageSearchBlocState.search() should throw if used where '
'StreamChat is not present in the widget tree', 'StreamChat is not present in the widget tree',
(tester) async { (tester) async {
final messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
child: Offstage(), child: Offstage(),
); );
@@ -49,7 +48,7 @@ void main() {
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey, key: messageSearchBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -101,7 +100,7 @@ void main() {
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey, key: messageSearchBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -151,7 +150,7 @@ void main() {
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey, key: messageSearchBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -243,7 +242,7 @@ void main() {
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
final messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey, key: messageSearchBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -6,7 +6,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'mocks.dart'; import 'mocks.dart';
final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); const testFilter = Filter.custom(operator: '\$test', value: 'testValue');
void main() { void main() {
List<GetMessageResponse> _generateMessages({ List<GetMessageResponse> _generateMessages({
@@ -56,10 +56,10 @@ void main() {
const messageSearchListCoreKey = Key('messageSearchListCore'); const messageSearchListCoreKey = Key('messageSearchListCore');
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse> messages) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object? error) => Offstage(), errorBuilder: (BuildContext context, Object? error) => const Offstage(),
filters: testFilter, filters: testFilter,
); );
@@ -86,10 +86,10 @@ void main() {
final controller = MessageSearchListController(); final controller = MessageSearchListController();
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse> messages) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
messageSearchListController: controller, messageSearchListController: controller,
filters: testFilter, filters: testFilter,
); );
@@ -122,10 +122,10 @@ void main() {
const errorWidgetKey = Key('errorWidget'); const errorWidgetKey = Key('errorWidget');
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse> messages) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage( errorBuilder: (BuildContext context, Object error) => const Offstage(
key: errorWidgetKey, key: errorWidgetKey,
), ),
filters: testFilter, filters: testFilter,
@@ -173,10 +173,11 @@ void main() {
const emptyWidgetKey = Key('emptyWidget'); const emptyWidgetKey = Key('emptyWidget');
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse> messages) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), emptyBuilder: (BuildContext context) =>
errorBuilder: (BuildContext context, Object error) => Offstage(), const Offstage(key: emptyWidgetKey),
errorBuilder: (BuildContext context, Object error) => const Offstage(),
filters: testFilter, filters: testFilter,
); );
@@ -223,12 +224,12 @@ void main() {
const childWidgetKey = Key('childWidget'); const childWidgetKey = Key('childWidget');
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage( childBuilder: (List<GetMessageResponse> messages) => const Offstage(
key: childWidgetKey, key: childWidgetKey,
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
filters: testFilter, filters: testFilter,
); );
@@ -283,9 +284,9 @@ void main() {
messages.map((e) => '${e.channel?.cid}-${e.message.id}').join(','), messages.map((e) => '${e.channel?.cid}-${e.message.id}').join(','),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
paginationParams: pagination, paginationParams: pagination,
filters: testFilter, filters: testFilter,
); );
@@ -399,9 +400,10 @@ void main() {
.join(','), .join(','),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) =>
const Offstage(),
paginationParams: pagination.copyWith(limit: limit), paginationParams: pagination.copyWith(limit: limit),
filters: testFilter, filters: testFilter,
); );
@@ -27,10 +27,9 @@ void main() {
type: 'testType', type: 'testType',
user: users[index], user: users[index],
createdAt: DateTime.now(), createdAt: DateTime.now(),
shadowed: false,
replyCount: index, replyCount: index,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'}, extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index', text: 'Dummy text #$index',
pinned: true, pinned: true,
pinnedAt: DateTime.now(), pinnedAt: DateTime.now(),
@@ -48,10 +47,9 @@ void main() {
user: users[index], user: users[index],
parentId: messages[0].id, parentId: messages[0].id,
createdAt: DateTime.now(), createdAt: DateTime.now(),
shadowed: false,
replyCount: index, replyCount: index,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'}, extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index', text: 'Dummy text #$index',
pinned: true, pinned: true,
pinnedAt: DateTime.now(), pinnedAt: DateTime.now(),
@@ -72,7 +70,7 @@ void main() {
final streamChannel = StreamChannel( final streamChannel = StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChannel); await tester.pumpWidget(streamChannel);
@@ -91,10 +89,10 @@ void main() {
final streamChannel = StreamChannel( final streamChannel = StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
final errorMessage = 'Error! Error! Error!'; const errorMessage = 'Error! Error! Error!';
final error = DioError( final error = DioError(
type: DioErrorType.response, type: DioErrorType.response,
error: errorMessage, error: errorMessage,
@@ -128,8 +126,7 @@ void main() {
final streamChannel = StreamChannel( final streamChannel = StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
showLoading: true,
); );
when(() => mockChannel.initialized).thenAnswer((_) async => false); when(() => mockChannel.initialized).thenAnswer((_) async => false);
@@ -158,8 +155,8 @@ void main() {
final streamChannel = StreamChannel( final streamChannel = StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
child: Offstage(key: childKey),
initialMessageId: 'testInitialMessageId', initialMessageId: 'testInitialMessageId',
child: const Offstage(key: childKey),
); );
when(() => mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
@@ -213,8 +210,8 @@ void main() {
StreamChannel( StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
child: Offstage(key: childKey),
initialMessageId: initialMessageId, initialMessageId: initialMessageId,
child: const Offstage(key: childKey),
); );
final beforePagination = PaginationParams( final beforePagination = PaginationParams(
@@ -23,7 +23,7 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -42,7 +42,7 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -63,7 +63,7 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -71,7 +71,8 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
when(() => mockClient.closeConnection()).thenAnswer((_) async { // ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return; return;
}); });
@@ -79,9 +80,10 @@ void main() {
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused);
verify(() => mockClient.closeConnection()).called(1); verify(mockClient.closeConnection).called(1);
}, },
); );
@@ -93,16 +95,16 @@ void main() {
await tester.runAsync(() async { await tester.runAsync(() async {
final mockClient = MockClient(); final mockClient = MockClient();
final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived();
const backgroundKeepAlive = const Duration(seconds: 3); const backgroundKeepAlive = Duration(seconds: 3);
const streamChatCoreKey = Key('streamChatCore'); const streamChatCoreKey = Key('streamChatCore');
const childKey = Key('child'); const childKey = Key('child');
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
onBackgroundEventReceived: mockOnBackgroundEventReceived, onBackgroundEventReceived: mockOnBackgroundEventReceived,
backgroundKeepAlive: backgroundKeepAlive, backgroundKeepAlive: backgroundKeepAlive,
connectivityStream: Stream.value(ConnectivityResult.mobile), connectivityStream: Stream.value(ConnectivityResult.mobile),
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -111,8 +113,9 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(type: EventType.any); final event = Event(type: EventType.any);
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.closeConnection()).thenAnswer((_) async { // ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return; return;
}); });
@@ -120,6 +123,7 @@ void main() {
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
@@ -129,7 +133,7 @@ void main() {
await Future.delayed(backgroundKeepAlive); await Future.delayed(backgroundKeepAlive);
verify(() => mockClient.closeConnection()).called(1); verify(mockClient.closeConnection).called(1);
verifyNever(() => mockOnBackgroundEventReceived.call(event)); verifyNever(() => mockOnBackgroundEventReceived.call(event));
}); });
}, },
@@ -143,15 +147,15 @@ void main() {
await tester.runAsync(() async { await tester.runAsync(() async {
final mockClient = MockClient(); final mockClient = MockClient();
final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived(); final mockOnBackgroundEventReceived = MockOnBackgroundEventReceived();
const backgroundKeepAlive = const Duration(seconds: 3); const backgroundKeepAlive = Duration(seconds: 3);
const streamChatCoreKey = Key('streamChatCore'); const streamChatCoreKey = Key('streamChatCore');
const childKey = Key('child'); const childKey = Key('child');
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
onBackgroundEventReceived: mockOnBackgroundEventReceived, onBackgroundEventReceived: mockOnBackgroundEventReceived,
backgroundKeepAlive: backgroundKeepAlive, backgroundKeepAlive: backgroundKeepAlive,
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -160,12 +164,13 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(type: EventType.any); final event = Event(type: EventType.any);
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
final streamChatCoreState = tester.state<StreamChatCoreState>( final streamChatCoreState = tester.state<StreamChatCoreState>(
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
@@ -193,8 +198,8 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
connectivityStream: Stream.value(ConnectivityResult.mobile), connectivityStream: Stream.value(ConnectivityResult.mobile),
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -203,10 +208,14 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(type: EventType.any); final event = Event(type: EventType.any);
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.openConnection()) when(mockClient.openConnection)
.thenAnswer((_) async => OwnUser(id: 'test')); .thenAnswer((_) async => OwnUser(id: 'test'));
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
// ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return;
});
when(() => mockClient.wsConnectionStatus) when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected); .thenReturn(ConnectionStatus.disconnected);
@@ -214,6 +223,7 @@ void main() {
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
@@ -222,7 +232,7 @@ void main() {
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.resumed); .didChangeAppLifecycleState(AppLifecycleState.resumed);
verify(() => mockClient.openConnection()).called(1); verify(mockClient.openConnection).called(1);
}); });
}, },
); );
@@ -238,18 +248,22 @@ void main() {
const childKey = Key('child'); const childKey = Key('child');
final event = Event(); final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.openConnection()) when(mockClient.openConnection)
.thenAnswer((_) async => OwnUser(id: 'test')); .thenAnswer((_) async => OwnUser(id: 'test'));
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
// ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return;
});
when(() => mockClient.wsConnectionStatus) when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected); .thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
connectivityStream: Stream.value(ConnectivityResult.none), connectivityStream: Stream.value(ConnectivityResult.none),
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -261,6 +275,7 @@ void main() {
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
@@ -269,7 +284,7 @@ void main() {
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.resumed); .didChangeAppLifecycleState(AppLifecycleState.resumed);
verifyNever(() => mockClient.openConnection()); verifyNever(mockClient.openConnection);
}); });
}, },
); );
@@ -287,7 +302,7 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey), child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -310,9 +325,7 @@ void main() {
emits(ownUser), emits(ownUser),
); );
addTearDown(() { addTearDown(userController.close);
userController.close();
});
}); });
}, },
); );
@@ -328,18 +341,22 @@ void main() {
BehaviorSubject.seeded(ConnectivityResult.none); BehaviorSubject.seeded(ConnectivityResult.none);
final event = Event(); final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.openConnection()) when(mockClient.openConnection)
.thenAnswer((_) async => OwnUser(id: 'test')); .thenAnswer((_) async => OwnUser(id: 'test'));
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
// ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return;
});
when(() => mockClient.wsConnectionStatus) when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected); .thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
connectivityStream: _connectivityController.stream, connectivityStream: _connectivityController.stream,
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -351,7 +368,9 @@ void main() {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
verify(() => mockClient.openConnection()).called(1); verify(mockClient.openConnection).called(1);
addTearDown(_connectivityController.close);
}); });
}, },
); );
@@ -368,8 +387,8 @@ void main() {
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
connectivityStream: _connectivityController.stream, connectivityStream: _connectivityController.stream,
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -378,10 +397,14 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(); final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.openConnection()) when(mockClient.openConnection)
.thenAnswer((_) async => OwnUser(id: 'test')); .thenAnswer((_) async => OwnUser(id: 'test'));
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
// ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return;
});
when(() => mockClient.wsConnectionStatus) when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.connected); .thenReturn(ConnectionStatus.connected);
@@ -389,7 +412,9 @@ void main() {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
verify(() => mockClient.closeConnection()).called(1); verify(mockClient.closeConnection).called(1);
addTearDown(_connectivityController.close);
}); });
}, },
); );
@@ -405,18 +430,22 @@ void main() {
BehaviorSubject.seeded(ConnectivityResult.none); BehaviorSubject.seeded(ConnectivityResult.none);
final event = Event(); final event = Event();
when(() => mockClient.on()).thenAnswer((_) => Stream.value(event)); when(mockClient.on).thenAnswer((_) => Stream.value(event));
when(() => mockClient.openConnection()) when(mockClient.openConnection)
.thenAnswer((_) async => OwnUser(id: 'test')); .thenAnswer((_) async => OwnUser(id: 'test'));
when(() => mockClient.closeConnection()).thenAnswer((_) async => null);
// ignore: prefer_expression_function_bodies
when(mockClient.closeConnection).thenAnswer((_) async {
return;
});
when(() => mockClient.wsConnectionStatus) when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected); .thenReturn(ConnectionStatus.disconnected);
final streamChatCore = StreamChatCore( final streamChatCore = StreamChatCore(
key: streamChatCoreKey, key: streamChatCoreKey,
client: mockClient, client: mockClient,
child: Offstage(key: childKey),
connectivityStream: _connectivityController.stream, connectivityStream: _connectivityController.stream,
child: const Offstage(key: childKey),
); );
await tester.pumpWidget(streamChatCore); await tester.pumpWidget(streamChatCore);
@@ -428,6 +457,7 @@ void main() {
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
); );
// ignore: cascade_invocations
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
@@ -437,7 +467,9 @@ void main() {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
verifyNever(() => mockClient.closeConnection()); verifyNever(mockClient.closeConnection);
addTearDown(_connectivityController.close);
}); });
}, },
); );
@@ -12,26 +12,24 @@ void main() {
List<User> _generateUsers({ List<User> _generateUsers({
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return User( return User(
id: 'testId$index', id: 'testId$index',
role: 'testRole$index', role: 'testRole$index',
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
lastActive: DateTime.now(), lastActive: DateTime.now(),
online: true, online: true,
banned: false, extraData: {
extraData: { 'name': '${alphabets[index]}-testName',
'name': '${alphabets[index]}-testName', },
}, );
); },
}, );
);
}
testWidgets( testWidgets(
'should throw if UserListCore is used where UsersBloc is not present ' 'should throw if UserListCore is used where UsersBloc is not present '
@@ -40,10 +38,10 @@ void main() {
const userListCoreKey = Key('userListCore'); const userListCoreKey = Key('userListCore');
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
await tester.pumpWidget(userListCore); await tester.pumpWidget(userListCore);
@@ -59,10 +57,10 @@ void main() {
const userListCoreKey = Key('userListCore'); const userListCoreKey = Key('userListCore');
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockClient = MockClient(); final mockClient = MockClient();
@@ -88,10 +86,10 @@ void main() {
final controller = UserListController(); final controller = UserListController();
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
userListController: controller, userListController: controller,
); );
@@ -122,9 +120,9 @@ void main() {
const errorWidgetKey = Key('errorWidget'); const errorWidgetKey = Key('errorWidget');
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => errorBuilder: (BuildContext context, Object error) =>
Container(key: errorWidgetKey), Container(key: errorWidgetKey),
); );
@@ -168,10 +166,10 @@ void main() {
const emptyWidgetKey = Key('emptyWidget'); const emptyWidgetKey = Key('emptyWidget');
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Offstage(), listBuilder: (_, __) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockClient = MockClient(); final mockClient = MockClient();
@@ -214,9 +212,9 @@ void main() {
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, __) => Container(key: listWidgetKey), listBuilder: (_, __) => Container(key: listWidgetKey),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
); );
final mockClient = MockClient(); final mockClient = MockClient();
@@ -261,20 +259,20 @@ void main() {
listBuilder: (_, items) => Container( listBuilder: (_, items) => Container(
key: listWidgetKey, key: listWidgetKey,
child: ListView( child: ListView(
children: items.map((e) { children: items
return Container( .map((e) => Container(
key: Key(e.key ?? ''), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
), ),
); ))
}).toList(growable: false), .toList(growable: false),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
groupAlphabetically: true, groupAlphabetically: true,
); );
@@ -329,20 +327,20 @@ void main() {
listBuilder: (_, items) => Container( listBuilder: (_, items) => Container(
key: listWidgetKey, key: listWidgetKey,
child: ListView( child: ListView(
children: items.map((e) { children: items
return Container( .map((e) => Container(
key: Key(e.key ?? ''), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
), ),
); ))
}).toList(growable: false), .toList(growable: false),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(),
pagination: pagination, pagination: pagination,
groupAlphabetically: true, groupAlphabetically: true,
); );
@@ -425,7 +423,7 @@ void main() {
const pagination = PaginationParams(); const pagination = PaginationParams();
StateSetter? _stateSetter; StateSetter? _stateSetter;
int limit = pagination.limit; var limit = pagination.limit;
const userListCoreKey = Key('userListCore'); const userListCoreKey = Key('userListCore');
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
@@ -434,20 +432,21 @@ void main() {
listBuilder: (_, items) => Container( listBuilder: (_, items) => Container(
key: listWidgetKey, key: listWidgetKey,
child: ListView( child: ListView(
children: items.map((e) { children: items
return Container( .map((e) => Container(
key: Key(e.key ?? ''), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
), ),
); ))
}).toList(growable: false), .toList(growable: false),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) =>
const Offstage(),
pagination: pagination.copyWith(limit: limit), pagination: pagination.copyWith(limit: limit),
groupAlphabetically: true, groupAlphabetically: true,
); );
@@ -12,30 +12,28 @@ void main() {
List<User> _generateUsers({ List<User> _generateUsers({
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return User( return User(
id: 'testId$index', id: 'testId$index',
role: 'testRole$index', role: 'testRole$index',
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
lastActive: DateTime.now(), lastActive: DateTime.now(),
online: true, online: true,
banned: false, extraData: const {'extra_data_key': 'extraDataValue'},
extraData: {'extra_data_key': 'extraDataValue'}, );
); },
}, );
);
}
testWidgets( testWidgets(
'usersBlocState.queryUsers() should throw if used where ' 'usersBlocState.queryUsers() should throw if used where '
'StreamChat is not present in the widget tree', 'StreamChat is not present in the widget tree',
(tester) async { (tester) async {
final usersBloc = UsersBloc( const usersBloc = UsersBloc(
child: Offstage(), child: Offstage(),
); );
@@ -49,7 +47,7 @@ void main() {
(tester) async { (tester) async {
const usersBlocKey = Key('usersBloc'); const usersBlocKey = Key('usersBloc');
const childKey = Key('child'); const childKey = Key('child');
final usersBloc = UsersBloc( const usersBloc = UsersBloc(
key: usersBlocKey, key: usersBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -98,7 +96,7 @@ void main() {
(tester) async { (tester) async {
const usersBlocKey = Key('usersBloc'); const usersBlocKey = Key('usersBloc');
const childKey = Key('child'); const childKey = Key('child');
final usersBloc = UsersBloc( const usersBloc = UsersBloc(
key: usersBlocKey, key: usersBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -116,7 +114,7 @@ void main() {
find.byKey(usersBlocKey), find.byKey(usersBlocKey),
); );
final error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
@@ -148,7 +146,7 @@ void main() {
(tester) async { (tester) async {
const usersBlocKey = Key('usersBloc'); const usersBlocKey = Key('usersBloc');
const childKey = Key('child'); const childKey = Key('child');
final usersBloc = UsersBloc( const usersBloc = UsersBloc(
key: usersBlocKey, key: usersBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -231,7 +229,7 @@ void main() {
(tester) async { (tester) async {
const usersBlocKey = Key('usersBloc'); const usersBlocKey = Key('usersBloc');
const childKey = Key('child'); const childKey = Key('child');
final usersBloc = UsersBloc( const usersBloc = UsersBloc(
key: usersBlocKey, key: usersBlocKey,
child: Offstage(key: childKey), child: Offstage(key: childKey),
); );
@@ -275,7 +273,7 @@ void main() {
final offset = users.length; final offset = users.length;
final pagination = PaginationParams(offset: offset); final pagination = PaginationParams(offset: offset);
final error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
@@ -3,8 +3,8 @@ import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart';
Future<void> main() async { Future<void> main() async {
/// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// Create a new instance of [StreamChatClient] passing the apikey obtained
/// project dashboard. /// from your project dashboard.
final client = StreamChatClient('b67pax5b2wdq'); final client = StreamChatClient('b67pax5b2wdq');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@@ -22,12 +22,13 @@ Future<void> main() async {
await client.connectUser( await client.connectUser(
User( User(
id: 'cool-shadow-7', id: 'cool-shadow-7',
extraData: { extraData: const {
'image': 'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow', 'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
}, },
), ),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.'
'gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
); );
/// Creates a channel using the type `messaging` and `godevs`. /// Creates a channel using the type `messaging` and `godevs`.
@@ -66,12 +67,10 @@ class StreamExample extends StatelessWidget {
final Channel channel; final Channel channel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( title: 'Stream Chat Dart Example',
title: 'Stream Chat Dart Example', home: HomeScreen(channel: channel),
home: HomeScreen(channel: channel), );
);
}
} }
/// Main screen of our application. The layout is comprised of an [AppBar] /// Main screen of our application. The layout is comprised of an [AppBar]
@@ -176,83 +175,81 @@ class _MessageViewState extends State<MessageView> {
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Column(
return Column( children: [
children: [ Expanded(
Expanded( child: ListView.builder(
child: ListView.builder( controller: _scrollController,
controller: _scrollController, itemCount: _messages.length,
itemCount: _messages.length, reverse: true,
reverse: true, itemBuilder: (BuildContext context, int index) {
itemBuilder: (BuildContext context, int index) { final item = _messages[index];
final item = _messages[index]; if (item.user?.id == widget.channel.client.uid) {
if (item.user?.id == widget.channel.client.uid) { return Align(
return Align( alignment: Alignment.centerRight,
alignment: Alignment.centerRight, child: Padding(
child: Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Text(item.text ?? ''),
child: Text(item.text ?? ''), ),
), );
); } else {
} else { return Align(
return Align( alignment: Alignment.centerLeft,
alignment: Alignment.centerLeft, child: Padding(
child: Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Text(item.text ?? ''),
child: Text(item.text ?? ''), ),
), );
); }
} },
}, ),
), ),
), Padding(
Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8), child: Row(
child: Row( children: [
children: [ Expanded(
Expanded( child: TextField(
child: TextField( controller: _controller,
controller: _controller, decoration: const InputDecoration(
decoration: const InputDecoration( hintText: 'Enter your message',
hintText: 'Enter your message',
),
),
),
Material(
type: MaterialType.circle,
color: Colors.blue,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
// We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
), ),
), ),
), ),
) Material(
], type: MaterialType.circle,
), color: Colors.blue,
) clipBehavior: Clip.hardEdge,
], child: InkWell(
); onTap: () async {
} // We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
),
)
],
),
)
],
);
} }
/// Helper extension for quickly retrieving /// Helper extension for quickly retrieving
@@ -8,9 +8,9 @@ environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
dependencies: dependencies:
cupertino_icons: ^1.0.3
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.0
stream_chat_persistence: stream_chat_persistence:
path: ../ path: ../
@@ -213,8 +213,8 @@ void main() {
test('should return sorted channels using custom field', () async { test('should return sorted channels using custom field', () async {
int sortComparator(ChannelModel a, ChannelModel b) { int sortComparator(ChannelModel a, ChannelModel b) {
final aData = a.extraData['test_custom_field'] as int; final aData = int.parse(a.extraData['test_custom_field'].toString());
final bData = b.extraData['test_custom_field'] as int; final bData = int.parse(b.extraData['test_custom_field'].toString());
return bData.compareTo(aData); return bData.compareTo(aData);
} }
@@ -173,8 +173,8 @@ void main() {
expect(entity.shadowed, message.shadowed); expect(entity.shadowed, message.shadowed);
expect(entity.showInChannel, message.showInChannel); expect(entity.showInChannel, message.showInChannel);
expect(entity.replyCount, message.replyCount); expect(entity.replyCount, message.replyCount);
expect(entity.mentionedUsers, expect(
message.mentionedUsers.map((e) => jsonEncode(e)).toList()); entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList());
expect(entity.reactionScores, message.reactionScores); expect(entity.reactionScores, message.reactionScores);
expect(entity.reactionCounts, message.reactionCounts); expect(entity.reactionCounts, message.reactionCounts);
expect(entity.status, message.status); expect(entity.status, message.status);
@@ -39,7 +39,7 @@ void main() {
lastActive: DateTime.now(), lastActive: DateTime.now(),
online: math.Random().nextBool(), online: math.Random().nextBool(),
banned: math.Random().nextBool(), banned: math.Random().nextBool(),
extraData: {'test_extra_data': 'extraData'}, extraData: const {'test_extra_data': 'extraData'},
); );
final entity = user.toEntity(); final entity = user.toEntity();
expect(entity, isA<UserEntity>()); expect(entity, isA<UserEntity>());