Usando um Firebase Stream como entrada para outro Stream no Flutter?

Dec 31 2020

Contexto: tenho dois Streams do Firebase que funcionam corretamente e eles buscam i) uma lista de perfis de usuário (coleção de 'usuários') e ii) uma lista de locais pertencentes a cada perfil de usuário (coleção de 'locais') e em seguida, mapeie-os para um modelo de usuário e local personalizado.

Fluxo de usuários:

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 o stream de localização:

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

O que eu quero fazer é gerar um fluxo personalizado que produza todos os locais para todos os usuários - em outras palavras, usar o fluxo de usuários como uma entrada para o fluxo de locais.

Não tenho certeza de qual abordagem funciona aqui - considerei adicionar o fluxo de usuários como um parâmetro de entrada ao fluxo de locais e, em seguida, criar um loop for, algo como este:

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;

mas é claro que recebo um erro, pois isso retorna uma lista, não um fluxo. Então não tenho ideia de como proceder ...

Respostas

4 dshukertjr Dec 31 2020 at 07:52

Eu ouço sua dor. Eu estive lá. Você estava bem perto. Deixe-me explicar como gosto de fazer isso.

Em primeiro lugar, alguns limpam:

Parecia que você não estava usando isso nas allLocationsfunções, então eu os apaguei

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

Em segundo lugar, alterei o tipo de retorno da função Stream<List<Location>>para Stream<Map<String, List<Location>>onde a chave do mapa seria o userId. Acho esse tipo útil, porque você não precisa se preocupar com a ordem dos usuários em sincronia com o stream.

Terceiro, quando você está criando fluxos, você não pode retornar, mas tem que render a partir de uma função. Você também deve marcar a função async*(* não é um erro de digitação).

Com isso, proponho que você use algo assim para sua allLocationsfunção:

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 goste deste método. Por favor, deixe-me saber se algo não é o que você deseja. Posso fazer ajustes.