diff --git a/example/lib/main.dart b/example/lib/main.dart index 38de0083..4319e839 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -50,50 +50,32 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Container( - child: ChannelListPage(client), + child: StreamChat( + client: client, + child: ChannelListPage(), + ), ), ); } } class ChannelListPage extends StatelessWidget { - final Client client; - - ChannelListPage(this.client); - @override Widget build(BuildContext context) { return Scaffold( - body: PageView( - children: [ - StreamChat( - client: client, - child: Builder( - builder: (context) => ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, - ), - channelWidget: ChannelPage(), - ), - ), + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, ), - StreamChat( - client: client, - child: ChannelListView( - sort: [SortOption('last_message_at')], - pagination: PaginationParams( - limit: 20, - ), - channelWidget: ChannelPage(), - ), - ), - ], + channelWidget: ChannelPage(), + ), ), ); } diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 580d4d84..b18b60d3 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/channels_bloc.dart'; import '../stream_chat_flutter.dart'; import 'channel_preview.dart'; @@ -104,11 +105,11 @@ class _ChannelListViewState extends State @override Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); return RefreshIndicator( onRefresh: () async { - return streamChat.queryChannels( + return channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -116,7 +117,7 @@ class _ChannelListViewState extends State ); }, child: StreamBuilder>( - stream: streamChat.channelsStream, + stream: channelsProvider.channelsStream, builder: (context, snapshot) { if (snapshot.hasError) { if (snapshot.error is Error) { @@ -164,7 +165,7 @@ class _ChannelListViewState extends State ), FlatButton( onPressed: () { - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -212,11 +213,11 @@ class _ChannelListViewState extends State i = i ~/ 2; - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; - final channelClient = streamChat.channels.firstWhere( + final channelClient = channelsProvider.channels.firstWhere( (c) => c.cid == channel.cid, orElse: () => null, ); @@ -280,13 +281,14 @@ class _ChannelListViewState extends State ), ); } else { - return _buildQueryProgressIndicator(context, streamChat); + return _buildQueryProgressIndicator(context, channelsProvider); } } - Widget _buildQueryProgressIndicator(context, StreamChatState streamChat) { + Widget _buildQueryProgressIndicator( + context, ChannelsBlocState channelsProvider) { return StreamBuilder( - stream: streamChat.queryChannelsLoading, + stream: channelsProvider.queryChannelsLoading, initialData: false, builder: (context, snapshot) { if (snapshot.hasError) { @@ -318,14 +320,14 @@ class _ChannelListViewState extends State ); } - void _listenChannelPagination(StreamChatState streamChat) { + void _listenChannelPagination(ChannelsBlocState channelsProvider) { if (_scrollController.position.maxScrollExtent == _scrollController.offset) { - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination.copyWith( - offset: streamChat.channels.length, + offset: channelsProvider.channels.length, ), options: widget.options, ); @@ -336,11 +338,11 @@ class _ChannelListViewState extends State void initState() { super.initState(); - final streamChat = StreamChat.of(context); + final channelsProvider = ChannelsBloc.of(context); WidgetsBinding.instance.addObserver(this); - streamChat.queryChannels( + channelsProvider.queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -348,14 +350,14 @@ class _ChannelListViewState extends State ); _scrollController.addListener(() { - _listenChannelPagination(streamChat); + _listenChannelPagination(channelsProvider); }); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - StreamChat.of(context).queryChannels( + ChannelsBloc.of(context).queryChannels( filter: widget.filter, sortOptions: widget.sort, paginationParams: widget.pagination, @@ -370,7 +372,4 @@ class _ChannelListViewState extends State WidgetsBinding.instance.removeObserver(this); super.dispose(); } - - @override - bool get wantKeepAlive => true; } diff --git a/lib/src/channels_bloc.dart b/lib/src/channels_bloc.dart new file mode 100644 index 00000000..b7dec76d --- /dev/null +++ b/lib/src/channels_bloc.dart @@ -0,0 +1,130 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; + +class ChannelsBloc extends StatefulWidget { + final Widget child; + + const ChannelsBloc({ + Key key, + this.child, + }) : super(key: key); + + @override + ChannelsBlocState createState() => ChannelsBlocState(); + + static ChannelsBlocState of(BuildContext context) { + ChannelsBlocState streamChatState; + + streamChatState = context.findAncestorStateOfType(); + + if (streamChatState == null) { + throw Exception('You must have a ChannelsProvider widget as anchestor'); + } + + return streamChatState; + } +} + +class ChannelsBlocState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + /// The current channel list + List get channels => _channelsController.value; + + /// The current channel list as a stream + Stream> get channelsStream => _channelsController.stream; + + final BehaviorSubject _queryChannelsLoadingController = + BehaviorSubject.seeded(false); + + final BehaviorSubject> _channelsController = + BehaviorSubject.seeded([]); + + /// The stream notifying the state of queryChannel call + Stream get queryChannelsLoading => + _queryChannelsLoadingController.stream; + + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream + Future queryChannels({ + Map filter, + List sortOptions, + PaginationParams paginationParams, + Map options, + bool onlyOffline = false, + }) async { + if (_queryChannelsLoadingController.value == true) { + return; + } + _queryChannelsLoadingController.sink.add(true); + + try { + final clear = paginationParams == null || + paginationParams.offset == null || + paginationParams.offset == 0; + final oldChannels = List.from(channels); + StreamChat.of(context) + .client + .queryChannels( + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, + onlyOffline: onlyOffline, + ) + .listen((channels) { + if (clear) { + _channelsController.add(channels); + } else { + final l = oldChannels + channels; + _channelsController.add(l); + } + }, onDone: () { + _queryChannelsLoadingController.sink.add(false); + }, onError: (err, stackTrace) { + print(err); + print(stackTrace); + _queryChannelsLoadingController.addError(err, stackTrace); + }); + } catch (err, stackTrace) { + _queryChannelsLoadingController.addError(err, stackTrace); + } + } + + StreamSubscription _newMessagesSubscription; + + @override + void initState() { + super.initState(); + + _newMessagesSubscription = + StreamChat.of(context).client.on(EventType.messageNew).listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + _channelsController.add(newChannels); + } + }); + } + + @override + void dispose() { + _channelsController.close(); + _queryChannelsLoadingController.close(); + _newMessagesSubscription.cancel(); + super.dispose(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/src/stream_chat.dart b/lib/src/stream_chat.dart index 3d0ffa50..915562f8 100644 --- a/lib/src/stream_chat.dart +++ b/lib/src/stream_chat.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -59,15 +58,12 @@ class StreamChat extends StatefulWidget { } } -class StreamChatState extends State - with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { +class StreamChatState extends State with WidgetsBindingObserver { Client get client => widget.client; final GlobalKey _navigatorKey = GlobalKey(); @override Widget build(BuildContext context) { - super.build(context); - final theme = _getTheme(context, widget.streamChatThemeData); return StreamChatTheme( data: theme, @@ -179,83 +175,9 @@ class StreamChatState extends State /// The current user as a stream Stream get userStream => widget.client.state.userStream; - /// The current channel list - List get channels => _channelsController.value; - - /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; - - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); - - final BehaviorSubject> _channelsController = - BehaviorSubject.seeded([]); - - /// The stream notifying the state of queryChannel call - Stream get queryChannelsLoading => - _queryChannelsLoadingController.stream; - - /// Calls [client.queryChannels] updating [queryChannelsLoading] stream - Future queryChannels({ - Map filter, - List sortOptions, - PaginationParams paginationParams, - Map options, - bool onlyOffline = false, - }) async { - if (_queryChannelsLoadingController.value == true) { - return; - } - _queryChannelsLoadingController.sink.add(true); - - try { - final clear = paginationParams == null || - paginationParams.offset == null || - paginationParams.offset == 0; - final oldChannels = List.from(channels); - client - .queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, - onlyOffline: onlyOffline, - ) - .listen((channels) { - if (clear) { - _channelsController.add(channels); - } else { - final l = oldChannels + channels; - _channelsController.add(l); - } - }, onDone: () { - _queryChannelsLoadingController.sink.add(false); - }, onError: (err, stackTrace) { - print(err); - print(stackTrace); - _queryChannelsLoadingController.addError(err, stackTrace); - }); - } catch (err, stackTrace) { - _queryChannelsLoadingController.addError(err, stackTrace); - } - } - - StreamSubscription _newMessagesSubscription; - @override void initState() { super.initState(); - - _newMessagesSubscription = client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - _channelsController.add(newChannels); - } - }); - WidgetsBinding.instance.addObserver(this); } @@ -274,14 +196,6 @@ class StreamChatState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); - - _channelsController.close(); - _queryChannelsLoadingController.close(); - _newMessagesSubscription.cancel(); - super.dispose(); } - - @override - bool get wantKeepAlive => true; } diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 857336ce..d777ffff 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -5,6 +5,7 @@ export 'src/channel_image.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; +export 'src/channels_bloc.dart'; export 'src/message_input.dart'; export 'src/message_list_view.dart'; export 'src/message_widget.dart';