Saltar al contenido

Cómo consultar el documento de Firestore dentro de streambuilder y actualizar la vista de lista

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 Streams: 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‘s builder ejecución. Eso significa tu Stream necesita contener datos de ambas colecciones
  • A pesar de que Stream puede contener varios elementos, desea un Stream>. Los comentarios sobre esta respuesta (https://stackoverflow.com/a/53903960/608347) ayudaron

El enfoque que tomé fue básicamente

  1. 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

  2. StreamBuilder como de costumbre, pero en objetos group-to-userList en lugar de QuerySnapshots

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 Usersy 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *