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
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Saving Your Data Online

Start by opening lib/data/CloudPersistence.dart. Then, add imports for dart:convert,dart:convert and for the firebase_storage package:

import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';  
import 'package:firebase_storage/firebase_storage.dart';

Next, update the save methods to the following:

@override  
Future<String> saveImage(String userId, String key, Uint8List image) async {
  // 1
  final StorageReference storageReference =
      FirebaseStorage().ref().child('$userId/$key');
  
  // 2
  final StorageUploadTask uploadTask = storageReference.putData(image);

  // 3
  await uploadTask.onComplete;
  return '$userId/$key';
}

@override
void saveObject(
    String userId, String key, Map<String, dynamic> object) async {
  // 4
  await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('objects')
      .document(key)
      .setData(object);
}

@override
void saveString(String userId, String key, String value) async {
  // 5
  await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('strings')
      .document(key)
      .setData({'string': value});
}

Here’s what you did:

  1. When saving an image, you get a reference to Firebase storage where you will save the image. The path is at userId/key.
  2. Then, you call putData on the storage reference.
  3. Next, you wait for the upload to finish. Finally return the path used for saving.
  4. When saving an object, you do a similar procedure, but instead of ‘images’, you use the collection ‘objects’. Then you set the data as object because it is already a map.
  5. Lastly, when saving a string, you do another similar procedure, but instead of ‘objects’, you use the collection ‘strings’. Then you set the data as a map containing the string.

After you’ve saved data to Firestore, you’ll be able to see the cloud persistence structure in the Firestore console.

Firestore persistence data structure

But at this point, you still need to read back your data in order to see the effect of saving it.

Reading Your Data Online

In order to read your login and cart data back, update CloudPersistence with the following methods:

// 1
@override
Future<Uint8List> getImage(String userId, String key) async {
  final maxImageBytes = 30 * 1024 * 1024;

  try {
    final StorageReference storageReference =
        FirebaseStorage().ref().child('$userId/$key');

    final bytes = await storageReference.getData(maxImageBytes);
    return bytes;
  } catch (e) {
    return null;
  }
}

// 2
@override
Future<Map<String, dynamic>> getObject(String userId, String key) async {
  final ref = await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('objects')
      .document(key)
      .get();
  return ref.data;
}

// 3
@override
Future<String> getString(String userId, String key) async {
  final ref = await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('strings')
      .document(key)
      .get();
  if (ref.data != null) return ref.data['string'] as String;
  return null;
}

To sum up, here’s what you did:

  1. First, when getting an image, try to get the data from Firebase storage at the same path where you saved it. Then, return it as bytes. Otherwise if there’s an exception, return null.
  2. When getting an object, read the data from users/userId/objects/key. Then return the data.
  3. When getting a string, read the data from users/userId/strings/key. Then if it exists, return ‘string’ inside the data. Otherwise return null.

Build and run your project to see that cloud persistence works like the other methods of persistence, but this time the data is saved online. You were able to easily swap from local to remote persistence thanks to your use of the Repository abstract class.

Removing Your Data Online

In order to complete the solution, you need to remove the items you stored online, e.g. when you remove them from the cart. Otherwise, you will see items in the cart with zero quantity.

In order to do this, add the following code, still in CloudPersistence/code>:

// 1
@override
Future<void> removeImage(String userId, String key) async {
  try {
    final StorageReference storageReference =
        FirebaseStorage().ref().child('$userId/$key');

    storageReference.delete();
  } catch (e) {
    return null;
  }
}

// 2
@override
Future<void> removeObject(String userId, String key) async {
  await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('objects')
      .document(key)
      .delete();
}

// 3
@override
Future<void> removeString(String userId, String key) async {
  await Firestore.instance
      .collection('users')
      .document(userId)
      .collection('strings')
      .document(key)
      .delete();
}

Here you are just deleting each image, object or string on the appropriate collection and document in Firestore, and in Firebase Storage for the image.

Build and run the app again, and you’ll be able to successfully add and remove items from the cart, even across logouts.

Finally, you finished; you persisted until the end. You wrote, you read, and you deleted. Congrats!

Where to Go From Here?

You can download the completed project by clicking on the Download Materials button at the top or bottom of the tutorial.

Flutter has done a cookbook on how to do some basics of data persistence. You can check it out here.

Look for a tutorial soon on data persistence with SQLite, another way to save data locally in your app. You can also read about using SQLite in Flutter in the official docs.

I hope you enjoyed this tutorial on Data Persistence on Flutter! If you have any questions or comments, please join the forum discussion below.