Data Persistence on Flutter

See how to persist data to storage in a Flutter app, including to files and to a remote datastore, and use a Repository interface for the persistence. By JB Lorenzo.

Leave a rating/review
Download materials
Save for later
Share

It’s really hard to use an app if you have to login every time you use it. Also, it is difficult if you have to reenter your data every time you use it. Thus you have to really be determined, or rather persistent, in using this particular app that always forgets you and your data. :]

In order to avoid frustrating users, your app needs to save data that survives an app restart. Saving app data to some type of storage that survives app restarts is called data persistence.

In this Flutter tutorial, you’ll:

  • Build an Alchemy-themed shopping app that persists data on different levels.
  • Learn to use Flutter’s File package.
  • Perform persistence on text, images, and classes.
  • Learn about the theory behind which persistence method to use.
  • Implement persistence on disk and online.
Note: This tutorial assumes that you’re already familiar with the basics of Flutter development. If you’re new to Flutter, read through the Getting Started With Flutter tutorial. You should also have knowledge of using Android Studio with Flutter, which you can learn about in this screencast.

Getting Started

Download the starter project by clicking on the Download Materials button at the top or bottom of the tutorial. Then, open the starter project in Android Studio 3.5 or later. You can also use VS Code, but you’ll have to adapt instructions below as needed.

You should use a recent version of Flutter, 1.7 or above. Make sure to get Flutter dependencies for the project if prompted to do so by Android Studio with a ‘Packages get’ has not been run message.

Looking at the starter code, you’ll find the starter project provides the interface and some logic for the shopping app already.

Try building and running the starter project. If you encounter issues running the project on iOS, try running pod install && pod update in Terminal at the project root. You should see the shopping cart application with a screen asking for a username, like below.

Login page with username and photo

Choose a username and photo (you may need to download a photo in your emulator or simulator browser first). Then after you log in, you should see the alchemical items available to be purchased.

Shopping Items List

Next, try using the app, e.g. adding some items to your cart. Afterwards, restart the app and you should see that you are being asked again for a username and all your shopping cart items are gone.

The app has lost your user’s data, and you will now learn how to fix this by implementing persistence.

What Kind of Persistence Can You Use

Before you can save data onto a persistence layer, you need to encode it. Typically, representations such as a string or an array of bytes are needed because persistence layers do not usually know how to handle your classes or objects.

Serialized Data and Encryption

Serialization is a way to convert a data structure, e.g. user data or an item in your shopping cart, into a string or a byte array. You will need to know how this concept works before heading into the topic of persistence. You can read about Google’s official documentation on serialization here.

Additionally, sometimes you want to protect the data you have serialized from being human readable. You can do this with encryption. There are no official docs about encryption for flutter but there are several plugins that exist. Check out these packages: encrypt, and flutter_string_encryption.

Saving Data to Memory

Once you have serialized your data, you need to know that saving it as a variable in memory does not achieve true persistence. While the app is running, the data is kept in memory, but once you restart the app, it’s gone. If you do not have another source of data, the data is lost. In contrast, saving it to disk lets you load the data again after a restart.

Flutter Persistence Options

The persistence options you have on Flutter include:

  • Key-Value Store
  • File storage
  • Local database using SQLite
  • Remote datastore

You’ll investigate three of these options in this tutorial. Saving data locally to an app database using SQLite will be left for another tutorial.

Persisting Data in a Key-Value Store

There’s multiple ways to store data in disk. For instance, one of them is using a key-value store. Accordingly, iOS and Android has native solutions for doing key-value storage in disk. Specifically, iOS has UserDefaults and Android has SharedPreferences. Instead of manually using each of them, you can use an existing Flutter package called shared_preferences that uses the appropriate one depending on the platform on which you are running.

Saving Plain Text with Key-Value Store

With this in mind, you can try storing plain text into disk using a key-value storage. Open the file lib/data/LocalKeyValuePersistence.dart. Add the _generateKey method below then go to the method saveString to add these lines:

// 1 
String _generateKey(String userId, String key) {
  return '$userId/$key';
}

@override
void saveString(String userId, String key, String value) async {
  // 2
  final prefs = await SharedPreferences.getInstance();
  // 3
  await prefs.setString(_generateKey(userId, key), value);
}

Click on SharedPreferences and hit option+return on Mac or Alt+Enter on PC to add the needed import.

In the above, you (1) add a method to generate a key from a user’s id and a key. Then (2) when saving, you get the instance for SharedPreferences. Lastly (3), you store a key-value pair onto disk using the shared_preferences Flutter plugin. The _generatedKey> method is called to make a new key for storing the string.

Note: Be sure to never persist user-sensitive information such passwords to disk. You’ll want to learn about the proper way to perform user authentication using some kind of token in a Flutter app. That’s why the sample project does not use passwords!

Reading Plain Text with Key-Value Store

Although you are now able to write a string using the key-value storage, you also need the ability to read it back. With this in mind, update getString to be the following:

@override
Future<String> getString(String userId, String key) async {
  // 1
  final prefs = await SharedPreferences.getInstance();
  // 2
  return prefs.getString(_generateKey(userId, key));
}

You are (1) again getting an instance of the SharedPreferences class from the Flutter plugin. Then (2) you are calling the getString method to return the previously stored value by providing key.