Este equipo de expertos pasados muchos días de investigación y recopilación de de datos, obtuvieron los datos necesarios, queremos que resulte de utilidad en tu plan.
Solución:
Publicando para aquellos en el futuro, ya que pasé varias horas tratando de resolver esto, esperando que salve a alguien más.
Primero recomiendo leer sobre Stream
s: https://www.dartlang.org/tutorials/language/streams Esto ayudará un poco y es una lectura breve
El pensamiento natural es tener un anidado StreamBuilder
dentro del exterior StreamBuilder
, lo cual está bien si el tamaño del ListView
no cambiará como resultado del interior StreamBuilder
recibiendo información. Puede crear un contenedor con un tamaño fijo cuando no tiene datos, luego renderizar el widget rico en datos cuando esté listo. En mi caso, quería crear un Card
para cada documento tanto de la colección “externa” como de la colección “interna”. Por ejemplo, tengo una colección de grupo y cada grupo tiene usuarios. Quería una vista como esta:
[
Group_A header card,
Group_A's User_1 card,
Group_A's User_2 card,
Group_B header card,
Group_B's User_1 card,
Group_B's User_2 card,
]
El anidado StreamBuilder
enfoque renderizó los datos, pero desplazando el ListView.builder
fue un problema. Al desplazarse, supongo que la altura se calculó como (group_header_card_height
+ inner_listview_no_data_height
). Cuando los datos fueron recibidos por el interior ListView
, expandió la altura de la lista para ajustarla y el desplazamiento se sacude. No es UX aceptable.
Puntos clave para la solución:
- Todos los datos deben adquirirse antes
StreamBuilder
‘sbuilder
ejecución. Eso significa tuStream
necesita contener datos de ambas colecciones - A pesar de que
Stream
puede contener varios elementos, desea unStream
. Los comentarios sobre esta respuesta (https://stackoverflow.com/a/53903960/608347) ayudaron- >
El enfoque que tomé fue básicamente
-
Crear un flujo de pares de listas de grupos a usuarios
una. Consulta para grupos
B. Para cada grupo, obtenga userList apropiado
C. Devuelve una lista de objetos personalizados que envuelven cada par
-
StreamBuilder
como de costumbre, pero en objetos group-to-userList en lugar deQuerySnapshot
s
Como se vería
El objeto auxiliar compuesto:
class GroupWithUsers
final Group group;
final List users;
GroupWithUsers(this.group, this.users);
los StreamBuilder
Stream> stream = Firestore.instance
.collection(GROUP_COLLECTION_NAME)
.orderBy('createdAt', descending: true)
.snapshots()
.asyncMap((QuerySnapshot groupSnap) => groupsToPairs(groupSnap));
return StreamBuilder(
stream: stream,
builder: (BuildContext c, AsyncSnapshot> snapshot)
// build whatever
);
esencialmente, “para cada grupo, cree un par” manejando todas las conversiones de tipos
Future> groupsToPairs(QuerySnapshot groupSnap)
return Future.wait(groupSnap.documents.map((DocumentSnapshot groupDoc) async
return await groupToPair(groupDoc);
).toList());
Finalmente, la consulta interna real para obtener User
sy construyendo nuestro ayudante
Future groupToPair(DocumentSnapshot groupDoc)
return Firestore.instance
.collection(USER_COLLECTION_NAME)
.where('groupId', isEqualTo: groupDoc.documentID)
.orderBy('createdAt', descending: false)
.getDocuments()
.then((usersSnap)
List users = [];
for (var doc in usersSnap.documents)
users.add(User.from(doc));
return GroupWithUser(Group.from(groupDoc), users);
);
Publiqué una pregunta similar y luego encontré una solución: hacer que el widget devuelto por itemBuilder tenga estado y use un FutureBuilder en él.
Consulta adicional para cada DocumentSnapshot dentro de StreamBuilder
Aquí está mi código. En su caso, le gustaría usar un nuevo widget Stateful en lugar de ListTile, por lo que puede agregar FutureBuilder para llamar a una función asíncrona.
StreamBuilder(
stream: Firestore.instance
.collection("messages").snapshots(),
builder: (context, snapshot)
switch (snapshot.connectionState)
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: PlatformProgressIndicator(),
);
default:
return ListView.builder(
reverse: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index)
List rev = snapshot.data.documents.reversed.toList();
ChatMessageModel message = ChatMessageModel.fromSnapshot(rev[index]);
return ChatMessage(message);
,
);
,
)
class ChatMessage extends StatefulWidget
final ChatMessageModel _message;
ChatMessage(this._message);
@override
_ChatMessageState createState() => _ChatMessageState(_message);
class _ChatMessageState extends State
final ChatMessageModel _message;
_ChatMessageState(this._message);
Future _load() async
await _message.loadUser();
return _message;
@override
Widget build(BuildContext context)
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: FutureBuilder(
future: _load(),
builder: (context, AsyncSnapshotmessage)
if (!message.hasData)
return Container();
return Row(
children: [
Container(
margin: const EdgeInsets.only(right: 16.0),
child: GestureDetector(
child: CircleAvatar(
backgroundImage: NetworkImage(message.data.user.pictureUrl),
),
onTap: ()
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) =>
ProfileScreen(message.data.user)));
,
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.data.user.name,
style: Theme.of(context).textTheme.subhead,
),
Container(
margin: const EdgeInsets.only(top: 5.0),
child: _message.mediaUrl != null
? Image.network(_message.mediaUrl, width: 250.0)
: Text(_message.text))
],
),
)
],
);
,
),
);
class ChatMessageModel
String id;
String userId;
String text;
String mediaUrl;
int createdAt;
String replyId;
UserModel user;
ChatMessageModel(String text, String mediaUrl, String userId)
this.text = text;
this.mediaUrl = mediaUrl;
this.userId = userId;
ChatMessageModel.fromSnapshot(DocumentSnapshot snapshot)
this.id = snapshot.documentID;
this.text = snapshot.data["text"];
this.mediaUrl = snapshot.data["mediaUrl"];
this.createdAt = snapshot.data["createdAt"];
this.replyId = snapshot.data["replyId"];
this.userId = snapshot.data["userId"];
Map toMap()
Map map =
"text": this.text,
"mediaUrl": this.mediaUrl,
"userId": this.userId,
"createdAt": this.createdAt
;
return map;
Future loadUser() async
DocumentSnapshot ds = await Firestore.instance
.collection("users").document(this.userId).get();
if (ds != null)
this.user = UserModel.fromSnapshot(ds);
Aquí puedes ver las reseñas y valoraciones de los usuarios
Si piensas que ha sido de utilidad este artículo, te agradeceríamos que lo compartas con otros entusiastas de la programación de esta manera contrubuyes a extender nuestra información.