Ali Yar Khan
Ali Yar Khan

Reputation: 1344

How to read nested fields from Firebase Firestore

I want to read nested fields from my firestore. The hierarchy of the fields is

comments

|__ c_id

|__ c_text

|_________replies

|____________ r_id

|____________ r_text

I tried the following code:

var documentSnapshot = await FirebaseFirestore.instance
        .collection("article_engagements")
        .doc(id.toString())
        .get();
var comments = await FirebaseFirestore.instance
        .collection("article_engagements")
        .doc(id.toString())
        .collection("comments")
        .get();
    
comments.docs.forEach((element) {
      var replies = element.get('replies');
      commentsList.add(Comment(
        id: element.get('c_id'),
        text: element.get('c_text'),
        replies: ,
      ));
    });


article = new ArticleEngagement(
            id: id,
            likes: documentSnapshot.get('likes'),
            shares: documentSnapshot.get('shares'),
            comments: commentsList,
        );

But i am stuck at getting the replies field. Screenshot of the firestore document is as follows: Article Engagement collection

The ArticleEngagement, comment and replies models are as follows:

class ArticleEngagement {
  int? id;
  int? likes;
  int? shares;
  List<Comment>? comments;

  ArticleEngagement({
    this.id,
    this.likes,
    this.shares,
    this.comments,
  });
}

class Comment {
  String? id;
  String? text;
  List<Reply>? replies;
  Comment({
    this.id,
    this.replies,
    this.text,
  });
}

class Reply {
  String? id;
  String? reply;

  Reply({
    this.id,
    this.reply,
  });
}

Upvotes: 1

Views: 847

Answers (1)

Rogelio Monter
Rogelio Monter

Reputation: 1214

The problem was that you wanted to access the comments field as if it were a subcollection of the article_engagements document, but, in fact, it is an array.

To access all your fields inside the document given a certain id, I would suggest mapping your fields as nested classes that reflect your actual data structure. As pointed out by this answer,

You can use the json_serializable package, which generates code for serializing/deserializing.

Creating model classes the json_serializable way

Ensure that your class attributes match your fields in Cloud Firebase

import 'package:json_annotation/json_annotation.dart';

/// This allows the `ArticleEngagement` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'test.g.dart';

/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class ArticleEngagement {
  int? article_id;
  int? likes;
  int? shares;
  List<Comment>? comment;

  ArticleEngagement({
    this.article_id,
    this.likes,
    this.shares,
    this.comment,
  });

  /// A necessary factory constructor for creating a new ArticleEngagement instance
  /// from a map. Pass the map to the generated `_ArticleEngagement()` constructor.
  /// The constructor is named after the source class, in this case, ArticleEngagement.
  factory ArticleEngagement.fromJson(Map<String, dynamic> json) =>
      _$ArticleEngagementFromJson(json);

  /// `toJson` is the convention for a class to declare support for serialization
  /// to JSON. The implementation simply calls the private, generated
  /// helper method `_$ArticleEngagementToJson`.
  Map<String, dynamic> toJson() => _$ArticleEngagementToJson(this);
}

@JsonSerializable()
class Comment {
  String? c_id;
  String? c_text;
  List<Reply>? replies;
  Comment({
    this.c_id,
    this.replies,
    this.c_text,
  });

  factory Comment.fromJson(Map<String, dynamic> json) =>
      _$CommentFromJson(json);

  Map<String, dynamic> toJson() => _$CommentToJson(this);
}

@JsonSerializable()
class Reply {
  String? r_id;
  String? r_text;

  Reply({
    this.r_id,
    this.r_text,
  });

  factory Reply.fromJson(Map<String, dynamic> json) => _$ReplyFromJson(json);

  Map<String, dynamic> toJson() => _$ReplyToJson(this);
}

Then you have to run the code generation utility.

One-time code generation

By running flutter pub run build_runner build in the project root, you generate JSON serialization code for your models whenever they are needed.

Finally, you can consume json_serializable models, like this:

  var document = await FirebaseFirestore.instance
      .collection("article_engagements")
      .doc(id.toString())
      .get();

  ArticleEngagement article =
      ArticleEngagement.fromJson(document.data() as Map<String, dynamic>);

  print(article.comment?.first.c_id);

See also:

Upvotes: 1

Related Questions