skip to Main Content

I am trying to create a post system with geo location filter. I want to fetch all the results in the given radius and then paginate it to reduce the reads and to be developer-cost efficient.

In the codes below, I have achieved half of the goal. I am retrieving the first 10 documents from the documents list that are found within the radius.

And it’s working properly, but the load more function isn’t quite working.

Because I used streamSubscription to listen and then to pause the subscription while the user is going through the results already retrieved, and only resume the subscription when user calls for more results and then pause the subscription again to avoid any abrupt updates to the results in realtime.

I’ve gone through many ways to achieve it, but since I’m not so great, I haven’t been able to accomplish it. I found that we could achieve the desired results with streamController, but I don’t know how to use it and also I’m not sure if it would keep the reads minimal like subScription or not.

Please help, for my whole application depends on this little guy.

Thanks in advance.

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

  @override
  State<geoFlutterFirePaginatedSystem> createState() => _geoFlutterFirePaginatedSystemState();
}

class _geoFlutterFirePaginatedSystemState extends State<geoFlutterFirePaginatedSystem> {

  // ----------- Variables --------------- //
  final _firestore = FirebaseFirestore.instance;
  final geo = Geoflutterfire();

  GeoFirePoint myLocation = GeoFirePoint(13.7738153, 100.5667812);

  late Stream<List<DocumentSnapshot>> stream;
  StreamSubscription<List<DocumentSnapshot>>? streamSubscription;

  @override
  void initState() {
    stream = geo.collection(collectionRef: _firestore.collection('Listings')).within(
      center: myLocation,
      radius: 1,
      field: 'location', // Replace with your geolocation field name
      strictMode: true,
    );

    getResults(false);
    super.initState();
  }

  // ----------- Variables --------------- //
  var resultList = [];
  bool hasData = true;

  getResults(bool moreData) async {
    // Listen to the stream and handle sorting and pagination.
    streamSubscription = stream.listen((List<DocumentSnapshot> documentList) async {
      if(documentList.isEmpty) return;

      if(!moreData) {
        var dataBatch = [];
        // For the initial fetch, retrive 10 results
        dataBatch.addAll(documentList.take(10).map((doc) {
          var data = doc.data() as Map<String, dynamic>;
          return data['query'].toString();
        }));

        // ------- Adding delay to add the await operation to get all function called in order written ------ //
        await Future.delayed(const Duration(milliseconds: 1));

        // ----- Pausing Stream to stop realtime updates ---- ///
        streamSubscription?.pause();

        // -------- Adding data to real List from Temp List ----------- //
        resultList.addAll(dataBatch);

        // -------- If the data is less than batch size, it mean there's no more data available...setting hasData false ----------- //
        if(dataBatch.length < 10) {
          hasData = false;
          streamSubscription?.cancel();
        }

      } else {
        // on User's demand, retrive next 10 results
        var dataBatch = [];
        dataBatch.addAll(documentList.skip(resultList.length).take(10).map((doc) {
          var data = doc.data() as Map<String, dynamic>;
          return data['query'].toString();
        }));

        // ------- Adding delay to add the await operation to get all function called in order written ------ //
        await Future.delayed(const Duration(milliseconds: 1));

        // ----- Pausing Stream to stop realtime updates ---- ///
        streamSubscription?.pause();

        // -------- Adding data to real List from Temp List ----------- //
        resultList.addAll(dataBatch);

        // -------- If the data is less than batch size, it mean there's no more data available...setting hasData false ----------- //
        if(dataBatch.length < 10) {
          hasData = false;
          streamSubscription?.cancel();
        }
      }

      // Pausing the stream so that data wouldn't update abruptly while user's viewing a result
      setState(() {});
    });
  }

  moreListing() async {
    // Resuming the stream to get the data from the stream again
    streamSubscription?.resume();
    await getResults(true);
  }

  @override
  void dispose() {
    streamSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;

    return Scaffold(
      appBar: AppBar(
        actions: [
          if(hasData) ... [
            InkWell(
              onTap: () async {
                print("Clicked");
                await moreListing();
              },
              borderRadius: BorderRadius.circular(100),
              child: Container(
                width: 100,
                height: 40,
                alignment: Alignment.center,
                child: Text("More Data", style: TextStyle(fontSize: 14),),
              ),
            ),
          ] else ... [
            Container(
              width: 100,
              height: 40,
              alignment: Alignment.center,
              child: Text("No more Data", style: TextStyle(fontSize: 14),),
            ),
          ]
        ],
      ),
      body: Container(
        child: SingleChildScrollView(
          child: Column(
            children: [
              Container(
                alignment: Alignment.centerLeft,
                child: Text("Results (${resultList.length})", style: TextStyle(fontSize: 14, color: Colors.black,),),
              ),
              Container(
                padding: const EdgeInsets.only(top: 15),
                child: ListView.builder(
                  physics: const NeverScrollableScrollPhysics(),
                  shrinkWrap: true,
                  itemCount: resultList.length,
                  itemBuilder: (context, index) {
                    return Container(
                      margin: const EdgeInsets.only(bottom: 10),
                      padding: const EdgeInsets.only(bottom: 10),
                      decoration: BoxDecoration(
                        border: Border(
                          bottom: BorderSide(
                            width: 1,
                            color: Colors.black,
                          ),
                        ),
                      ),
                      child: Text("${resultList[index]}"),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2

Answers


  1. I am not sure, but I guess you can use the the sink operation with the stream controller to do something.

    streamController.sink(list)

    Login or Signup to reply.
  2. Creating a paginated system that efficiently fetches documents from a Firestore database within a certain radius can be a complex task. It seems like you’ve got a good start but are encountering issues with the stream-based approach when implementing the "load more" functionality. Let’s address this in parts:

    Initial Data Fetching: You have implemented the initial fetch correctly, where you get the first 10 documents within the specified radius.

    Pausing the Stream: You pause the stream to avoid real-time updates while the user is still browsing through the already-fetched data. This is a good approach to ensure users don’t see changes in the list while interacting with it.

    Load More Functionality: This is where the problem arises. The intention is to fetch the next set of documents when the user requests it, without fetching updates in real-time.

    To refine the "load more" functionality, consider the following adjustments:

    Using Cursors: Firestore supports query cursors, which allow you to paginate query results. You can use the last document from the previous fetch as a starting point for the next query.

    StreamController: A StreamController can give you more control over the stream of documents you’re fetching. You can add documents to the stream only when you want to, which can be triggered by a user action like pressing a "load more" button.

    Managing State: Your current implementation relies on the data flag to determine if more data exists to load. Ensure that this flag is being managed correctly after each fetch.

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