skip to Main Content

First, apologies I am a total newb to Flutter, Dart & Firestore and despite trying to follow many tutorials I am stuck.

I am trying to retrieve a Firestore collection record by a specific value.

I have a service dart class which runs the query.

import 'package:cloud_firestore/cloud_firestore.dart';

class WaypointService {

  getWaypointById(int wpCheckPointId) {
    return FirebaseFirestore.instance
        .collection("waypoints")
        .where("CheckPoint", isEqualTo: wpCheckPointId)
        .get();
  }

}

This service is passed an integer from a constant in a constants file. The screen that calls the service is:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:lhgth/services/waypoint_service.dart';

class WaypointScreen extends StatelessWidget {
  const WaypointScreen({ super.key, required this.wpFirebaseID });

  final int wpFirebaseID;

  @override
  Widget build(BuildContext context) {
    var waypoint;
    WaypointService().getWaypointById(wpFirebaseID).then((QuerySnapshot wayPoint) {
      if (wayPoint.docs.isNotEmpty) {
        waypoint = wayPoint.docs.first.data();
        print(waypoint); //##1
      }
    });

    print(waypoint); //##2

    return Container(
      padding: EdgeInsets.all(8),
      child: Scaffold(
        backgroundColor: Colors.white,
        body: Container(

        ),
      )
    );
  }
}

The issue I have is that the print statement ##1 is outputting what looks to be a JSON value:

{
  Radius: 25,
  Long: 50.9491835, 
  Info: Text,
  Lat: 0.7317906,
  CheckPoint: 1,
  Name: Waypoint Name
}

However, the second print ##2 returns a NULL and so I am not sure how I would work with the data returned from the query in the view itself.

Any guidance would be helpful.

2

Answers


  1. That’s totally normal, the get() method is an async method you have to wait before it returns the value.

    The second print ##2 returns null because it called before the getWaypointById method finishes.

    First: make your getWaypointById method as Future like this:

    Future<QuerySnapshot> getWaypointById(int wpCheckPointId) {
        return FirebaseFirestore.instance
            .collection("waypoints")
            .where("CheckPoint", isEqualTo: wpCheckPointId)
            .get();
    }
    

    Then: instead of doing tests in the build method, you can test in an external method like this:

    test() async {
        var waypoint;
        var results = await getWaypointById(wpFirebaseID);
        if (results.docs.isNotEmpty) {
          waypoint = results.docs.first.data();
        }
        print(waypoint);
      }
    

    Then you can call the test method in the build.

    To show the value on the screen you can use FutureBuilder:

    class WaypointScreen extends StatelessWidget {
      const WaypointScreen({ super.key, required this.wpFirebaseID });
    
      final int wpFirebaseID;
    
      @override
      Widget build(BuildContext context) {
    
        return Container(
            padding: EdgeInsets.all(8),
            child: Scaffold(
              backgroundColor: Colors.white,
              body: Container(
                child: FutureBuilder<QuerySnapshot>(
                  future: WaypointService().getWaypointById(wpFirebaseID),
                  builder: (context, snapshot) {
                    if (snapshot.hasData && (snapshot.data?.docs.isNotEmpty?? false)) {
                      var waypoint = snapshot.data!.docs.first.data();
                      return Text(waypoint['Name']);
                    } else {
                      return Text('Loading...');
                    }
                  },
                ),
              ),
            )
        );
      }
    }
    
    Login or Signup to reply.
  2. The issue you are facing is related to the asynchronous nature of your code. When you use WaypointService().getWaypointById(wpFirebaseID).then(...), it performs an asynchronous operation. Your print(waypoint); //##2 runs before the asynchronous operation is complete, hence waypoint is still null.

    one Approach can be Using async and await with StatefulWidget

     class WaypointScreen extends StatefulWidget {
      const WaypointScreen({Key? key, required this.wpFirebaseID}) : super(key: key);
    
      final int wpFirebaseID;
    
      @override
      _WaypointScreenState createState() => _WaypointScreenState();
    }
    
    class _WaypointScreenState extends State<WaypointScreen> {
      late Map<String, dynamic> waypoint;
    
      @override
      void initState() {
        super.initState();
        _fetchWaypoint();
      }
    
      _fetchWaypoint() async {
        var wayPoint = await WaypointService().getWaypointById(widget.wpFirebaseID);
        if (wayPoint.docs.isNotEmpty) {
          setState(() {
            waypoint = wayPoint.docs.first.data() as Map<String, dynamic>;
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return waypoint == null
            ? CircularProgressIndicator()
            : Container(
                padding: EdgeInsets.all(8),
                child: Scaffold(
                  backgroundColor: Colors.white,
                  body: Container(
                    // Your UI code here
                  ),
                ),
              );
      }
    }
    

    Note: A FutureBuilder widget can help you to build your UI based on the latest snapshot. It will automatically rebuild its UI at the right time when the Future is complete. if UI is not too much complicated you can use initState() for better result.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search