flutterdartsharedpreferencesflutter-sharedpreference

Flutter - Load variables with SharedPreferences


I am learning how to use the SharedPreferences library in Flutter.

I created this code and I would like the counter and counter2 variables once I close and reopen the app to remain as the last save.

However, when I reopen the app the counter and counter2 values return to 0. Can anyone explain to me where I am going wrong?

Thank you.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'data.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int counter = 0;
  int counter2 = 0;

  increment() {
    setState(() {
      counter += 1;
      counter2 += 2;
    });
  }

  loadData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    setState(() {
      String? json = prefs.getString('UserData');
      print('loaded json: $json');

      if (json == null) {
        print('NO DATA (null)');
      } else {
        Map<String, dynamic> map = jsonDecode(json);
        print('map $map');
        final data = Data.fromJson(map);
        print('Data ${data.counter}, ${data.counter2}');
      }
    });
  }

  saveData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    final _data = Data(counter: counter, counter2: counter2);

    String json = jsonEncode(_data);
    print('saved json: $json');
    prefs.setString('UserData', json);
  }

  clearData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.clear();
    print('data cleared');
  }

  /// dichiarare l' initState()
  @override
  void initState() {
    super.initState();
    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              'c: $counter, c2: $counter2',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          increment();
          saveData();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class Data {
  int counter = 0;
  int counter2 = 0;

  Data({required this.counter, required this.counter2});

  Map<String, dynamic> toJson() {
    return {
      'counter': counter,
      'counter2': counter2,
    };
  }

  Data.fromJson(Map<String, dynamic> json) {
    counter = json['counter'];
    counter2 = json['counter2'];
  }
}

Solution

  • I agree with the other answer, the best is to use a FutureBuilder. But you can make your current code work with simply adding two lines at the end of loadData:

    loadData() async {
      SharedPreferences prefs = await SharedPreferences.getInstance();
    
      setState(() {
        String? json = prefs.getString('UserData');
        print('loaded json: $json');
    
        if (json == null) {
          print('NO DATA (null)');
        } else {
          Map<String, dynamic> map = jsonDecode(json);
          print('map $map');
          final data = Data.fromJson(map);
          print('Data ${data.counter}, ${data.counter2}');
    
          // add these lines
          counter = data.counter;
          counter2 = data.counter2;
        }
      });
    }
    

    What happens (as the other answer says) is that your widget is first built without knowing the values from SharedPreferences. After a little time this first build is done, the loadData future completes, and with setState the widget is rebuilt.

    In a real application you'd like to avoid unnecessary builds, so you'd rather display a progress indicator while async data is being loaded, check FutureBuilder.