setState() not updating UI elements even though the state variable, a Future, is updated?

I have a HomePage screen which has a FutureBuilder List implemented with a Future function as the state variable. I am updating this Future in another dart file by using keys to access the future. The Future gets updated and I’m sure of this as I’ve seen the print statements, but when I call the setState method, the UI doesn’t show the newly added entry.

Here’s my HomePage.dart:

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  Future<List<Model>> getData() async {
    return await DatabaseHelper.instance.getModels();
  }

  Future? userFuture;

  @override
  void initState() {
    super.initState();
    userFuture = getData();
    print(userFuture);
  }

  @override
  Widget build(BuildContext context) {
    print('Building listview');
    return Center(
      child: FutureBuilder<List<Model>>(
        future: userFuture as Future<List<Model>>,
        builder: ((context, AsyncSnapshot<List<Model>> snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.waiting:
              return const CircularProgressIndicator();
            default:
              if (snapshot.data!.isEmpty) {
                return Text('No data present');
              } else if (snapshot.hasData) {
                return ListView.builder(
                  itemCount: snapshot.data?.length,
                  itemBuilder: ((context, index) {
                    return MyCard(
                        key: ValueKey(snapshot.data![index].id),
                        snapshot.data![index].id,
                        snapshot.data![index].title,
                        snapshot.data![index].purpose);
                  }),
                );
              }
              return Text('data');
          }
        }),
      ),
    );
  }
}

Here’s my other dart file. Under the AddEntryState I’m updating the Future state variable and then right after calling the setState method.

class RootPage extends StatefulWidget {
  const RootPage({super.key});

  @override
  State<RootPage> createState() => RootPageState();
}

class RootPageState extends State<RootPage> {
  static final GlobalKey<HomePageState> homepageKey =
      GlobalKey<HomePageState>();
  int currentPage = 0;
  List<Widget>? pages;

  @override
  void initState() {
    super.initState();
    pages = [
      HomePage(key: homepageKey),
      StatsPage(),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('App Title'),
      ),
      body: pages?[currentPage],
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
              context, MaterialPageRoute(builder: (context) => AddEntry()));
        },
        child: Icon(Icons.add),
      ),
      bottomNavigationBar: NavigationBar(
        destinations: [
          NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
          NavigationDestination(icon: Icon(Icons.data_usage), label: 'Stats'),
        ],
        onDestinationSelected: (int index) {
          setState(() {
            currentPage = index;
            print(index);
          });
        },
        selectedIndex: currentPage,
      ),
    );
  }
}

class AddEntry extends StatefulWidget {
  const AddEntry({super.key});

  @override
  State<AddEntry> createState() => _AddEntryState();
}

class _AddEntryState extends State<AddEntry> {
  final GlobalKey<FormState> _key = GlobalKey<FormState>();
  Map<String, String?> formField = <String, String?>{};

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('New Entry'),
      ),
      body: Form(
        key: _key,
        child: Column(
          children: [
            Flexible(
              child: MyTextField('Title', callback),
            ),
            Flexible(
              child: MyTextField('Purpose', callback),
            ),
            Flexible(
              child: MyTextField('Password', callback, obscure: true),
            ),
            TextButton(
              onPressed: () async {
                if (_key.currentState!.validate()) {
                  _key.currentState?.save();
                  formField.forEach((label, value) => print('$label = $value'));
                  await DatabaseHelper.instance.insertModel(Model(
                      id: null,
                      title: formField['Title'],
                      purpose: formField['Purpose'],
                      lastAccess: DateTime.now().toString(),
                      dateAdded: DateTime.now().toString(),
                      password: formField['Password']));
                  print(await DatabaseHelper.instance.getModels());
                  // await DatabaseHelper.instance.deleteAllData();
                  // print(await DatabaseHelper.instance.getModels());
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Data Saved!'),
                      action: SnackBarAction(
                        label: 'Edit',
                        onPressed: () {
                          print('edit pressed!');
                        },
                      ),
                    ),
                  );
                  Navigator.pop(context);
                  print("HomePage userFuture: ");
                  print(RootPageState.homepageKey.currentState!.userFuture!
                      .then((result) => print(result)));
                  print("getData function: ");
                  print(RootPageState.homepageKey.currentState!
                      .getData()
                      .then((result) => print(result)));

                  print("New Future: ");
                  print(RootPageState.homepageKey.currentState!.userFuture!
                      .then((result) => print(result)));

                  setState(() {
                    RootPageState.homepageKey.currentState!.userFuture =
                        RootPageState.homepageKey.currentState!.getData();
                  });

                  //add logic to rebuild home screen after every addition of entry
                }
              },
              child: Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }

  callback(varLabel, varValue) {
    formField[varLabel] = varValue;
  }
}

>Solution :

The issue is that you are updating the state of the Future object in the AddEntryState, but the HomePage widget is not being rebuilt to reflect the change in the Future. One way to fix this is to pass a callback function to the AddEntry widget that updates the state of the HomePage widget when the form is submitted. In the HomePage widget, you can pass a callback function that updates the Future object to the AddEntry widget and call that function in the onPressed callback of the submit button in the AddEntry widget. This way, when the form is submitted, the Future object will be updated and the HomePage widget will be rebuilt to reflect the change in the Future.

Leave a Reply