¿Estás usando una transmisión de Firebase como entrada para otra transmisión en Flutter?

Dec 31 2020

Contexto: tengo dos transmisiones de Firebase que funcionan correctamente y obtienen i) una lista de perfiles de usuario (colección de 'usuarios') y ii) una lista de ubicaciones que pertenecen a cada perfil de usuario (colección de 'ubicaciones'), y luego, asígnelos a un modelo de ubicación y usuario personalizado.

Flujo de usuarios:

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);
}

y el flujo de ubicación:

  // 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);
 }

Lo que quiero hacer es generar una transmisión personalizada que genere todas las ubicaciones para todos los usuarios; en otras palabras, usar la transmisión de usuarios como entrada para la transmisión de ubicaciones.

No estoy seguro de qué enfoque funciona aquí; consideré agregar la transmisión de usuarios como un parámetro de entrada a la transmisión de ubicaciones y luego crear un bucle for, algo como esto:

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;

pero, por supuesto, recibo un error ya que esto devuelve una lista, no una secuencia. Así que no tengo idea de cómo proceder ...

Respuestas

4 dshukertjr Dec 31 2020 at 07:52

Escucho tu dolor. He estado ahí. Estabas bastante cerca. Déjame explicarte cómo me gusta hacerlo.

Primero que nada, un poco de limpieza:

Parecía que no los estaba usando en las allLocationsfunciones, así que los eliminé

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

En segundo lugar, he cambiado el tipo de retorno de la función a partir Stream<List<Location>>de Stream<Map<String, List<Location>>donde la clave del mapa sería el ID de usuario. Encuentro este tipo útil, porque no tienes que preocuparte por el orden de los usuarios sincronizados con la transmisión.

En tercer lugar, cuando crea transmisiones, no puede regresar, pero debe ceder el paso de una función. También tienes que marcar la función async*(* no es un error tipográfico).

Con esto, te propongo que uses algo como esto para tu allLocationsfunción:

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;
        }
      }
    }
  }
}

Espero que te guste este método. Por favor, avíseme si algo no es lo que desea. Puedo hacer ajustes.