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