Search code examples
flutterdartlistviewnested-listsflutter-futurebuilder

How to avoid using FutureBuilder as it is duplicating items in setState


I believe I am having issues with using setState in a futurebuilder - but Im not sure how to change this? Below you can see I am building a listView using a futureBuilder (http request to api) and connecting it to my ProjectModel model. From here I filter the values I need into List<ProjectSearch> searchList. What I am finding is when I try to implement a search box to filter through the objects I believe the setState is causing the futurebuilder to rebuild the page each time causing duplications. How can I separate the futurebuilder from the listView so that it only displays the one list object as the user types?

        class _HomePageState extends State<HomePage> {
          TextEditingController controller = TextEditingController();
           late final Future<ProjectModel> futureProjects;

            List<ProjectSearch> searchList = [];
            List<ProjectSearch> finder = [];
            var jobNames = [];
            var jobNumbers = [];
            var techs = [];
            var pms = [];
            var address = [];
            var majors = [];
            var budget = [];

          @override
          void initState() {
          super.initState();
         futureProjects = fetchProjects();
         }

         @override
         Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
            title: const Text(
                 'Upcoming/Live Projects',
             style: TextStyle(
                 color: Colors.white,
                fontSize: 18,
                 fontWeight: FontWeight.bold,
                 ),
               ),
              backgroundColor: ColorConstants.darkScaffoldBackgroundColor,
             ),
           drawer: const CustomDrawer(),
            backgroundColor: ColorConstants.lightScaffoldBackgroundColor,

  
        body: Center(
        child: FutureBuilder<ProjectModel>(
            future: futureProjects,
            builder: (context, snapshot) {
              // finder.clear();
              if (snapshot.hasData) {
                var data = snapshot.data!;
                var columns = data.columns;
                var rows = data.rows;

                for (var item in rows!) {
                  var cells = item.cells;
                  for (var elements in cells!) {
                    if (elements.columnId != null) {
                      if (elements.columnId == 2057691532158852) {
                        var displayValues = elements.displayValue;
                        if (displayValues != null) {
                          jobNames.add(displayValues);
                        }
                        if (displayValues == null) {
                          pms.removeLast();
                          techs.removeLast();
                          address.removeLast();
                          majors.removeLast();
                          budget.removeLast();
                        }
                      }
                      if (elements.columnId == 697505454286724) {
                        var projectNumber = elements.displayValue;
                        if (projectNumber != null) {
                          jobNumbers.add(projectNumber);
                        }
                      }
                      if (elements.columnId == 7452904895342468) {
                        var techAssigned = elements.displayValue;
                        if (techAssigned != null) {
                          if (techAssigned == 'ts@ag.com.au') {
                            techAssigned = 'Ts';
                            techs.add(techAssigned);
                          } else {
                            techs.add(techAssigned);
                          }
                        }
                        if (techAssigned == null) {
                          techAssigned = 'No tech assigned as yet';
                          techs.add(techAssigned);
                        }
                      }
                      if (elements.columnId == 2949305267971972) {
                        var pmName = elements.displayValue;
                        if (pmName != null) {
                          pms.add(pmName);
                        }
                        if (pmName == null) {
                          pmName = 'No project manager allocated';
                          pms.add(pmName);
                        }
                      }
                      if (elements.columnId == 5201105081657220) {
                        var addressValue = elements.displayValue;
                        if (addressValue != null) {
                          address.add(addressValue);
                        }
                        if (addressValue == null) {
                          addressValue = '';
                          address.add(addressValue);
                        }
                      }
                      if (elements.columnId == 52961559766916) {
                        var majorValue = elements.displayValue;
                        if (majorValue != null) {
                          majors.add(majorValue);
                        }
                        if (majorValue == null) {
                          majorValue = 'No';
                          majors.add(majorValue);
                        }
                      }
                      if (elements.columnId == 4226807856686980) {
                        var budgetHours = elements.displayValue;
                        if (budgetHours != null) {
                          budget.add(budgetHours);
                        }
                        if (budgetHours == null) {
                          budgetHours = 'TBA';
                          budget.add(budgetHours);
                        }
                      }
                    }
                  }
                }

                int index = 0;
                for (int i = 0; i < jobNames.length; i++) {
                  // List<ProjectSearch> innerMap = [];
                  ProjectSearch myProjects = ProjectSearch(
                      address: jobNames[index],
                      budget: budget[index],
                      jobNumber: jobNumbers[index],
                      major: majors[index],
                      name: jobNames[index],
                      pM: pms[index],
                      tech: techs[index]);

                  index++;

                  searchList.add(myProjects);
                }
                return Container(
                  child: Column(
                    children: <Widget>[
                      Padding(
                        padding:
                            const EdgeInsets.only(left: 20, top: 20, right: 20),
                        child: TextField(
                          textAlign: TextAlign.center,
                          controller: controller,
                          onChanged: search,
                          keyboardType: const TextInputType.numberWithOptions(
                              signed: true),
                          // keeps going....
                      ),
                      Expanded(
                        child: ListView.builder(
                            shrinkWrap: true,
                            itemCount: finder.length,
                            itemBuilder: (context, index) {
                              // print(finder.length);
                              final projectData = finder[index];
                              return MaterialButton(
                                onPressed: () => showModalBottomSheet<void>(
                                  backgroundColor: Colors.transparent,
                                  context: context,
                                  builder: (BuildContext context) {
                                    return ClipRRect(
                                      borderRadius: const BorderRadius.only(
                                          topRight: Radius.circular(27.0),
                                          topLeft: Radius.circular(27.0)),
                                      child: Container(
                                        height: 1000,
                                        color: ColorConstants
                                            .secondaryDarkAppColor,
                                        child: Column(
                                          mainAxisSize: MainAxisSize.min,
                                          children: <Widget>[
                                            Flexible(
                                              child: Container(
                                                height: 400,
                                                margin: const EdgeInsets.only(
                                                    top: 20,
                                                    left: 20,
                                                    right: 20),
                                                child: Column(
                                                  crossAxisAlignment:
                                                      CrossAxisAlignment.start,
                                                  mainAxisAlignment:
                                                      MainAxisAlignment
                                                          .spaceAround,
                                                  children: [
                                                    const Padding(
                                                        padding:
                                                            EdgeInsets.only(
                                                                top: 10.0,
                                                                bottom: 5.0)),
                                                    const Center(
                                                      child: Text(
                                                        'Project Details',
                                                        style: TextStyle(
                                                            color: Colors.white,
                                                            fontSize: 20,
                                                            fontWeight:
                                                                FontWeight
                                                                    .w700),
                                                      ),
                                                    ),
                                                    Row(

                                                      children: [
                                                        const Text(
                                                          'Project: ',
                                                          style: TextStyle(
                                                              color: Colors
                                                                  .white70,
                                                              fontWeight:
                                                                  FontWeight
                                                                      .w600,
                                                              fontSize: 17),
                                                        ),
                                                        const SizedBox(
                                                            width: 20),
                                                        Flexible(
                                                          child: Padding(
                                                            padding:
                                                                const EdgeInsets
                                                                        .symmetric(
                                                                    horizontal:
                                                                        8.0),
                                                            child: Text(
                                                              projectData.name,
                                                              overflow:
                                                                  TextOverflow
                                                                      .ellipsis,
                                                              maxLines: 2,
                                                              style: const TextStyle(
                                                                  color: Colors
                                                                      .white,
                                                                  fontWeight:
                                                                      FontWeight
                                                                          .w600,
                                                                  fontSize: 17),
                                                            ),
                                                          ),
                                                        ),
                                                      ],
                                                    ),                                     
                                          ],
                                        ),
                                      ),
                                    );
                                  },
                                ),
                                child: Container(
                                  height: 50,
                                  margin: const EdgeInsets.only(
                                      top: 30, left: 20, right: 20),
                                  decoration: const BoxDecoration(
                                    color: ColorConstants
                                        .darkScaffoldBackgroundColor,
                                    borderRadius:
                                        BorderRadius.all(Radius.circular(8)),
                                  ),
                                  padding: const EdgeInsets.all(15),
                                  child: Center(
                                    child: Text(
                                      projectData.name,
                                      textAlign: TextAlign.center,
                                      style: const TextStyle(
                                          color: Colors.white,
                                          fontWeight: FontWeight.w600,
                                          fontSize: 17),
                                    ),
                                  ),
                                ),
                              );
                            }),
                      ),
                    ],
                  ),
                );
              } else if (snapshot.hasError) {
                print(snapshot);
                return Text(
                  '${snapshot.error}',
                  style: const TextStyle(color: Colors.white),
                );
              } else {
                return const CircularProgressIndicator();
              }
            }),
           ),
          );
          }

        void search(String query) {
   
          final suggestions = searchList.where((search) {
          final projectName = search.name.toLowerCase();
          final input = query.toLowerCase();
           return projectName.contains(input);
           }).toList();

           setState(() {
            finder.clear();
            finder = suggestions;
           });
          } 
          }

Here is what my UI looks like after searching.... UI


Solution

  • Inside your FutureBuilder's builder, try this:

    searchList = [];// <---- add this
    int index = 0;
    for (int i = 0; i < jobNames.length; i++) {
       // List<ProjectSearch> innerMap = [];
       ProjectSearch myProjects = ProjectSearch(
       address: jobNames[index],
       budget: budget[index],
       jobNumber: jobNumbers[index],
       major: majors[index],
       name: jobNames[index],
       pM: pms[index],
       tech: techs[index]);
    
       index++;
    
       searchList.add(myProjects);
     }
    

    and also because when every time keyboard status change your FutureBuilder rebuild again do this:

    var data = snapshot.data!;
    var columns = data.columns;
    var rows = data.rows;
    
    jobNames = [];
    jobNumbers = [];
    techs = [];
    pms = [];
    address = [];
    majors = [];
    budget = [];
    
    for (var item in rows!) {
       var cells = item.cells;
       for (var elements in cells!) {
         ...
       }
    }