How to modify Flutter Firebase Stream listener based on Firebase Auth stream when using Provider for DI?

1.6k views Asked by At

In my Flutter Firebase App with Provider for state management, I have a stream for reacting to FirebaseAuth.instance.authStateChanges() and a separate stream for listening to the my app related metadata for the logged in uid provided by FirebaseAuth.

    return MultiProvider(
      providers: [

        // This returns a stream of firebase user auth events so my app can react to 
        // login, force logout, etc. 
        StreamProvider<fireauth.User>.value(
          value: FirebaseAuth.instance.authStateChanges(),
        ),
        
        // conditional on non-null FirebaseAuth User, I would like to register a Firestore listener 
        // for the provided userId.
        // For security purposes, if the authenticated uid changes, the listener should be dereigstered.
        // After logout, if a different user is logged in, the this stream should listen to that uid's doc.
        StreamProvider<MyUser>.value(
          value: FirebaseFirestore.instance.collection('users')
                   .doc(/* use the userId from firebaseAuth here! */)
                   .snapshots()
                   .map((ds) => MyUser.fromJson(ds.data()))
        ),

      ],
  );

I think I can use ProxyProvider to allow the MyUser stream to take a dependency on the FirebaseAuth.User stream, but once the MyUser stream is registered for this uid, it seems to be immutable. How can I "reload" the Firestore stream based on the result from the FirebaseAuth.User?

2

There are 2 answers

0
Tndevl On

Ran into the same problem but with Firebase Realtime Database. Using the async package I created a StreamGroup. A StreamGroup.

Create StreamGroup: StreamGroup group = StreamGroup();

Create a stream Variable: Stream streamvar;

created function with this scheme:


    Stream<PlaceHolderClass> functionName(User user) async*{
      if (user != null) {
          await for (var x in streamOfFirestoreDocuments) {
            yield PlaceHolderClass();
          }
        } else {
          yield PlaceHolderClass();
        }
    }

Created an initializer:

where I listened to: FirebaseAuth.instance.authStateChanges() and added it to group by group.add(), also set the stream variable and added it to the group: streamvar = functionName(FirebaseAuth.instance.currentUser); group.add(streamvar)

then created:

Stream<FirestoreDocuments> providerFunc(){
  return group.stream.map((event) {
   if(isUserChangetest){
     
     group.remove(streamvar);
     streamvar = newStreamvar;
     group.add(newStreamvar)
     fetch = newFirestoreDocs;
     return fetch;
}else{
 return sth;
}

})
}


Now streamVar always holds a reference to the last firestore stream input to group. Swap stream from Firestore and you can listen to both kind of changes Firestore and Auth.
Incase I missed something:


    class Wallet {
      static final Wallet _singleton = Wallet._internal();
      factory Wallet() {
        return _singleton;
      }
      Wallet._internal();
      Stream coins;
      create() {
        Stream<CrossOver> signin = FirebaseAuth.instance
            .authStateChanges()
            .map((event) => CrossOver(user: event, isnum: false));
        group.add(signin);
        coins = replace(FirebaseAuth.instance.currentUser);
        group.add(coins);
      }
    
      Stream<CrossOver> replace(User user) async* {
        if (user != null) {
          await for (var x in FirebaseDatabase.instance
              .reference()
              .child('users/' + user.uid + '/scans')
              .onValue) {
            yield CrossOver(coins: Coins.load(x.snapshot.value), isnum: true);
          }
        } else {
          yield CrossOver(coins: Coins.load(0), isnum: true);
        }
      }
    
      Stream<Coins> real() {
        return group.stream.map((event) {
          Coins val;
          if (event.isnum == true) {
            print('new money');
            val = event.coins;
          }
          if (event.isnum == false) {
            Stream<CrossOver> nStream = replace(event.user);
            print('new user');
            group.remove(coins);
            coins = nStream;
            group.add(nStream);
            FirebaseDatabase.instance
                .reference()
                .child('users/' + event.user.uid + '/scans')
                .once()
                .then((value) => val = Coins.load(value.value));
          }
          return val;
        });
      }
    
      StreamGroup<CrossOver> group = StreamGroup();
    }
    
    class Coins {
      int money;
      Coins(this.money);
      factory Coins.load(int i) {
        return Coins(i);
      }
    }
    
    class CrossOver {
      bool isnum;
      User user;
      Coins coins;
      CrossOver({this.user, this.coins, this.isnum});
    }


0
Karen On

I used flatMap() in rxdart to merge and combine the auth user stream and the firestore user stream so the resulting stream listens to both auth and firestore changes.

I wrote a more detailed answer with the code here: https://stackoverflow.com/a/66234728/10394353