Flutter set state not updating my UI with new data

Viewed 1210

I have a ListView.builder widget wrapped inside a RefreshIndicator and then a FutureBuilder. Refreshing does not update my list, I have to close the app and open it again but the refresh code does the same as my FutureBuilder.

Please see my code below, when I read it I expect the widget tree to definitely update.

@override
void initState() {
  super.initState();
  taskListFuture= TaskService().getTasks();
}

@override
  Widget build(BuildContext context) {
    return Consumer<TaskData>(builder: (context, taskData, child) {
      return FutureBuilder(
          future: taskListFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              taskData.tasks = (snapshot.data as ApiResponseModel).responseBody;
              return RefreshIndicator(
                onRefresh: () async {
                  var responseModel = await TaskService().getTasks();
                  setState(() {
                    taskData.tasks = responseModel.responseBody;
                  });
                },
                child: ListView.builder(
...
...

Let me know if more code is required, thanks in advance!

Points

  • I am using a StatefulWidget
  • Task data is a class that extends ChangeNotifier
  • When I debug the refresh I can see the new data in the list, but the UI does not update

getTasks()

  Future<ApiResponseModel> getTasks() async {
    try {
      var _sharedPreferences = await SharedPreferences.getInstance();
      var userId = _sharedPreferences.getString(PreferencesModel.userId);

      var response = await http.get(
        Uri.parse("$apiBaseUrl/$_controllerRoute?userId=$userId"),
        headers: await authorizeHttpRequest(),
      );

      var jsonTaskDtos = jsonDecode(response.body);
      var taskDtos= List<TaskDto>.from(
          jsonTaskDtos.map((jsonTaskDto) => TaskDto.fromJson(jsonTaskDto)));
      return ApiResponseModel(
          responseBody: taskDtos,
          isSuccessStatusCode: isSuccessStatusCode(response.statusCode));
    } catch (e) {
      return null;
    }
  }
2 Answers

The issue here seems to be that you are updating a property that is not part of your StatefulWidget state.

setState(() {
  taskData.tasks = responseModel.responseBody;
});

That sets a property part of TaskData. My suggestion is to only use the Consumer and refactor TaskService so it controls a list of TaskData or similar. Something like:

Provider

class TaskService extends ChangeNotifier {
  List<TaskData> _data;

  load() async {
    this.data = await _fetchData();
  }

  List<TaskData> get data => _data;

  set data(List<TaskData> data) {
    _data = data;
    notifyListeners();
  }
}

Widget

class MyTaskList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<TaskService>(builder: (context, service, child) {
      return RefreshIndicator(
          onRefresh: () {
            service.getTasks();
          },
          child: ListView.builder(
                   itemCount: service.data.length,
                   itemBuilder: (BuildContext context, int index) {
                     return MyTaskItem(data:service.data[index]);
                   },
                 ),
         ); 
    });
  }
}

and make sure to call notifyListeners() in the service.getTasks() method to make the Consumer rebuild

I think (someone will correct me if I'm wrong) the problem is that you are using the FutureBuilder, once it's built, you need to refresh to whole widget for the FutureBuilder to listen to changes. I can suggest a StreamBuilder that listens to any changes provided from the data model/api/any kind of stream of data. Or better yet, you can use some sort of state management like Provider and use Consumer from the Provider package that notifies the widget of any changes that may occurred.

Related