Flutter: Another option for using FutureBuilder with multiple futures

Typically, FutureBuilder is used to wait for a single future and there is a simple way of using FutureBuilder to wait for multiple futures, but what if you want a more complicated, yet possibly more readable way, to use FutureBuilder to wait for multiple futures? I have found that way!

Suppose the data you are waiting for are 2 lists of String. In my example, the list of strings are month names and weekday names. Here are the two functions that return these two lists in the future (by waiting 10 and 13 seconds):

Future<List<String>> loadMonthNames() {
  return Future.delayed(const Duration(seconds: 10), () {
    return const ["January", "February", "March", "April", "May", 
            "June", "July", "August", "September", "October",
           "November", "December"];
  });
}

Future<List<String>> loadWeekdayNames() {
  return Future.delayed(const Duration(seconds: 13), () {
    return const ["Monday", "Tuesday", "Wednesday", "Thursday", 
            "Friday", "Saturday", "Sunday"];
  });
}

The first class we will create is the class that will hold the final result data, which in this case is a class with two lists of strings:

class DataGroup {
  final List<String> monthNames;
  final List<String> weekDayNames;
  DataGroup({required this.monthNames, required this.weekDayNames});
}

The second class is a little more complicated, it needs to hold the futures of the data we are retrieving and then return a future of DataGroup so that when all the futures are finished we get our class:

class DataGroupFuture {
  final Future<List<String>> monthNamesF;
  final Future<List<String>> weekDayNamesF;
  DataGroupFuture({required this.monthNamesF, required this.weekDayNamesF});
  Future<DataGroup> get future async {
    List<String> monthNames = await monthNamesF;
    List<String> weekDayNames = await weekDayNamesF;
    return DataGroup(monthNames: monthNames, weekDayNames: weekDayNames);
  }
}

The following code is used to create an instance of the class:

  final DataGroupFuture dataGroupF = DataGroupFuture(
    monthNamesF: loadMonthNames(),
    weekDayNamesF: loadWeekdayNames(),
  );

Once the instance is created, we can pass it to the future like this:

@override
  Widget build(BuildContext context) {
    return FutureBuilder<DataGroup>(
      future: dataGroupF.future,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Text("Waiting for data to load");
        } else {
          DataGroup dataGroup = snapshot.data!;
          return Column(
            children: [
              Text("Month names: ${dataGroup.monthNames}"),
              Text("Weekday names: ${dataGroup.weekDayNames}"),
            ]
          );
        }
      }
    );
  }

The important part of the code is in DataGroupFuture’s “future” get method. The async statements collect all the data from the group of futures (the two List of Strings) and then compile them into a single instance of DataGroup. Inside the FutureBuilder, we can access this value with the line:

DataGroup dataGroup = snapshot.data!;

The following code should work if you paste this into DartPad:

import 'package:flutter/material.dart';

Future<List<String>> loadMonthNames() {
  return Future.delayed(const Duration(seconds: 10), () {
    return const ["January", "February", "March", "April", "May", 
            "June", "July", "August", "September", "October",
           "November", "December"];
  });
}

Future<List<String>> loadWeekdayNames() {
  return Future.delayed(const Duration(seconds: 13), () {
    return const ["Monday", "Tuesday", "Wednesday", "Thursday", 
            "Friday", "Saturday", "Sunday"];
  });
}


const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

class DataGroup {
  final List<String> monthNames;
  final List<String> weekDayNames;
  DataGroup({required this.monthNames, required this.weekDayNames});
}

class DataGroupFuture {
  final Future<List<String>> monthNamesF;
  final Future<List<String>> weekDayNamesF;
  DataGroupFuture({required this.monthNamesF, required this.weekDayNamesF});
  Future<DataGroup> get future async {
    List<String> monthNames = await monthNamesF;
    List<String> weekDayNames = await weekDayNamesF;
    return DataGroup(monthNames: monthNames, weekDayNames: weekDayNames);
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  final DataGroupFuture dataGroupF = DataGroupFuture(
    monthNamesF: loadMonthNames(),
    weekDayNamesF: loadWeekdayNames(),
  );
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<DataGroup>(
      future: dataGroupF.future,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Text("Waiting for data to load");
        } else {
          DataGroup dataGroup = snapshot.data!;
          return Column(
            children: [
              Text("Month names: ${dataGroup.monthNames}"),
              Text("Weekday names: ${dataGroup.weekDayNames}"),
            ]
          );
        }
      }
    );
  }
}

Similar Posts