0

My Flutter application needs to listen for any changes in a specific Firestore document.

The process is quite simple and is found here in various solutions:

  1. StackOverflow: flutter-firestore-how-to-listen-for-changes-to-one-document
  2. StackOverflow:can-i-listen-to-a-single-document-in-firestore-with-a-streambuilder
  3. StackOverflow:using-stream-building-with-a-specific-firestore-document
  4. StackOverflow: how-to-get-a-specific-firestore-document-with-a-streambuilder
  5. Medium: how-to-get-data-from-firestore-and-show-it-on-flutterbuilder-or-streambuilder-e05

These are all solutions to using a Stream & StreamBuilder and listening for any changes.

Commonly one uses this approach:

Stream<UserModel?> getFirestoreUser(String uid) {
  return FirebaseFirestore.instance.collection('users').doc(uid).snapshots().map((event) => event.data()).map((event) => event == null ? null : UserModel.fromJson(event));
}

where:

  • UserModel has a fromJson(Map<String, dynamic>) factory constructor
  • and where there exists a users collection with the document ID being uid.

Stream returns a Stream<UserModel?> that can be used later by a Provider.of<UserModel?>(context) or in a StreamBuider<UserModel?>


Problem:

TL;DR - after a Firestore().getCurrentUser(uid) stream is updated (directly, or manually using firestore emulator and changing the document values), the StreamBuilder<UserModel?> below doesn't update.

At all points in app lifecycle (authenticated or not), the stream always returns null (however manually invoking the stream with .first returns the user content, but .last just hangs). See below for more info.

Link to github project.

To get started:

  1. Throw in your own gooogle-services.json file into android/app folder
  2. change all references for com.flutterprojects.myapp to ${your google-services.json package name}
  3. Use local emulator to update firestore changes

More Details:

My issue uses a similar approach, yet the Stream doesn't return anything. My Flutter widget LoggedIn() is used when the FirebaseAuth.instance.currentUser is valid (i.e. not null)

class LoggedIn extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return StreamBuilder<UserModel?>(
      builder: (context, snapshot) {
        print("Checking UserModel from stream provider");
        if (snapshot.data == null) {
          print("UserModel is null");
          return UiLogin();
        }

        print("UserModel is NOT null");
        return UiHome();
      },
    );
  }
}

After a successful authentication, the text Checking UserModel from stream provider is printed to the console, then UserModel is null. At no point does UserModel is NOT null get shown.

After successful authentication (i.e. user registered & document updated), I have a button that I manually invoke the stream and get the first item:

var currentUser = Firestore().getCurrentUser(FirebaseAuth.instance.currentUser!.uid);
var first = await currentUser.first;
print(first);

which returns the UserModel based on the newly added user information.

When running the following code, execution stops on .last, no error, no crash, no app exit, just stops executing.

var currentUser = Firestore().getCurrentUser(FirebaseAuth.instance.currentUser!.uid);
var last = await currentUser.last;
print(last);

BUT

When adding the following, any changes gets updated and printed to the console

Firestore().getCurrentUser(currentUid).listen((data) {
    print("Firestore Data Changed");
    print("Firestore change value: $data");
});

This means the StreamBuilder<UserModel?> isn't working correctly, as the Stream<UserModel> events do listen() to changes and acknowledge those changes, but the StreamBuilder<UserModel?> changes do not get affected and consistently returns null.

Am I doing something wrong?

CybeX
  • 2,060
  • 3
  • 48
  • 115

2 Answers2

0

Try if (snapshot.hasData) { print("UserModel is null"); return UiLogin(); }

orgreeno
  • 143
  • 2
  • 9
  • `snapshot.hasData` is a glorified `snapshot.data != null` check which I am already doing, see https://api.flutter.dev/flutter/widgets/AsyncSnapshot/hasData.html. How will this help? – CybeX Jun 17 '21 at 17:56
0

This is working for me. Maybe the connection states are what's missing.

StreamBuilder(
        stream: FirebaseFirestore.instance
            .collection('users')
            .doc(widget.uid)
            .collection('contacts')
            .doc(widget.client)
            .collection('messages')
            .orderBy('timestamp', descending: true)
            .snapshots(),
        builder: (context, snapshot) {
          if (snapshot.hasError)
            return Expanded(
              child: Center(child: Text('Problem Getting Your Messages')),
            );

          switch (snapshot.connectionState) {
            case ConnectionState.none:
              return Text('waiting');
            case ConnectionState.waiting:
              return Center(child: Text('Waiting'));
            case ConnectionState.active:
              var snap;
              print('active');
              if (snapshot.data != null) {
                snap = snapshot.data;
              }
              return Expanded(
                child: ListView(
                  ...
              );

            case ConnectionState.done:
              print('done');
              return Text('Lost Connection');
          }

          return Text('NOPE');
        },
      ),
orgreeno
  • 143
  • 2
  • 9