简体   繁体   中英

Flutter Firestore: FirestoreBuilder with initial data

I'm making my first Flutter app and I encounter a problem and doesn't found any solution for it.

I have a view where I render a Firestore document, and there is two ways of getting there:

  • From a list where I already loaded my documents
  • From Dynamic Links with uid attached as arguments (args)

So in order to listen document changes and loading the data when arriving from the link I used FirestoreBuilder like this:

    return FirestoreBuilder<EventDocumentSnapshot>(
        ref: eventsRef.doc(args.uid),
        builder: (context, AsyncSnapshot<EventDocumentSnapshot> snapshot, Widget? child) {
          if (!snapshot.hasData) {
            return Container();
          }
          Event? event = snapshot.requireData.data;
          
          return Scafold(); //Rest of my rendering code
        }
     );
       
 

How I could avoid first call to Firebase when I already have the data but still listen to changes? The main problem is that my hero animation doesn't work because of this.

I tried with a StreamBuilder and initialData param but since it's expecting stream I didn't know how to cast my data.

Okay, so I found the solution myself after many tries, so I added my Model object that can be null as initialData, but the thing that makes me struggle with is how you get the data in the builder. You have to call different methods depending on where the data is coming from.

    return StreamBuilder(
        initialData: args.event
        ref: eventsRef.doc(args.uid),
        builder: (context, AsyncSnapshot<dynamic> snapshot) {
          // Here is the trick, when data is coming from initialData you only
          // need to call requireData to get your Model
          Event event = snapshot.requireData is EventDocumentSnapshot ? snapshot.requireData.data : snapshot.requireData;
          
          return Scafold(); //Rest of my rendering code
        }
     );

Reading through cloud_firestore's documentation you can see that a Stream from a Query can be obtained via snapshots()

StreamBuilder<QuerySnapshot>(
  stream: Firestore.instance.collection('books').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    if (!snapshot.hasData) return new Text('Loading...');
    return new ListView(
      children: snapshot.data.documents.map((DocumentSnapshot document) {
        return new ListTile(
          title: new Text(document['title']),
          subtitle: new Text(document['author']),
        );
      }).toList(),
    );
  },
);

This won't help you, but with GetX it's simple to implement like this: You don't need StreamBuilder anymore.

//GetXcontroller
class pageController extends GetXcontroller {

... 

RxList<EventModel> events = RxList<EventModel>([]);


Stream<List<EventModel>> eventStream(User? firebaseUser) =>
      FirebaseFirestore.instance
          .collection('events')
          .snapshots()
          .map((query) =>
              query.docs.map((item) => UserModel.fromMap(item)).toList());


@override
  void onReady() async {
    super.onReady();
    events.bindStream(
        eventStream(controller.firebaseUser)); // subscribe any change of events collection
  }

@override
  onClose() {
    super.onClose();
    events.close(); //close rxObject to stop stream
    
}

...
}

You can use document snapshots on StreamBuilder.stream . You might want to abstract the call to firebase and map it to an entity you defined.

  MyEntity fromSnapshot(DocumentSnapshot<Map<String, dynamic>> snap) {
    final data = snap.data()!;
    return MyEntity (
      id: snap.id,
      name: data['name'],
    );
  }

  Stream<MyEntity> streamEntity(String uid) {
    return firebaseCollection
        .doc(uid)
        .snapshots()
        .map((snapshot) => fromSnapshot(snapshot));
  }
  
  return StreamBuilder<MyEntity>(
    // you can use firebaseCollection.doc(uid).snapshots() directly
    stream: streamEntity(args.uid),
    builder: (context, snapshot) {
     if (snapshot.hasData) {
        // do something with snapshot.data
        return Scaffold(...);
      } else {
        // e.g. return progress indicator if there is no data
        return Center(child: CircularProgressIndicator());
      }
    },
  );

For more complex data models you might want to look at simple state management or patterns such as BLoC .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM