StreamController with Firestore

2019-08-24 20:57发布

问题:

I want to user a StreamController to control a StreamBuilder that gets data from a collection in Firestore. This will enable me to use a RefereshIndicator so that when I pull down on the list, it refreshes/ fetches more data if there is any.

I used most of the information in this article. My current code is below

    class _Lists extends State<List> {
      StreamController _controller;
      final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();

        @override
         void initState() {
        _controller = new StreamController();
         loadPosts();         
        super.initState();
        }

       Future fetchPost() async {
       return await . 
       Firestore.instance.collection(_locationState).snapshots();
        }

      Future<Null> _handleRefresh() async {
      count++;
      print(count);
       fetchPost().then((res) async {
       _controller.add(res);
       showSnack();
        return null;
      });
     }

      showSnack() {
      return scaffoldKey.currentState.showSnackBar(
      SnackBar(
      content: Text('New content loaded'),
      ),
       );  
     }

      loadPosts() async {
      fetchPost().then((res) async {
       print(res.document);
       _controller.add(res);
      return res;
      });
      }


       @override
   Widget build(BuildContext context) {
   final topBar = AppBar(Title("List"));
   bottom: TabBar(
    indicatorColor: Colors.blueAccent,
    indicatorWeight: 3.0,
    //indicatorSize: 2.0,
    indicatorPadding:
        const EdgeInsets.only(bottom: 10.0, left: 47.0, right: 
    47.0),
    tabs: [
      Tab(
        child: Image(
          image: AssetImage("MyImage1"),
          width: 65.0,
          height: 65.0,
        ),
      ),
      Tab(
        child: Image(
          image: AssetImage("Image2"),
          width: 90.0,
          height: 90.0,
        ),
      ),
    ],
  ),

return DefaultTabController(
    length: 2,
    child: Scaffold(
        key: scaffoldKey,
        appBar: topBar,
        body: StreamBuilder(
            stream: _controller.stream,
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (snapshot.hasError) {
                return Text(snapshot.error);
              }
              if (snapshot.connectionState == ConnectionState.active) {
                List aList = new List();
                aList.clear();
                for (DocumentSnapshot _doc in snapshot.data.documents) {
                  Model _add = new Model.from(_doc);
                  aList.add(_add);
                }
                return TabBarView(
                  children: <Widget>[
                    RefreshIndicator(
                      onRefresh: _handleRefresh,
                      child: ListView.builder(
                        itemCount: aList.length,
                        itemBuilder: (context, index) {
                          return Card(aList[index]);
                        },
                      ),
                    ),
                    Icon(Icons.directions_transit),
                  ],
                );
              } else {
                return Container(
                    child: Center(child: CircularProgressIndicator()));
              }
            })));
 }
}
}

The Problem I have with this is I keep getting the error

    flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY 
╞═══════════════════════════════════════════════════════════
flutter: The following NoSuchMethodError was thrown building 
StreamBuilder<dynamic>(dirty, state:
flutter: _StreamBuilderBaseState<dynamic, 
AsyncSnapshot<dynamic>>#53c04):
flutter: Class '_BroadcastStream<QuerySnapshot>' has no instance getter 'documents'.
flutter: Receiver: Instance of '_BroadcastStream<QuerySnapshot>'
flutter: Tried calling: documents

Any ideas on how to go about using the StreamController with data from Firestore ?

回答1:

Keeping a close eye on return types in your IDE will likely help avoid a lot of confusing issues like this. Unfortunately, that blog does not indicate any types for the API call, StreamController, or 'res' in the then statement. Having those types declared will help show what you are working with (at least it does for me in Android Studio). For example in my StreamBuilder with a stream from Firestore, I use AsyncSnapshot<QuerySnapshot> snapshot instead of just AsyncSnapshot. This allows the tools in Android Studio to tell me that snapshot.data.documents is the map from the QuerySnapshot class. If I don't add the extra type, I can't see that.

Here is an example of listening to the stream from the Firestore Dart package.

//Performing a query:

Firestore.instance
    .collection('talks')
    .where("topic", isEqualTo: "flutter")
    .snapshots()
    .listen((data: QuerySnapshot) =>
        // do stuff here
    );

Since you're using the async/await style (also perfectly fine), you'll have the same result that will be inside of .listen((data) =>. We can follow the documentation/classes to see what types are returned.

Firestore.instance.collection(<whatever>).snapshots() will return Stream<QuerySnapshot>, so we know that await Firestore.instance.collection(<whatever>).snapshots() will return QuerySnapshot.

Digging further into the class, we see it has a property called documents.

  /// Gets a list of all the documents included in this snapshot
  final List<DocumentSnapshot> documents;

This finally gives us those DocumentSnapshots, which you'll have to pull the data property from.

So in your case, I believe the res type being QuerySnapshot will help show you what data to put in your stream, which can be done multiple ways at this point. List<DocumentSnapshot> seems to look like what you're going for, but you could go farther to List<YourClass> built from the DocumentSnapshot data property. With that, you can say what data type your StreamController will return, making the builder's AsyncSnapshot<your stream type> much more clear to work with.

I'm not sure what development tools you are using, but in case you aren't familiar most will allow you to do something like: press/hold (command or ctrl), hover over the type/class/function/var you want to see, left click, and you should be taken to the source files/declarations (I find this super handy).