How to solve: Unable to reload Flutter widget on setSate()

Question

Asked by DGF on January 12, 2022 (source).

I'm building my first flutter app (also my first mobile app) and I'm having a hard time understanding how to reload the page (change the state).

I saw the documentation and I also searched for some questions related to mine, but I still can't wrap my head around this since there are so many ways of changing states that vary depending on the specific issue.

So, I have this screen:

import 'package:flutter/material.dart';
import 'package:mobile_tech_overdose/models/categories.dart';
import 'package:mobile_tech_overdose/models/subcategories.dart';
import 'package:mobile_tech_overdose/services/api/category_api.dart';
import 'package:mobile_tech_overdose/services/api/subcategory_api.dart';

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


  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {

  CategoryApi categoryApi = CategoryApi();
  SubcategoryApi subcategoryApi = SubcategoryApi();
  late Future<List<Category>> futureCategories;
  late Future<List<Subcategory>> futureSubcategoriesFromCategory;
  int? category_id;

  @override
  void initState() {
    super.initState();
    futureCategories = categoryApi.fetchCategories();
    futureSubcategoriesFromCategory = subcategoryApi.fetchSubcategoriesFromCategory(category_id);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Forum Categories'),
        ),
        body: Column(
          children: [
            SizedBox(
              height: 100,
              child: FutureBuilder<List<Category>>(
                  future: futureCategories,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return ListView.builder(
                          scrollDirection: Axis.horizontal,
                          itemCount: snapshot.data!.length,
                          itemBuilder: (context, index) {
                            Category category = snapshot.data![index];
                            return Container(
                              height: 200,
                              width: 200,
                              margin: const EdgeInsets.only(top: 20, left: 40),
                              child: ListTile(
                                title: ElevatedButton(
                                  child: Text(category.name),
                                  onPressed: () {
                                    setState(() {
                                      category_id = category.id;
                                    });
                                  },
                                ),
                              ),
                            );
                          });
                    } else if (snapshot.hasError) {
                      return const Text('Something went wrong');
                    }

                    return const Center(
                        child: CircularProgressIndicator()
                    );
                  }),
            ),
            Expanded(
              child: FutureBuilder<List<Subcategory>>(
                  future: futureSubcategoriesFromCategory,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return ListView.builder(
                          scrollDirection: Axis.vertical,
                          itemCount: snapshot.data!.length,
                          itemBuilder: (context, index) {
                            Subcategory subcategory = snapshot.data![index];
                            return Center(
                              child: Padding(
                                padding: const EdgeInsets.all(10.0),
                                child: Card(
                                  color: Colors.black45,
                                  child: InkWell(
                                      splashColor: Colors.blue.withAlpha(50),
                                      onTap: () {
                                        debugPrint('Card');
                                      },
                                    child: Padding(
                                      padding: const EdgeInsets.all(8.0),
                                      child: Column(
                                        mainAxisSize: MainAxisSize.min,
                                        children: <Widget>[
                                          ListTile(
                                            leading: Padding(
                                              padding: const EdgeInsets.only(right: 10.0),
                                              child: Container(
                                                  width: 50.0,
                                                  height: 100.0,
                                                  decoration: BoxDecoration(
                                                      color: Colors.white,
                                                      shape: BoxShape.circle,
                                                      image: DecorationImage(
                                                          fit: BoxFit.fitHeight,
                                                          image: NetworkImage(
                                                              subcategory.photo_url)
                                                      )
                                                  )
                                              ),
                                            ),
                                            title: Padding(
                                              padding: const EdgeInsets.only(bottom: 4.0),
                                              child: Text(
                                                  subcategory.name,
                                                  style: const TextStyle(
                                                      fontWeight: FontWeight.bold,
                                                      color: Colors.black87
                                                  )
                                              ),
                                            ),
                                            subtitle: Padding(
                                              padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
                                              child: Text.rich(
                                                TextSpan(
                                                  text: 'Last Topic: ',
                                                  style: const TextStyle(
                                                    fontWeight: FontWeight.bold,
                                                    color: Colors.black54
                                                  ),
                                                  children: <TextSpan>[
                                                    TextSpan(
                                                        text: subcategory.latest_topic!,
                                                        style: const TextStyle(color: Colors.white70)
                                                    )
                                                  ],
                                                )
                                              ),
                                            ),
                                            // subtitle: Text(
                                            //     'Last Topic: ${subcategory.latest_topic!}',
                                            //     style: TextStyle(
                                            //         color: Colors.white,
                                            //         fontWeight: FontWeight.bold
                                            //     )
                                            // ),
                                          ),
                                          Row(
                                            mainAxisAlignment: MainAxisAlignment.end,
                                            children: <Widget>[
                                              RichText(
                                                text: TextSpan(
                                                  children: [
                                                    const WidgetSpan(
                                                      child: Icon(Icons.live_help, size: 20, color: Colors.redAccent,),
                                                    ),
                                                    TextSpan(
                                                      text: " ${subcategory.topics_count}",
                                                      semanticsLabel: "Topics count"
                                                    ),
                                                  ],
                                                ),
                                              ),
                                              const SizedBox(width: 8),
                                              RichText(
                                                text: TextSpan(
                                                  children: [
                                                    const WidgetSpan(
                                                      child: Icon(Icons.question_answer, size: 20, color: Colors.redAccent,),
                                                    ),
                                                    TextSpan(
                                                        text: " ${subcategory.comments_count}",
                                                        semanticsLabel: "Topics count"
                                                    ),
                                                  ],
                                                ),
                                              ),
                                              const SizedBox(width: 8),
                                            ],
                                          ),
                                        ],
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            );
                          });
                    } else if (snapshot.hasError) {
                      return const Text('Something went wrong');
                    }

                    return const Center(
                        child: CircularProgressIndicator()
                    );
                  }),
            ),
          ],
        ));
  }
}

As you can see, I'm calling setState() on the first ListView.builder ElevatedButton in order to update the category_id value. The purpose of that var is for the second ListView.builder(futureSubcategoriesFromCategory = subcategoryApi.fetchSubcategoriesFromCategory(category_id);). But I'm probably doing something wrong since the widget doesn't reload.

Can anyone help me please?

Answer

Question answered by omar h (source).

setState will rebuild the screen, i.e the build function will be called again, your code which will change the second ListView.builder is in the initState so it won't get executed again with the new value of category_id

a quick fix would be to add this to your setState

futureSubcategoriesFromCategory = subcategoryApi.fetchSubcategoriesFromCategory(category.id);
DART FLUTTER FLUTTER-LAYOUT
SHARE: