While developing a mobile application, you may want to save a custom object for later use, for example, saving a user’s info when the application is closed and using it when the app is opened later.

To do so, we’ll encode our object into a JSON string and save it in the SharedPreferences space. When we want to read the saved data, we only have to decode from a JSON string back to our object. Saving data in SharedPreferences is an easy solution when you want to save small amounts of data.

. . .

Create a Custom Object

For our example, we’ll use a very simple object to store some user information.

Our object will have three attributes: name, age, and location; each one represented by a string.

Our class is really simple. In our example, it’s initiated as an object with null attributes and each one is assigned later.

class User {
  String name;
  String age;
  String location;

  User();

  ...
  
};

. . .

JSON: Encode and Decode

To encode our user object into JSON, we must create a function inside the class that will do this conversion. It will create a map where each attribute (value) corresponds to a key:

Map<String, dynamic> toJson() => {
  'name': name,
  'age': age,
  'location': location,
};

This will convert our user object into a Map<String, dynamic>. To convert it to a JSON string, late we’ll later have to use the function json.encode(Map<String, dynamic>). This will convert our user object to a JSON string like:

{"name":"Alfonso","age":"21","location":"Portugal"}

This string will be saved in the SharedPreferences.

To decode from a JSON string to a user object, we must first use the function json.decode(JSON-String). This will return a Map<String, dynamic> which we can then convert into our user object:

User.fromJson(Map<String, dynamic> json)
    : name = json['name'],
      age = json['age'],
      location = json['location'];

Therefore, the final code for our user class will be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User {
  String name;
  String age;
  String location;

  User();

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        age = json['age'],
        location = json['location'];

  Map<String, dynamic> toJson() => {
        'name': name,
        'age': age,
        'location': location,
      };
}

. . .

SharedPreferences: Save and Read

First, in order to use the SharedPreferences, we have to use Flutter’s plugin for it. To do so, make sure dependencies in your pubspec.yaml file looks similar to this:

dependencies:
  ...
  shared_preferences: any

To save (write) something in SharedPreferences, we must provide a value that we want to save and a key that identifies it.

In our case, the value will be a string—the key is always a string.

The first thing we have to do is getInstance of the SharedPreferences:

final prefs = await SharedPreferences.getInstance();

Here, the code await will hold the program’s execution until we have the SharedPreferences instance. If you don’t understand asynchronous programming very well, you can read more about it in the Dart documentation.

After having the SharedPreferences instance, we’ll want to save the string value with a given key:

prefs.setString(key, value);

Remember that the value we want to save is a custom object, therefore, we need to encode it into a JSON string, as explained above. The code then becomes:

prefs.setString(key, json.encode(value));

Our write method will be:

static write(String key, value) async {
  final prefs = await SharedPreferences.getInstance();
  return await prefs.setString(key, json.encode(value));
}
Give special attention to the fact that the function is "async".

The read method is similar to the write one and its code is the following:

static read(String key) async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  if (prefs.containsKey(key)) {
    return json.decode(prefs.getString(key));
  } else {
    return null;
  }
}
Here we must check if the key exists and then decode the stored String (JSON string).

Prior, we should have organized every function used to manage the SharedPreferences in a class called SharedPref. The code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

class SharedPref {
  static read(String key) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    if (prefs.containsKey(key)) {
      return json.decode(prefs.getString(key));
    } else {
      return null;
    }
  }

  static write(String key, value) async {
    final prefs = await SharedPreferences.getInstance();
    return await prefs.setString(key, json.encode(value));
  }

  static remove(String key) async {
    final prefs = await SharedPreferences.getInstance();
    prefs.remove(key);
  }

  static clear() async {
    final prefs = await SharedPreferences.getInstance();
    prefs.clear();
  }
Also added the "remove" and "clear" methods.

. . .

Demo: Use it in an App

Since the SharedPref methods are static, we can save a user object to our SharedPreferences very simply:

SharedPref.save("user", userSave);

The tricky part of using this method for saving objects in a real app is retrieving the object itself, and not a Future<Dynamic>. The trick is having an asynchronous function in our app that will retrieve our object and update the application’s state when it’s ready.

The function will look something like:

loadSharedPrefs() async {
  try {
    User user = User.fromJson(await SharedPref.read("user"));
    setState(() {
      userLoad = user;
    });
  } catch (Excepetion) {
    // do something
  }
}

As before, the await will make sure that the function’s execution is on hold until we retrieved the value corresponding to the key “user” from the SharedPreferences. After it, we create a user object from the retrieved JSON string and update our application’s State.

I created a simple demo application to showcase all the above. Here is a gif of the app in action:


And here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import 'package:flutter/material.dart';
import 'SharedPref.dart';
import 'user.dart';

class Demo extends StatefulWidget {
  @override
  DemoView createState() {
    return DemoView();
  }
}

class DemoView extends State<Demo> {
  SharedPref sharedPref = SharedPref();
  User userSave = User();
  User userLoad = User();

  loadSharedPrefs() async {
    try {
      User user = User.fromJson(await SharedPref.read("user"));
      Scaffold.of(context).showSnackBar(SnackBar(
          content: new Text("Loaded!"),
          duration: const Duration(milliseconds: 500)));
      setState(() {
        userLoad = user;
      });
    } catch (Excepetion) {
      Scaffold.of(context).showSnackBar(SnackBar(
          content: new Text("Nothing found!"),
          duration: const Duration(milliseconds: 500)));
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        Container(
          height: 200.0,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Container(
                  height: 50.0,
                  width: 300.0,
                  child: TextField(
                    decoration: InputDecoration(hintText: "Name"),
                    onChanged: (value) {
                      setState(() {
                        userSave.name = value;
                      });
                    },
                  )),
              Container(
                  height: 50.0,
                  width: 300.0,
                  child: TextField(
                    decoration: InputDecoration(hintText: "Age"),
                    onChanged: (value) {
                      setState(() {
                        userSave.age = value;
                      });
                    },
                  )),
              Container(
                  height: 50.0,
                  width: 300.0,
                  child: TextField(
                    decoration: InputDecoration(hintText: "Location"),
                    onChanged: (value) {
                      setState(() {
                        userSave.location = value;
                      });
                    },
                  )),
            ],
          ),
        ),
        Container(
          height: 80.0,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              RaisedButton(
                onPressed: () {
                  SharedPref.save("user", userSave);
                  Scaffold.of(context).showSnackBar(SnackBar(
                      content: new Text("Saved!"),
                      duration: const Duration(milliseconds: 500)));
                },
                child: Text('Save', style: TextStyle(fontSize: 20)),
              ),
              RaisedButton(
                onPressed: () {
                  loadSharedPrefs();
                },
                child: Text('Load', style: TextStyle(fontSize: 20)),
              ),
              RaisedButton(
                onPressed: () {
                  SharedPref.remove("user");
                  Scaffold.of(context).showSnackBar(SnackBar(
                      content: new Text("Cleared!"),
                      duration: const Duration(milliseconds: 500)));
                  setState(() {
                    userLoad = User();
                  });
                },
                child: Text('Clear', style: TextStyle(fontSize: 20)),
              ),
            ],
          ),
        ),
        SizedBox(
          height: 300.0,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Text("Name: " + (userLoad.name ?? ""),
                  style: TextStyle(fontSize: 16)),
              Text("Age: " + (userLoad.age ?? ""),
                  style: TextStyle(fontSize: 16)),
              Text("Location: " + (userLoad.location ?? ""),
                  style: TextStyle(fontSize: 16)),
            ],
          ),
        ),
      ],
    );
  }
}