Usando um Firebase Stream como entrada para outro Stream no Flutter?
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
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 allLocations
funçõ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 allLocations
funçã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.