fluttericonsfavorites

Why my Flutter JSON data didn't updated from setState?


I've made the JSON data and appear it into FutureBuilder with ListView.builder widget. I want to create a favorite Icon in the trailing of the ListView.builder. So i created it with IconButton, but when I create setState to make some item as favorited, the data didn't updated.

Here is my code

import 'package:flutter/material.dart';
import 'package:json_test/class/doa.dart';
import 'package:json_test/page/DoaPage.dart';

class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  Future<List<Doa>> fetchDoa(BuildContext context) async {
    final jsonstring =
        await DefaultAssetBundle.of(context).loadString('assets/doa.json');
    return doaFromJson(jsonstring);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("JSON Data test"),
        ),
        body: Container(
            child: FutureBuilder(
                future: fetchDoa(context),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ListView.builder(
                      itemCount: snapshot.data.length,
                      itemBuilder: (BuildContext context, int index) {
                        Doa doa = snapshot.data[index];
                        return Card(
                            margin: EdgeInsets.all(8),
                            child: ListTile(
                                title: Text(doa.judul),
                                onTap: () {
                                  Navigator.of(context).push(MaterialPageRoute(
                                      builder: (BuildContext context) =>
                                          DoaPage(
                                            doa: doa,
                                          )));
                                },
                                trailing: IconButton(
                                  icon: Icon(
                                    doa.fav
                                        ? Icons.favorite
                                        : Icons.favorite_border,
                                    color: doa.fav ? Colors.red : null,
                                  ),
                                  onPressed: () =>
                                      setState(() => doa.fav = !doa.fav),
                                )));
                      },
                    );
                  }
                  return CircularProgressIndicator();
                })));
  }
}

and this is the preview


Solution

  • the thing is that when you call setState you run build again, and that in turn runs the FutureBuilder again with the original Doa object.

    you need to keep a variable that will hold the changes in your _MainPageState outside the build method, theres a few ways to do that and in your case its a little more complicated because you need the context in your fetchDoa.

    one workaround is creating a doaList variable to hold the fetched data outside the build and changing the fetchDoa function to set the doaList instead of returning it(that's why it's Future now. but that's not enough because the FutureBuilder will just set the doaList from scrach every time build runs, so we'll add a _isInit bool to check if its the first time running build. after that you should replace all the 'snapshot.data' with doaList as the snapshot holds nothing

      class _MainPageState extends State<MainPage> {
      List<Doa> doaList;
      bool _isInit = true; 
    
      Future<void> fetchDoa(BuildContext context) async {
        final jsonstring =
            await DefaultAssetBundle.of(context).loadString('assets/doa.json');
        doaList = doaFromJson(jsonstring);
        _isInit = false;
      }
    
      @override
       Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("JSON Data test"),
             ),
            body: Container(
                child: FutureBuilder(
                    future: _isInit? fetchDoa(context): Future(),
                    builder: (context, _) {
                  
    

    try this and tell me if it works :)