Usi uno stream Firebase come input per un altro stream in Flutter?

Dec 31 2020

Contesto: ho due Firebase Stream che funzionano correttamente e recuperano i) un elenco di profili utente (raccolta "utenti") e ii) un elenco di posizioni appartenenti a ciascun profilo utente (raccolta "posizioni") e quindi mappali su un modello utente e posizione personalizzato.

Streaming utenti:

class DatabaseService {

final String uid;
final String friendUid;
final String locationId;
DatabaseService({ this.uid, this.locationId, this.friendUid });

// collection reference for users
final CollectionReference userCollection = FirebaseFirestore.instance.collection('users');

// get users stream
Stream<List<CustomUserModel>> get users {

final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;

List<CustomUserModel> userList = [];

List<CustomUserModel> _streamMapper(DocumentSnapshot snapshot) {
  CustomUserModel individualUser = CustomUserModel(
    uid: snapshot.id,
    name: snapshot.data()['name'],
    username: snapshot.data()['username'],
    email: snapshot.data()['email'],
  );
  userList.add(individualUser);
  return userList;
}

  return userCollection.doc(uid).snapshots().map(_streamMapper);
}

e il Location Stream:

  // collection reference for location
   final CollectionReference locationCollection = 
   FirebaseFirestore.instance.collection('locations');

  Stream<List<Location>> get locations {

  final FirebaseAuth auth = FirebaseAuth.instance;
  final User user = auth.currentUser;
  final uid = user.uid;

  List<Location> _locationListFromSnapshot(QuerySnapshot snapshot) {
   List<Location> locationList = [];
    snapshot.docs.forEach((element) {
     Location individualLocation = Location(
       locationId: element.id,
       locationName: element.data()['locationName'],
       city: element.data()['city'],
     );
    locationList.add(individualLocation);
  });
   return locationList;
 }

  return userLocationCollection.doc(uid).collection('locations').snapshots()
   .map(_locationListFromSnapshot);
 }

Quello che voglio fare è generare un flusso personalizzato che restituisca tutte le posizioni per tutti gli utenti, in altre parole utilizzare il flusso degli utenti come input per il flusso delle posizioni.

Non sono sicuro di quale approccio funzioni qui: ho considerato l'aggiunta dello stream degli utenti come parametro di input allo stream delle posizioni e quindi la creazione di un ciclo for, qualcosa del genere:

Stream<List<Location>> allLocations(Stream<List<CustomUserModel>> users) {

final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;

List<Location> locationList = [];

users.forEach((element) {

// append user's locations to empty list
locationList.add(locationCollection.doc(element.first.uid).collection('locations')
.snapshots().map(SOME FUNCTION TO MAP A DOCUMENT SNAPSHOT TO THE CUSTOM LOCATION MODEL)

}

return locationList;

ma ovviamente ricevo un errore in quanto restituisce un elenco, non un flusso. Quindi non ho idea di come procedere ...

Risposte

4 dshukertjr Dec 31 2020 at 07:52

Ho sentito il tuo dolore. Io ci sono stato. Eri abbastanza vicino. Lascia che ti spieghi come mi piace farlo.

Prima di tutto, un po 'di pulizia:

Sembrava che non li stessi usando nelle allLocationsfunzioni, quindi li ho cancellati

final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;

In secondo luogo, ho cambiato il tipo di ritorno della funzione da Stream<List<Location>>a Stream<Map<String, List<Location>>cui la chiave della mappa sarebbe l'ID utente. Trovo questo tipo utile, perché non devi preoccuparti dell'ordine degli utenti sincronizzati con lo stream.

Terzo, quando crei flussi, non puoi tornare indietro, ma devi cedere da una funzione. Devi anche contrassegnare la funzione async*(* non è un errore di battitura).

Con questo, ti propongo di usare qualcosa del genere per la tua allLocationsfunzione:

class DataService {
  List<Location> convertToLocations(QuerySnapshot snap) {
    // This is the function to convert QuerySnapshot into List<Location>
    return [Location()];
  }

  Stream<Map<String, List<Location>>> allLocations(
      Stream<List<CustomUserModel>> usersStream) async* {
    Map<String, List<Location>> locationsMap = {};

    await for (List<CustomUserModel> users in usersStream) {
      for (CustomUserModel user in users) {
        final Stream<List<Location>> locationsStream = locationCollection
            .doc(user.uid)
            .collection('locations')
            .snapshots()
            .map(convertToLocations);
        await for (List<Location> locations in locationsStream) {
          locationsMap[user.uid] = locations;
          yield locationsMap;
        }
      }
    }
  }
}

Spero che questo metodo ti piaccia. Per favore fatemi sapere se qualcosa non è quello che volete. Posso apportare modifiche.