Riverpod Provider to pass down FireStore document Id to other pages in app flow

587 Views Asked by At

I have an archaic solution for my problem, but it's quickly become cumbersome and not as robust. I want to switch to a riverpod provider and am studying how to do so, but I want to put this question out there at the same time. For simplicity, my app is similar to BlaBlaCar. Lots of trips listed, and then users can request to be picked up. The general flow goes like this:

enter image description here

To call the same document ID from Firestore, my solution has been just to pass a detailDocument parameter over and over. In my trips page, I have a streambuilder that displays the document:

StreamBuilder<QuerySnapshot<Object?>> tripStreamBuilder() {
    return StreamBuilder<QuerySnapshot>(
      stream: tripStream.snapshots(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }
        return ListView(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            Map<String, dynamic> data =
                document.data()! as Map<String, dynamic>;
            return Card(
              child: ListTile(
                // leading: Text((data['departureDate']).toString()),
                title: Text('From ${data['departure']}'),
                subtitle: Text('To ${data['arrival']}'),
                trailing: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Column(
                    children: [
                      Text((data['departureDate']).toString()),
                      Text('Leaving at ${data['departureTime']}'),
                    ],
                  ),
                ),

                onTap: () {
                  Navigator.of(context, rootNavigator: true)
                      .push(MaterialPageRoute(
                          builder: (context) => TripDetails(
                                detailDocument: document,
                              )));
                },
              ),
            );
          }).toList(),
        );
      },
    );

From here, I just keep making detailDocument a parameter. For exmaple in my trip details:

class TripDetails extends ConsumerStatefulWidget {
  const TripDetails({super.key, required this.detailDocument});
  final DocumentSnapshot detailDocument;

And jointrip class:

class JoinTrip extends ConsumerStatefulWidget {
  const JoinTrip({
    super.key,
    required this.detailDocument,
  });
  final DocumentSnapshot detailDocument;

So my question is how can I pass the document ID into a riverpod provider? And one that gets disposed and reset as users click on different trips.

2

There are 2 best solutions below

4
On BEST ANSWER

You shouldn't pass the id of your document to pages. You can check this example from the official documentation.

In you case, you can have a provider that saves the id. So whenever you need to fetch or do something about the same id, you watch its value and when you need to change it, you can change the id in that provider.

Let's say we have two providers as follow:

// id provider
final tripIdProvider = StateProvider<String?>((ref) => null);

// Another provider fetching the document for an id
final tripProvider = Provider((ref) {
  final id = ref.watch(tripIdProvider);
  // code to call the Firebase and returning the result for instance.
});

tripIdProvider has the latest clicked id, and tripProvider will watch this id and updates and fetch the new values if the id changes. It fetches the document of that id from the Firebase.

Now if user clicks another object having another id, in the callback of pressing/tapping that object, you can do something like this:

...
onPressed:(){
 ref.read(tripIdProvider.notifier).state = id;
}

This will change the id and because tripProvider is watching that providers' value, it will be triggered again and will fetch new data for the new id.

It's also good to check stateNotifier.

3
On

Ok so not sure if this is the best solution, but I ended up making a StateNotifierProvider out of the DocumentSnapshot class from Firestore.

class DocumentSnapshotNotifier extends StateNotifier<DocumentSnapshot?> {
  DocumentSnapshotNotifier() : super(null);

  DocumentSnapshot? get documentSnapshot => state;

  set documentSnapshot(DocumentSnapshot? documentSnapshot) {
    state = documentSnapshot;
  }

  void updateDocumentSnapshot(DocumentSnapshot? documentSnapshot) {
    state = documentSnapshot;
  }
}

final documentProvider = StateNotifierProvider<
    DocumentSnapshotNotifier,
    DocumentSnapshot?>((ref) => DocumentSnapshotNotifier());

Then once a user clicks on a trip, I pass the value to the provider via

  onTap: () {
                  ref
                      .read(documentProvider.notifier)
                      .updateDocumentSnapshot(document);

Then I can just call it for whatever field in the doc I need.

Center(
                child: Text(
                    'Departure Place: ${ref.read(documentProvider)!['departure']}')),
          ),
          Center(
              child:
                  Text('Arrival Place: ${ref.read(documentProvider)!['arrival']}')),
          Center(
              child: Text(
                  'Departure Date: ${ref.read(documentProvider)!['departureDate']}'))

It's working for now...