Stream Firestore data with Riverpod in Flutter

Stream Firestore data with Riverpod in Flutter

Flutter and Firebase are widely used together. However, when adding state management such as Riverpod there are not many examples available to demonstrate how to implement them together. In this blog, I will share how I stream data from Firebase with Riverpod as state management in Flutter. It might not be the best implementation but this works well enough and is easy to understand and use. Also, as usual, I will provide the link to the resources I referenced in the credits section.

What I wanted to achieve

In the Coolors Clone app, users can save their favorite color and be able to use it later. A very simple feature.

Collection and field store in Firestore.

Using Riverpod StreamProvider with Firebase query snapshot.

I create StreamProvider in one of the logic files called db_logic.dart. with explanation comments in another code snippet.

final savedColorStreamProvider = StreamProvider<List<ColorDoc>>((ref) async* {

  final FirebaseFirestore firestore = FirebaseFirestore.instance;
  var allColorDoc = const <ColorDoc>[];

  final user = ref.watch(authStateProvider).value;

  await for (var snapshot in firestore
      .collection(kSavedColors)
      .where('createdBy', isEqualTo: user!.uid)
      .snapshots()) {
    allColorDoc = [];

    for (DocumentSnapshot colorDoc in snapshot.docs) {
      Color color = hexToColor(colorDoc.get('colorHex'));

      var savedColor = ColorDoc(colorDoc.id, color);

      allColorDoc = [...allColorDoc, savedColor];
      yield allColorDoc;
    }
  }
});

Next, with comments explanation.

//Create stream provider that return stream list of ColorDoc objects. ColorDoc is a model class in models.dart.
final savedColorStreamProvider = StreamProvider<List<ColorDoc>>((ref) async* {

  //create instance of Firestore and variable to hold return data.
  final FirebaseFirestore firestore = FirebaseFirestore.instance;
  var allColorDoc = const <ColorDoc>[];

  //get auth state from auth state provider, which is another provider in the app.
  final user = ref.watch(authStateProvider).value;

  //Each Snapshot contain list of documents queried from Firestore.
  await for (var snapshot in firestore
      .collection(kSavedColors)//- kSavedColors = 'SavedColors', which I used as constants through out the app to minimize type error.  
      .where('createdBy', isEqualTo: user!.uid) //Query snapshot with user id
      .snapshots()) {
    //everytime data is updated new snapshot will be created.
    allColorDoc = [];

    //iterate through each documents in a snapshot
    for (DocumentSnapshot colorDoc in snapshot.docs) {
      Color color = hexToColor(colorDoc.get('colorHex'));

      var savedColor = ColorDoc(colorDoc.id, color);

      allColorDoc = [...allColorDoc, savedColor];
      yield allColorDoc; //similar to return but will not terminate the function
    }
  }
});

Create List<widgets> from StreamProvider

Now that we can stream saved colors from the provider we will create a list of widgets with this stream. To make code easier to read, I will remove code that is not related to this topic. If you want to see a full code I will provide a link to this repo at the end.

import 'package:coolors_inspired_flutter/logics/db_logic.dart';
import 'package:coolors_inspired_flutter/models.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';

class ColorPickerTab extends ConsumerWidget {
  const ColorPickerTab({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    //call provider and store stream value, ref.watch will watch for changes and return new value.
    final userSavedColors = ref.watch(savedColorStreamProvider);

    return Center(
      child: userSavedColors.when(//return listview when data is available.
        data: (savedColors) {
          return ListView.builder(
            reverse: false,
            itemCount: savedColors.length,
            itemBuilder: (context, index) {
              ColorDoc color = savedColors[index];
              return SavedColorRow(
                  //Custom Widget
                  color: color.color,
                  notifier: colorlistNofifier,
                  activeIndex: activeIndex,
                  docId: color.docId,
                  db: db);
            },
          );
        },
        error: (error, stackTrace) => Text(error.toString()),
        loading: () => const CircularProgressIndicator(),
      ),
    );
  }
}

Link to the files in git:

The solution might be overkilled for just simply getting saved data from the database. But I think it is more convenient that we do not need to worry to update the UI every time data is changed or calling the data every time that we build the UI. I am sure that this code is not perfect but it could give you a general idea and example code of how to integrate Flutter, Firestore and Riverpod, at least as a starting point.

Credits

Below are the resources I referenced to develop this solution.