Skip to main content

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(),
};
}