13.1 Data Persistence
# Shared Preferences
SharedPreferences
in Flutter are a lot like LocalStorage
in the browser. You create a String key for each value you want to save and then save a value associated with that key.
The primary differences are:
- that you don't have to worry about other apps or webpages interfering with your key names because all the values are connected to your app and the code that you are writing.
- When you save or retrieve the value, you need to know what the datatype is for the information that you are saving.
- The read write operations for
SharedPreferences
are asynchronous so we need to useFuture
and possiblyasync
andawait
.
To use SharedPreferences
we need to import the dart package shared_preferences from pub.dev (opens new window).
Add shared_preferences: ^2.0.13
to your pubspec.yaml
dependencies list and run flutter pub get
(if VSCode does not run it for you).
Then you can import it at the top of any file that needs it.
import 'package:shared_preferences/shared_preferences.dart';
//inside an async function we will access the SharedPreferences storage for our app
final prefs = await SharedPreferences.getInstance();
2
3
4
Here is the shared_preferences reference (opens new window).
Once you have a reference to the SharedPreferences
storage instance for your app then you can start saving data. The data that you save must be one of the following types: boolean, string, integer, double, or List of Strings.
//To SAVE values use these methods
// Save an integer value to 'counter' key.
await prefs.setInt('counter', 10);
// Save an boolean value to 'repeat' key.
await prefs.setBool('repeat', true);
// Save an double value to 'decimal' key.
await prefs.setDouble('decimal', 1.5);
// Save an String value to 'action' key.
await prefs.setString('action', 'Start');
// Save an list of strings to 'items' key.
await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);
2
3
4
5
6
7
8
9
10
11
To read the values from your SharedPreferences
instance
// Try reading data from the 'counter' key. If it doesn't exist, returns null.
final int? counter = prefs.getInt('counter');
// Try reading data from the 'repeat' key. If it doesn't exist, returns null.
final bool? repeat = prefs.getBool('repeat');
// Try reading data from the 'decimal' key. If it doesn't exist, returns null.
final double? decimal = prefs.getDouble('decimal');
// Try reading data from the 'action' key. If it doesn't exist, returns null.
final String? action = prefs.getString('action');
// Try reading data from the 'items' key. If it doesn't exist, returns null.
final List<String>? items = prefs.getStringList('items');
2
3
4
5
6
7
8
9
10
To remove an entry from your SharedPreferences
instance, use the remove
method.
// Remove data for the 'counter' key.
final success = await prefs.remove('counter');
2
It is worth noting that the write and delete methods are asynchronous and require the await
keyword, but the read methods do not.
But, what about my big chunk of JSON, you ask? What if I have an array of numbers plus a whole host of other properties that I need to save?
The answer comes from the fact that JSON is already a String. If the data came from an API as a JSON String then you can simply use the await prefs.setString()
method.
If you have data that you have gathered from other locations, like a form, and you want to save that information in SharedPreferences
, then you can put all your information into a Map
and convert that Map
into a JSON String using the dart:convert
package.
import 'package:shared_preferences/shared_preferences.dart';
import 'data:convert';
String convertToJsonAndSave (String key, String name, int id, String email) async{
//pass in the values you want to save as a JSON string
//plus a key to use for your saved data in SharedPreferences
//access the Shared Preferences instance for your app
final prefs = await SharedPreferences.getInstance();
//build the map to hold the parameters passed to the function
Map<String, dynamic> data = {
'id': id,
'name': name,
'email': email,
};
//convert the Map to a JSON string
String jsonStr = json.encode(data);
//write the String to SharedPreferences
await prefs.setString(key, jsonStr);
//return the saved string in case it is needed elsewhere
return jsonStr;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Shared Preferences Solution
Here is the repo showing examples of connecting logins with SharedPreferences (opens new window). The example repo shows logging in, getting a token, saving the token in SharedPreferences, and navigating between screens when logged in or logged out. The login and navigating steps are split into separate buttons just to show the effects on the screens. In a real app you would be combining this functionality into a single button click.
# Random Numbers in Dart
In dart, when you want to create random values then you need to import the dart:math
package. After that you will use the Random()
class constructor to create a random value generator. You can optionally pass a unique seed value to the Random
constructor. With the generator created you can call nextInt()
or nextDouble()
or nextBool()
to get a random integer, double, or boolean value. For nextInt
and nextDouble
you can pass in a max value. The random number will be from zero, up to but not including your max value.
If you want to improve the randomness you can use the named constructor Random.secure()
instead.
import 'dart:math';
//pick a random name from a list.
List<String> names = ['Tony', 'Robert', 'SuCheng', 'Steve', 'David'];
int seed = 3661;
int index = Random(seed).nextInt(names.length);
String randomName = name[index];
2
3
4
5
6
7
8
# JS Array map and filter method Equivalencies
In Javascript, we have a number of Array methods that we use to manipulate arrays of objects that we get from API calls that return JSON. Dart has many methods on it's List
object that do the same things. Dart Reference for List objects (opens new window).
In Javascript, the two most common methods are map
and filter
. map
returns a new Array that has the same length as the original. It allows us to modify the objects that are inside, usually reducing the size of the original objects. The filter
method allows us to remove one or more unwanted objects from the original array and create a new modified Array. Both methods achieve this by calling a function on each of the elements in the Array and returning a new element (for map) or true | false (for filter) to decide whether to keep the old element.
//map example creating a new Array with new objects
//that are a copy of the old objects with a new property
let newArr = oldArr.map((element) => {
let newElement = { ...element };
newElement.newProp = 'hello';
return newElement;
});
//filter example creating a new Array that copies the old Array except
//that the element with the id 5 has been removed.
let idToMatch = 5;
let newArr = oldArr.filter((element) => element.id != idToMatch);
2
3
4
5
6
7
8
9
10
11
And here are the dart equivalents to the JS methods above, using map
and where
.
List newArr = oldArr.map((element) {
Map<String, dynamic> newElement = {...element}; //destructuring works here too
newElement['newProp'] = 'hello';
return newElement;
}).toList();
int idToMatch = 5;
List newArr = oldArr.where((element) => element['id'] != idToMatch).toList();
2
3
4
5
6
7
8
NB: The dart methods must have the
.toList
method at the end to create the new list.
Both dart List
objects and Javascript Array
objects have a forEach
method too.
# Path Provider
Getting the location of files that are saved on the device requires knowing about two folders - the temp folder and the app document directory. The pub.dev package path_provider
can get us these two locations. We will also need the dart:io
package to be imported.
import 'dart:io';
import 'package:path_provider/path_provider.dart';
void _getFolders() {
Directory tempDir = await getTemporaryDirectory();
Directory appDocDir = await getApplicationDocumentDirectory();
String tempPath = tempDir.path;
String appDocPath = appDocDir.path;
}
2
3
4
5
6
7
8
9
A useful application of the path
and path_provider
packages is the ability to take pictures and save them in an app folder. The Flutter Cookbook section has a recipe on how to do this. Cookbook Recipe for Camera (opens new window).
# Advice for Final Project
Final Project and Git
- One partner will create the private repo and then invite their partner as a collaborator.
- Remember to use GitHub as the way to share code with your partner.
- Remember to set up the
Settings > Branches > rules
to require pull requests when merging into main. - Since only one person will own the repo. The owner will be responsible for doing the merge of every pull request.
- The main branch is the starting point. Each time you want to add to the project, create your own new branch with
git checkout -b myNewBranch
. - When your code for a feature is ready, do a pull request and merge it back into the main branch on GitHub.
# What to do this week
TODO
Things to do before next week
- Finish your Hybrid step six assignment
- Work with your partner to start planning the screens for your Flutter Final Project