StreamProvider
StreamProvider
is similar to FutureProvider but for Streams instead of
Futures.
StreamProvider
is usually used for:
- listening to Firebase or web-sockets
- rebuilding another provider every few seconds
Since Streams naturally expose a way for listening to updates, some may think
that using StreamProvider
has a low value. In particular, you may believe that
Flutter's StreamBuilder would work just as well for listening to a Stream, but
this is a mistake.
Using StreamProvider
over StreamBuilder has numerous benefits:
- it allows other providers to listen to the stream using ref.watch.
- it ensures that loading and error cases are properly handled, thanks to AsyncValue.
- it removes the need for having to differentiate broadcast streams vs normal streams.
- it caches the latest value emitted by the stream, ensuring that if a listener is added after an event is emitted, the listener will still have immediate access to the most up-to-date event.
- it allows easily mocking the stream during tests by overriding the
StreamProvider
.
Usage example: live chat using sockets
StreamProvider
is used in when we handle stream of asynchronous data
such as Video Streaming, Weather broadcasting Apis or Live chat as follows:
Stream<List<String>> chat(ChatRef ref) async* {
// Connect to an API using sockets, and decode the output
final socket = await Socket.connect('my-api', 4242);
ref.onDispose(socket.close);
var allMessages = const <String>[];
await for (final message in socket.map(utf8.decode)) {
// A new message has been received. Let's add it to the list of all messages.
allMessages = [...allMessages, message];
yield allMessages;
}
}
Then, the UI can listen to live streaming chats like so:
Widget build(BuildContext context, WidgetRef ref) {
final liveChats = ref.watch(chatProvider);
// Like FutureProvider, it is possible to handle loading/error states using AsyncValue.when
return switch (liveChats) {
// Display all the messages in a scrollable list view.
AsyncData(:final value) => ListView.builder(
// Show messages from bottom to top
reverse: true,
itemCount: value.length,
itemBuilder: (context, index) {
final message = value[index];
return Text(message);
},
),
AsyncError(:final error) => Text(error.toString()),
_ => const CircularProgressIndicator(),
};
}