Home > Software engineering >  Flutter - Load variables with SharedPreferences
Flutter - Load variables with SharedPreferences

Time:06-29

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'];
  }
}

CodePudding user response:

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.

CodePudding user response:

A short answer is that when you call loadData(); inside initState the function is performed asynchronously relative to the rest of the widget, so your Scaffold is built before the data is available. This is why you are seeing the data in from your print but not in the app.

One way to address it is to us a https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

  • Related