Skip to content
DeveloperMemos

A Basic SharedPreferences Tutorial for Flutter

Dart, Flutter, Flutter Tutorials2 min read

If you need to persist data in Flutter you have a few different options available to you. One of the most popular and simplest options is the shared_preferences package. In this tutorial I'll show you how you can use it in conjunction with the example counter app.

About shared_preferences

The shared_preferences package has been around for a long time. You can use it to persist simple key-value pairs or lists of Strings. Under the hood it uses SharedPreferences on Android and UserDefaults on iOS. It should only really be used for simple data because when you read values it loads everything - it's not like an SQLite database that you can perform queries on(and pull a limited set of results). Because of this, as your data grows the time it takes to load it from disk will increase. That being said, you can convert complex objects into JSON and store them as a String - it's possible. In the end you need to make a decision based on how much data you will save and what type of data it is.

Installation

You can install shared_preferences by adding the following line to your pubspec.yaml file under 'dependencies':

1shared_preferences: ^2.0.6

Alternatively you can use the following command:

1flutter pub add shared_preferences

For the purposes of this tutorial I'll be working with the example counter app that you get when you create a new Flutter project, so if you're going to follow along you should create a new Flutter project using the following command:

1flutter create counter_prefs

The UI

Like I said above, after creating a new project your main.dart file should have this Widget inside it:

1class MyHomePage extends StatefulWidget {
2 MyHomePage({Key? key, required this.title}) : super(key: key);
3
4 // This widget is the home page of your application. It is stateful, meaning
5 // that it has a State object (defined below) that contains fields that affect
6 // how it looks.
7
8 // This class is the configuration for the state. It holds the values (in this
9 // case the title) provided by the parent (in this case the App widget) and
10 // used by the build method of the State. Fields in a Widget subclass are
11 // always marked "final".
12
13 final String title;
14
15 @override
16 _MyHomePageState createState() => _MyHomePageState();
17}
18
19class _MyHomePageState extends State<MyHomePage> {
20 int _counter = 0;
21
22 void _incrementCounter() {
23 setState(() {
24 // This call to setState tells the Flutter framework that something has
25 // changed in this State, which causes it to rerun the build method below
26 // so that the display can reflect the updated values. If we changed
27 // _counter without calling setState(), then the build method would not be
28 // called again, and so nothing would appear to happen.
29 _counter++;
30 });
31 }
32
33 @override
34 Widget build(BuildContext context) {
35 // This method is rerun every time setState is called, for instance as done
36 // by the _incrementCounter method above.
37 //
38 // The Flutter framework has been optimized to make rerunning build methods
39 // fast, so that you can just rebuild anything that needs updating rather
40 // than having to individually change instances of widgets.
41 return Scaffold(
42 appBar: AppBar(
43 // Here we take the value from the MyHomePage object that was created by
44 // the App.build method, and use it to set our appbar title.
45 title: Text(widget.title),
46 ),
47 body: Center(
48 // Center is a layout widget. It takes a single child and positions it
49 // in the middle of the parent.
50 child: Column(
51 // Column is also a layout widget. It takes a list of children and
52 // arranges them vertically. By default, it sizes itself to fit its
53 // children horizontally, and tries to be as tall as its parent.
54 //
55 // Invoke "debug painting" (press "p" in the console, choose the
56 // "Toggle Debug Paint" action from the Flutter Inspector in Android
57 // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
58 // to see the wireframe for each widget.
59 //
60 // Column has various properties to control how it sizes itself and
61 // how it positions its children. Here we use mainAxisAlignment to
62 // center the children vertically; the main axis here is the vertical
63 // axis because Columns are vertical (the cross axis would be
64 // horizontal).
65 mainAxisAlignment: MainAxisAlignment.center,
66 children: <Widget>[
67 Text(
68 'You have pushed the button this many times:',
69 ),
70 Text(
71 '$_counter',
72 style: Theme.of(context).textTheme.headline4,
73 ),
74 ],
75 ),
76 ),
77 floatingActionButton: FloatingActionButton(
78 onPressed: _incrementCounter,
79 tooltip: 'Increment',
80 child: Icon(Icons.add),
81 ), // This trailing comma makes auto-formatting nicer for build methods.
82 );
83 }
84}

When you run the app there will be an 'Increment' button that will increase the counter and show you how many times you have pushed the button. The goal of this tutorial will be to save the counter's value so that it will persist when you reload or restart the app.

Adding SharedPreferences

First of all make sure that you have imported the shared_preferences package:

1import 'package:shared_preferences/shared_preferences.dart';

Using SharedPreferences

We're going to have to load the value of the counter when the app starts up(or MyHomePage appears). We can do this in the initState method of _MyHomePageState. We're going to wrap this logic in a microtask to make sure there are no layout rendering issues:

1@override
2void initState() {
3 super.initState();
4 Future.microtask(() async {
5 final prefs = await SharedPreferences.getInstance();
6 final value = prefs.getInt('counter_value') ?? 0;
7 setState(() {
8 _counter = value;
9 });
10 });
11}

The above method will get an instance of SharedPreferences and then retrieve the value for the 'counter_value' key. The result can be null so I've added ?? 0 to the end, this basically means 'give me 0 if the result is null'.

Next we're going to modify the existing _incrementCounter method inside _MyHomePageState. We need to bump the value of the existing _counter variable, save the new result with SharedPreferences and then update the UI with setState.

1Future<void> _incrementCounter() async {
2 final value = _counter + 1;
3 final prefs = await SharedPreferences.getInstance();
4 await prefs.setInt('counter_value', value);
5
6 setState(() => _counter = value);
7}

Now when you press the FloatingActionButton the count will still increase, but if you reload the app or restart it the count from the last session will be persisted.

Adding a reset button

Now all that's left to do is add a reset button so the saved value can be reset for the counter. Let's add it to the existing Column widget inside _MyHomePageState:

1<Widget>[
2 Text(
3 'You have pushed the button this many times:',
4 ),
5 Text(
6 '$_counter',
7 style: Theme.of(context).textTheme.headline4,
8 ),
9 // The reset button
10 ElevatedButton(
11 onPressed: () async {
12 final prefs = await SharedPreferences.getInstance();
13 await prefs.remove('counter_value');
14
15 final value = prefs.getInt('counter_value') ?? 0;
16 setState(() => _counter = value);
17 },
18 child: Text('Reset'),
19 )
20]

When we push the new button it will get an instance of SharedPreferences and then remove the value for the 'counter_value' key(using the remove method). After that we will reload the value again and update the UI with the setState method.

Here's what the UI looks like after all changes have been made:

Example App Screenshot

And here's the complete code for main.dart:

1import 'package:flutter/material.dart';
2import 'package:shared_preferences/shared_preferences.dart';
3
4void main() {
5 runApp(MyApp());
6}
7
8class MyApp extends StatelessWidget {
9 // This widget is the root of your application.
10 @override
11 Widget build(BuildContext context) {
12 return MaterialApp(
13 title: 'Flutter Demo',
14 theme: ThemeData(
15 // This is the theme of your application.
16 //
17 // Try running your application with "flutter run". You'll see the
18 // application has a blue toolbar. Then, without quitting the app, try
19 // changing the primarySwatch below to Colors.green and then invoke
20 // "hot reload" (press "r" in the console where you ran "flutter run",
21 // or simply save your changes to "hot reload" in a Flutter IDE).
22 // Notice that the counter didn't reset back to zero; the application
23 // is not restarted.
24 primarySwatch: Colors.blue,
25 ),
26 home: MyHomePage(title: 'Flutter Demo Home Page'),
27 );
28 }
29}
30
31class MyHomePage extends StatefulWidget {
32 MyHomePage({Key? key, required this.title}) : super(key: key);
33
34 // This widget is the home page of your application. It is stateful, meaning
35 // that it has a State object (defined below) that contains fields that affect
36 // how it looks.
37
38 // This class is the configuration for the state. It holds the values (in this
39 // case the title) provided by the parent (in this case the App widget) and
40 // used by the build method of the State. Fields in a Widget subclass are
41 // always marked "final".
42
43 final String title;
44
45 @override
46 _MyHomePageState createState() => _MyHomePageState();
47}
48
49class _MyHomePageState extends State<MyHomePage> {
50 int _counter = 0;
51
52 @override
53 void initState() {
54 super.initState();
55 Future.microtask(() async {
56 final prefs = await SharedPreferences.getInstance();
57 final value = prefs.getInt('counter_value') ?? 0;
58 setState(() {
59 _counter = value;
60 });
61 });
62 }
63
64 Future<void> _incrementCounter() async {
65 final value = _counter + 1;
66 final prefs = await SharedPreferences.getInstance();
67 await prefs.setInt('counter_value', value);
68
69 setState(() => _counter = value);
70 }
71
72 @override
73 Widget build(BuildContext context) {
74 // This method is rerun every time setState is called, for instance as done
75 // by the _incrementCounter method above.
76 //
77 // The Flutter framework has been optimized to make rerunning build methods
78 // fast, so that you can just rebuild anything that needs updating rather
79 // than having to individually change instances of widgets.
80 return Scaffold(
81 appBar: AppBar(
82 // Here we take the value from the MyHomePage object that was created by
83 // the App.build method, and use it to set our appbar title.
84 title: Text(widget.title),
85 ),
86 body: Center(
87 // Center is a layout widget. It takes a single child and positions it
88 // in the middle of the parent.
89 child: Column(
90 // Column is also a layout widget. It takes a list of children and
91 // arranges them vertically. By default, it sizes itself to fit its
92 // children horizontally, and tries to be as tall as its parent.
93 //
94 // Invoke "debug painting" (press "p" in the console, choose the
95 // "Toggle Debug Paint" action from the Flutter Inspector in Android
96 // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
97 // to see the wireframe for each widget.
98 //
99 // Column has various properties to control how it sizes itself and
100 // how it positions its children. Here we use mainAxisAlignment to
101 // center the children vertically; the main axis here is the vertical
102 // axis because Columns are vertical (the cross axis would be
103 // horizontal).
104 mainAxisAlignment: MainAxisAlignment.center,
105 children: <Widget>[
106 Text(
107 'You have pushed the button this many times:',
108 ),
109 Text(
110 '$_counter',
111 style: Theme.of(context).textTheme.headline4,
112 ),
113 ElevatedButton(
114 onPressed: () async {
115 final prefs = await SharedPreferences.getInstance();
116 await prefs.remove('counter_value');
117
118 final value = prefs.getInt('counter_value') ?? 0;
119 setState(() => _counter = value);
120 },
121 child: Text('Reset'),
122 )
123 ],
124 ),
125 ),
126 floatingActionButton: FloatingActionButton(
127 onPressed: _incrementCounter,
128 tooltip: 'Increment',
129 child: Icon(Icons.add),
130 ), // This trailing comma makes auto-formatting nicer for build methods.
131 );
132 }
133}

Some closing points

What I have showcased above is a very, very basic implementation of shared_preferences using the sample counter app. When you're creating an app that you will have to maintain(possibly for years to come?) you should use a more complex implementation that includes utility classes(or repositories) and dependency injection. I mostly use Riverpod and Provider for state management and dependency injection in my own projects. I also recommend writing unit tests, but that is a topic for another day.