How to properly retrieve data from Cloud Firestore using Flutter and Future Builder?

Issue

I am new to Flutter and especially Cloud Firestore. My data is stored as a Map containing another Map in my collection. To keep queries to my database as low as possible, I will store the data in a single document and want to retrieve only the lastest document. The code regarding fetching the data:

Future<Map<dynamic, dynamic>> fetchData() async {
  final _firestore = FirebaseFirestore.instance;
  late Map<dynamic, dynamic> data;

  try {
    await _firestore
        .collection('collection')
        .orderBy('timestamp', descending: true)
        .limit(1)
        .get()
        .then((value) => data = value.docs.first.data());
  } catch (e) {
    print(e);
  }
  return data;
}

Firstly, I am wondering whether there is a better way to getting the stored map from the most recent document in my collection?

Secondly,I would like to wait for this to finish and then use a FutureBuilder to show each element of my map as text, for example. At a later stage I was thinking about using a provider called in the initState that can send the data retrieved to various screens. For now, I am simply trying to display the data, but it seems like I am misunderstanding how this works:

class Home extends StatefulWidget {
  Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late Future<Map<dynamic, dynamic>> fixtures;

  @override
  void initState() {
    fixtures = _getData();
    super.initState();
  }

  Future<Map<dynamic, dynamic>> _getData() async {
    return await fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      child: FutureBuilder(
          future: fixtures,
          builder: (BuildContext context,
              AsyncSnapshot<Map<dynamic, dynamic>> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return Center(child: Text('loading...'));
            } else {
              if (snapshot.hasError) {
                return Center(child: Text('Error: ${snapshot.error}'));
              } else {
                return Center(child: Text('${snapshot.data!['XY']}'));
              }
            }
          }),
    ));
  }
}

It kind of works this way, but I feel like its not correct. What I don’t really understand is how to properly access the data from the snapshot, how to properly store all data, and if there is a better way of doing so. The documentation always simply says snapshot.data but does not actually use the data retrieved to perform any action.

Many thanks in advance.

Solution

It looks fine to me, if maybe a bit convoluted.

I’d probably simplify your fetchData to:

Future<Map<dynamic, dynamic>> fetchData() async {
  var value = await FirebaseFirestore.instance
        .collection('collection')
        .orderBy('timestamp', descending: true)
        .limit(1)
        .get()
  return value.docs.first.data();
}

The changes:

  • Removed the catch. Since you’re not really handling the error, it’s best to let it bubble up and only catch errors at the top-level, where you can then centrally log them or for example send them to Crashlytics.
  • It’s often harder to read code that combines await and then. Use one of the other.

Answered By – Frank van Puffelen

Answer Checked By – Senaida (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.