Search code examples
flutterflutter-routesflutter-streambuilder

How to pass a Firestore document from a stream to next page in Flutter?


I have a listview where each item is one document from a firestore collection. I would like to tap the item and pass the document information to a details page.

This is how I am retrieving document information within the first stream:

child: Text(streamSnapshot.data.docs[index]['event_title'],

This is how I'm attempting to send the data to the next page:

child: GestureDetector(
                          onTap: () {
                            Navigator.pushNamed(context, EventPage.id, arguments: streamSnapshot.data.docs[index]);
                          },

I'm lost as to how to receive the passed data:

    class _EventPageState extends State<EventPage> {
  @override

final db = FirebaseFirestore.instance;
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments;
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('event_title'),
      ),
      child: Column(

I know I need a StreamBuilder on the next page, but do you have any insight on how to make that stream show only the passed in document?


Solution

  • I have worked out an answer to this question. I'm sure there are several ways to do this, but here's mine:

    The key is to pass the firestore document ID to the next page. In this example code, I pass streamSnapshot.data.docs[index].id.toString() as a parameter to a custom widget. I've located my named route within that widget.

    StreamBuilder(
        stream: FirebaseFirestore.instance
            .collection('events')
            .where('start_date', isGreaterThanOrEqualTo: DateTime.now())
            .snapshots(),
        builder: (context, AsyncSnapshot streamSnapshot) {
    
          if (!streamSnapshot.hasData) {
            return SizedBox(
              height: 250,
              child: Center(
                child: CircularProgressIndicator(),
              ),
            );
          } else
            return SizedBox(
              height: 250,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                itemCount: streamSnapshot.data.docs.length,
                itemBuilder: (ctx, index) =>
    
                    EventListHorizontalTile(
    
                    //passes the document ID as a string down to the horizontally scrollable tile,
                    //where we push a named route with the docID string as an argument
    
                    firestoreDocID: streamSnapshot.data.docs[index].id.toString(),
    
                      image: streamSnapshot.data.docs[index]['main_image'],
                      name: streamSnapshot.data.docs[index]['name'],
                  ),
              ),
            );
        }),
    

    I then created a class to pass as an argument through a named route.

    class Events {
      final String firestoreDocID;
    
      Events({
        required this.firestoreDocID,
    
      });
    
    }
    

    Now, within my EventListHorizontalTile widget:

    class EventListHorizontalTile extends StatelessWidget {
      const EventListHorizontalTile({
        Key? key,
    
        required this.name,
        this.firestoreDocID = '',
    
      }) : super(key: key);
    
      final String name;
      final String firestoreDocID;
    
      @override
      Widget build(BuildContext context) {
    return GestureDetector(
    
            onTap: () {
    
    //Here I am pushing a named route with an argument, using that Events class I made earlier.
    
              Navigator.pushNamed(context, EventPage.id, arguments: Events(firestoreDocID: firestoreDocID));
    
            },
    
    //child: The rest of the list tile widget
    
    ),
    

    Now we have to write a bit of code in the EventPage to receive the argument.

    class EventPage extends StatefulWidget {
      const EventPage({
        Key? key,
      }) : super(key: key);
    
      static String id = 'EventPage';
    
      @override
      _EventPageState createState() => _EventPageState();
    }
    
    class _EventPageState extends State<EventPage> {
      @override
      Widget build(BuildContext context) {
    
    
    //This is how we receive the argument.
        final args = ModalRoute.of(context)!.settings.arguments as Events;
    
        return CupertinoPageScaffold(
          navigationBar: CupertinoNavigationBar(),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
    
    //Some text to see if the string made it.
                Text(args.firestoreDocID),
    
    ]),
    ),
    );
    }
    }
    

    And that's it! Once you have that document ID in your new page, you can call a Streambuilder like this:

    StreamBuilder(
                  stream: FirebaseFirestore.instance
                      .collection('events')
                      .doc(args.firestoreDocID)
                      .snapshots(),