Search code examples
flutterflutter-listview

How to update the list when I click the button in flutter?


I have the below code for fetching updated data on changing the filter value and pressing update. The data is getting fetched, but the list is not getting updated. How to make this work. The list is showing properly once loaded but it is not updating.

child: ElevatedButton(
  style: ElevatedButton.styleFrom(
    primary: Colors.red,
  ),
  onPressed: () async {
    setState(() {
      accordianFlag = false;
    });
    // programsListFuture = getCropNutritionPrograms();
    // var updatedProgramsList = await getCropNutritionPrograms();
    // setState(() {
    //   programsListFuture = Future.value(updatedProgramsList);
    // });
    fetchData();  // <--- not working. I tried various other method, but list is not updating
  },
  child: Text(
    'Update',
    style: TextStyle(
      color: Colors.white,
      fontWeight: FontWeight.bold
    ),
  ),
)

Full code:

class CropNutritionProgramOverviewScreen extends StatefulWidget {
  CropNutritionProgramOverviewScreen({Key? key, required this.showAppbar, required this.showBottombar}) : super(key: key);
  bool showAppbar = false;
  bool showBottombar = false;

  @override
  State<CropNutritionProgramOverviewScreen> createState() => _CropNutritionProgramOverviewScreenState();
}

class _CropNutritionProgramOverviewScreenState extends State<CropNutritionProgramOverviewScreen> {
  Future<RegionsList?>? regionsListFuture;
  Future<CropsModel?>? cropsListFuture;
  Future<CropNutritionProgramModel?>? programsListFuture;
  bool accordianFlag = false;
  bool _showContent = false;
  String regionSelected = '1';
  String cropSelected = '0';
  List<DropdownMenuItem<String>> regionDropdownItems = [];
  List<DropdownMenuItem<String>> cropDropdownItems = [];

  _CropNutritionProgramOverviewScreenState();

  @override
  void initState() {
    super.initState();
  }

  Future<RegionsList?> getRegions() async {
    RegionsList regionsList = await getRegionsAPI();
    print("flutter - regions list: " + regionsList.toString());
    
    List<Region> regions = regionsList.regions ?? [];
    for (Region region in regions) {
      regionDropdownItems.add(DropdownMenuItem(
        child: Text(region.name),
        value: region.id.toString(),
      ));
    }
    if (regions.length > 0) {
      regionSelected = regions[0].id.toString();
    }

    return regionsList;
  }

  Future<CropsModel?> getCrops(int regionId) async {
    CropsModel cropsList = await getCropsAPI(regionId);
    print("flutter - cropsList: " + cropsList.toString());

    List<Crop> crops = cropsList.cropsList ?? [];
    for (Crop crop in crops) {
      cropDropdownItems.add(DropdownMenuItem(
        child: Text(crop.name),
        value: crop.id.toString(),
      ));
    }

    return cropsList;
  }

  Future<CropNutritionProgramModel?> getCropNutritionPrograms() async {
    var regionId = int.parse(regionSelected);
    var cropId = int.parse(cropSelected);
    CropNutritionProgramModel programsModel = await getCropNutritionProgramsAPI(regionId, cropId);
    print("flutter - cropNutritionProgramsModel: " + programsModel.toString());
    return programsModel;
  }

  Future<List<dynamic>> fetchData() async {
    return await Future.wait([getRegions(), getCrops(0), getCropNutritionPrograms()]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: widget.showAppbar ? TopAppBar() : null,
      drawer: widget.showAppbar ? TopDrawer() : null,
      body: FutureBuilder<List<dynamic>>(
        future: fetchData(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return _buildLoadingIndicator();
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else if (snapshot.hasData) {
            var regionsList = snapshot.data?.elementAt(0) as RegionsList?;
            var cropsList = snapshot.data?.elementAt(1) as CropsModel?;
            var programsList = snapshot.data?.elementAt(2) as CropNutritionProgramModel?;
            List<Region> regions = regionsList == null ? [] : regionsList!.regions;
            List<Crop> crops = cropsList == null ? [] : cropsList!.cropsList;
            List<CropNutritionProgram> programs = programsList == null ? [] : programsList.cropNutritionPrograms;
            return Container(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Padding(
                    padding: const EdgeInsets.only(top: 16, left: 16, bottom: 8),
                    child: Text(
                      'Crop Nutrition Programs',
                      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
                    ),
                  ),
                  Expanded(
                    child: SingleChildScrollView(
                      child: Container(
                        padding: EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            SizedBox(height: 4.0),
                            Image.asset(
                              regionsAssetPath,
                              fit: BoxFit.cover,
                            ),
                            SizedBox(height: 16.0),
                            Center(
                              child: Text(
                                "Discover Crop Nutrition Program recommendations for your state!",
                                textAlign: TextAlign.center,
                              ),
                            ),
                            SizedBox(height: 16.0),
                            Padding(
                              padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 8.0),
                              child: StatefulBuilder(
                                builder: (BuildContext context, StateSetter setState) {
                                  return Card(
                                    shape: new RoundedRectangleBorder(
                                      side: new BorderSide(color: Colors.red, width: 1.0),
                                      borderRadius: BorderRadius.circular(4.0),
                                    ),
                                    child: Column(
                                      children: [
                                        Container(
                                          height: 40,
                                          child: Transform.translate(
                                            offset: Offset(0, -8),
                                            child: ListTile(
                                              title: const Text("Filter By", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: Colors.red)),
                                              leading: Icon(Icons.filter_alt_outlined, color: Colors.red,),
                                              trailing: IconButton(
                                                icon: Icon(
                                                  _showContent ? Icons.arrow_drop_up : Icons.arrow_drop_down, color: Colors.red,),
                                                onPressed: () {
                                                  setState(() {
                                                    _showContent = !_showContent;
                                                  });
                                                },
                                              ),
                                              onTap: () {
                                                setState(() {
                                                  _showContent = !_showContent;
                                                });
                                              },
                                            ),
                                          ),
                                          decoration: new BoxDecoration(
                                            border: new Border(bottom: new BorderSide(color: Colors.red)),
                                          ),
                                        ),
                                        _showContent
                                        ? Container(
                                            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
                                            child: Column(
                                              crossAxisAlignment: CrossAxisAlignment.start,
                                              mainAxisSize: MainAxisSize.min,
                                              children: [
                                                SizedBox(
                                                  height: 5,
                                                ),
                                                Padding(
                                                  padding:
                                                  const EdgeInsets.only(left: GlobalMargin.leftAccordian, right: GlobalMargin.rightAccordian),
                                                  child: InputDecorator(
                                                    decoration: const InputDecoration(
                                                        contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0),
                                                        border: OutlineInputBorder(),
                                                    ),
                                                    child: DropdownButtonHideUnderline(
                                                      child: DropdownButton<String>(
                                                        value: regionSelected,
                                                        isExpanded: true,
                                                        isDense: true,
                                                        hint: Text('Select Region'),
                                                        icon:
                                                        const Icon(Icons.arrow_drop_down_outlined),
                                                        style: const TextStyle(color: Colors.black),
                                                        onChanged: (value) {
                                                          setState(() {
                                                            accordianFlag = true;
                                                            if (value != null) {
                                                              regionSelected = value;
                                                            }
                                                          });
                                                        },
                                                        items: regionDropdownItems
                                                      ),
                                                    ),
                                                  ),
                                                ),
                                                SizedBox(
                                                  height: 10,
                                                ),
                                                Padding(
                                                  padding:
                                                  const EdgeInsets.only(left: GlobalMargin.leftAccordian, right: GlobalMargin.rightAccordian),
                                                  child: InputDecorator(
                                                    decoration: const InputDecoration(
                                                        contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0),
                                                        border: OutlineInputBorder(),
                                                    ),
                                                    child: DropdownButtonHideUnderline(
                                                      child: DropdownButton<String>(
                                                        value: cropSelected,
                                                        isExpanded: true,
                                                        isDense: true,
                                                        hint: Text('Select Crop'),
                                                        icon:
                                                        const Icon(Icons.arrow_drop_down_outlined),
                                                        style: const TextStyle(color: Colors.black),
                                                        onChanged: (value) {
                                                          setState(() {
                                                            accordianFlag = true;
                                                            if (value != null) {
                                                              cropSelected = value;
                                                            }
                                                          });
                                                        },
                                                        items: cropDropdownItems,
                                                      ),
                                                    ),
                                                  ),
                                                ),
                                                SizedBox(
                                                  height: 10,
                                                ),
                                                Container(
                                                  width: double.infinity,
                                                  alignment: Alignment.topCenter,
                                                  child: Container(
                                                    width: 200,
                                                    child: ElevatedButton(
                                                      style: ElevatedButton.styleFrom(
                                                        primary: Colors.red,
                                                      ),
                                                      onPressed: () async {
                                                        setState(() {
                                                          accordianFlag = false;
                                                        });
                                                        // programsListFuture = getCropNutritionPrograms();
                                                        // var updatedProgramsList = await getCropNutritionPrograms();
                                                        // setState(() {
                                                        //   programsListFuture = Future.value(updatedProgramsList);
                                                        // });
                                                        fetchData();
                                                      },
                                                      child: Text(
                                                        'Update',
                                                        style: TextStyle(
                                                          color: Colors.white,
                                                          fontWeight: FontWeight.bold
                                                        ),
                                                      ),
                                                    )
                                                  )
                                                ),
                                              ],
                                            ),
                                          )
                                        : Container(),
                                      ],
                                    ),
                                  );
                                },
                              ),
                            ),
                            ListView.separated(
                              shrinkWrap: true,
                              physics: NeverScrollableScrollPhysics(),
                              itemCount: programs.length,
                              separatorBuilder: (_, __) => Divider(height: 1),
                              itemBuilder: (context, index) {
                                return Padding(
                                  padding: EdgeInsets.fromLTRB(-8, 8, 0, 8),
                                  child: ListTile(
                                    title: Text("${programs[index].name}"),
                                    trailing: Icon(Icons.arrow_forward, color: Colors.red),
                                    onTap: () {
                                      print("crop nutrition program tapped");
                                      navigateToCropNutritionListScreen(context, int.parse(regionSelected), 0);
                                    },
                                  ),
                                );
                              }
                            ),
                          ]
                        ),
                      ),
                    ),
                  ),  
                ],
              ),
            );
          } else {
            return Text('No data available');
          }
        },
      ),
    );
  }

  void navigateToCropNutritionListScreen(BuildContext context, int regionId, int stateId) {
    var provider = Provider.of<AppBarContitonProvider>(
      context,
      listen: false,
    );
    provider.setInnerPageLoaded(true);
    provider.setPdfScreenLoaded(false, '');
    NavKey.key.currentState?.pushNamed(Routes.cropNutritionList, 
      arguments: CropNutritionListArguments(regionId, stateId)).then((value) {
    });
  }

  Widget _buildLoadingIndicator() {
    return Padding(
      padding: EdgeInsets.all(8.0),
      child: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}


I have updated the code to use only one future variable but still the list is not rebuilding when new data is fetched.
Code: https://pastebin.com/AYENDAkr

Could you please help me with this? Thanks.


I am new to flutter. I don't understand why when a variable changes the corresponding widget does not update.

programsListFuture = getCropNutritionPrograms();
var updatedProgramsList = await getCropNutritionPrograms();
  setState(() {
    programsListFuture = Future.value(updatedProgramsList);
});

The future is assigned new value, but why is the list not updated?


Solution

  • If you want to continuously monitor the changes of a certain attribute, you can use ValueListenableBuilder and ValueNotifier to achieve that.

    ValueListenableBuilder DartPad

    You can run this DartPad to see the effect.

    I use ValueListenableBuilder to listen to the datasource. If the datasource changes, it will re-execute the build.

    You can copy and run this code.

    import 'package:flutter/material.dart';
    import 'dart:math';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const ValueListenableDemo(),
        );
      }
    }
    
    class ValueListenableDemo extends StatefulWidget {
      const ValueListenableDemo({super.key});
    
      @override
      State<ValueListenableDemo> createState() => _ValueListenableDemoState();
    }
    
    class _ValueListenableDemoState extends State<ValueListenableDemo> {
      ValueNotifier<List<int>> datasource = ValueNotifier<List<int>>([1, 2, 3]);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(),
            body: Column(
              children: [
                const Text(
                  'Click the refresh button in the bottom right corner to update the list.',
                  style: TextStyle(fontSize: 18),
                ),
                Expanded(
                  child: ValueListenableBuilder<List<int>>(
                    valueListenable: datasource,
                    builder: (context, value, child) {
                      return ListView.builder(
                        itemCount: value.length,
                        itemBuilder: (context, index) {
                          return ListTile(
                            title: Text('Item ${value[index]}'),
                          );
                        },
                      );
                    },
                  ),
                ),
              ],
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                int count = Random().nextInt(10) + 1;
    
                datasource.value = List<int>.generate(count, (index) => index);
              },
              child: const Icon(Icons.refresh),
            ));
      }
    }