Search code examples
flutterlistsharedpreferences

Flutter: Why is my table only loading after I save the data and not when I load it in initState?


I have a list of objects that I am attempting to save

List<Exercise> todaysExercises = [];

 //Load data from shared preferences
 Future<void> loadData() async {
   final SharedPreferences prefs = await SharedPreferences.getInstance();
   final String? myDataString = prefs.getString('todaysExercises');
   if (myDataString != null) {
     final List<dynamic> myDataJson = jsonDecode(myDataString);
     todaysExercises =
         myDataJson.map((dynamic item) => Exercise.fromMap(item)).toList();
   }
 }

 // Save Todays exercieses to shared preferences
 Future<void> saveData() async {
   final SharedPreferences prefs = await SharedPreferences.getInstance();
   final List<dynamic> myDataJson =
       todaysExercises.map((Exercise exercise) => exercise.toMap()).toList();
   final String myDataString = jsonEncode(myDataJson);
   await prefs.setString('todaysExercises', myDataString);
 }

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

Prior to clicking add exercise: Prior to clicking add exercise

After clicking add exercise, calling SaveData(): After clicking add exercise, calling SaveData()

I do not understand why initState is not calling loadData() correctly. If my understanding is correct it should set todays exercises to the saved date prior to my use of TodaysExercises when passing it to my ExerciseList().

ExerciseList(
            userExercises: todaysExercises,
            editFunction: _showEditDialog,
          ),

I'm very new to shared preferences so thank you for the help!


Solution

  • That's because when initState is called, loadData is invoked, but it does not block the execution of initState. This means that the widget continues to build BEFORE loadData has finished loading the data from SharedPreferences. As a result, the widget is built with the initial empty data BEFORE the data is loaded from SharedPreferences.

    To fix this issue, you can use a FutureBuilder to wait for the data to be loaded before building the widget. Here's an example of how to use FutureBuilder to solve this issue.

    I do not know what your code looks like, but take this as an example of what I mean:

    FutureBuilder<List<Exercise>>(
          future: loadData(),
          builder: (BuildContext context, AsyncSnapshot<List<Exercise>> snapshot) {
            if (snapshot.hasData) {
              // Use the loaded data here
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(
                    title: Text(snapshot.data![index].name),
                    subtitle: Text(snapshot.data![index].description),
                  );
                },
              );
            } else {
              // Show a loading indicator while the data is loading
              return Center(child: CircularProgressIndicator());
            }
          },
        );