Search code examples
flutterflutter-provider

ProxyProvider - Struggling using it to return List<int>


i'm new to flutter and i'm trying to use ProxyProvider to return List depending on List here's the code in main.dart

providers: [
        FutureProvider<List<Mixer>>(
            create: (_) => mixerdbService.retrieveMixers(),
            initialData: <Mixer>[]),
        ProxyProvider<List<Mixer>, List<int>>(update: (_, mixers, __) {
          final List<int> ints = [];

          for (var mixer in mixers) {
            shipmentdbService
                .retrieveNumberOfShipmentsByMixerId(mixer.id)
                .then((value) => ints.add(value));
          }
          return ints;
        }),

and this the method retrieveNumberOfShipmentsByMixerId

  Future<int> retrieveNumberOfShipmentsByMixerId(String? mixerId) async {
    QuerySnapshot<Map<String, dynamic>> snapshot = await _db
        .collection("shipments")
        .where("mixer_id", isEqualTo: mixerId)
        .get();
    return snapshot.docs.length;
  }

The provider value is an empty list. i think that there is a mistake in the logic in update method of the proxyprovider. if the question is not clear, please ask me for more details

Solution

step#1

        FutureProvider<List<Mixer>>(
            create: (_) => mixerdbService.retrieveMixers(),
            initialData: <Mixer>[]),
        ProxyProvider<List<Mixer>, Future<List<int>>>(
            update: (_, mixers, __) async {
          final List<int> ints = [];

          for (var mixer in mixers) {
            final value = await shipmentdbService
                .retrieveNumberOfShipmentsByMixerId(mixer.id);
            ints.add(value);
          }
          return ints;
        }),

step#2: Resolve the Future from the Provider by using FutureBuilder

FutureBuilder<List<int>>(
        future: context.read<Future<List<int>>>(),
        builder: (_, snapshot) {
          if (snapshot.hasData &&
              snapshot.data!.isNotEmpty &&
              listOfMixers.isNotEmpty) {
            return ListView.builder(
                itemCount: listOfMixers.length,
                itemBuilder: (_, index) {
                  return MixerCard(
                      mixer: listOfMixers[index],
                      numberOfShipments: snapshot.data![index]);
                });
          }
          return Center(
            child: CircularProgressIndicator(),
          );
        },
      ),

This solution is okay, but i'm still searching for another solution without the use of FutureBuilder.


Solution

  • The problem is that the ProxyProvider is returning a list that's not populated yet because of the async calls with .then.

    To solve it return a Future<List<int>> instead and make the update function async. It's going to be like below:

    ProxyProvider<List<Mixer>, Future<List<int>>>(
        update: (_, mixers, __) async {
      final List<int> ints = [];
    
      for (var mixer in mixers) {
        final value = await shipmentdbService
            .retrieveNumberOfShipmentsByMixerId(mixer.id);
        ints.add(value);
      }
      return ints;
    }),
    

    Solution 1

    Then add another FutureProvider like so:

    FutureProvider<List<int>>(
      create: (context) => context.read<Future<List<int>>>(),
      initialData: [],
    ),
    

    This way it's easy to use it like:

    final listOfMixers = context.watch<List<Mixer>>();
    final listOfInts = context.watch<List<int>>();
    return Scaffold(
      body: listOfMixers.isNotEmpty && listOfInts.isNotEmpty   // <- Here
          ? ListView.builder(
              itemCount: listOfMixers.length,
              itemBuilder: (context, index) {
                return MixerCard(
                  mixer: listOfMixers[index],
                  numberOfShipments: listOfInts[index],
                );
              })
          : const Center(
              child: CircularProgressIndicator(),
            ),
    );
    
    

    Solution 2

    Or use a FutureBuilder to get the List<int> out of the Future. Something like the following:

    FutureBuilder<List<int>>(
      future: context.read<Future<List<int>>>(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return ErrorPage();
        }
    
        if (snapshot.data == null) {
          return Center(child: CircularProgressIndicator());
        }
    
        final listLengths = snapshot.data;
        // Do something with `listLengths`
      }),