Step by step to persist data in Flutter
Tags: flutter, dart, beginners, tutorials
Published: 2020-04-25 02:17:06

---
title: Step by step to persist data in Flutter
published: true
date: 2020-04-24 12:34:38 UTC
tags: flutter, dart, beginners, tutorials
canonical_url: https://pythongeeks.net/flutter-tutorials/step-by-step-to-store-data-locally-in-flutter/?utm_source=rss&utm_medium=rss&utm_campaign=step-by-step-to-store-data-locally-in-flutter
---

![step by step to persist data in Flutter](https://i1.wp.com/pythongeeks.net/wp-content/uploads/2020/04/Persist-Data-in-Flutter.png?resize=640%2C360&ssl=1)

## **Introduction**

Most mobile apps need to store persistence data locally. There are many reasons to do so, from restoring the app state at the last session, to improve app performance. Therefore, Flutter offers us several ways to accomplish this feature. In this post, I will introduce to you 3 different ways to store persistence data in Flutter:

- Store key-value to shared\_preference
- Saving data to a local database
- Writing and reading a file

## **Prerequisites**

Before starting, I assume you already know the very basic Flutter things as below:

- [Getting Start Guide](https://flutter.dev/docs/get-started/install).
- Have your own favorite IDE and already install Flutter plugin (I recommend VSCode or Android Studio).

What if you haven’t known these things yet. No problem! You only need 10 minutes to finish the Getting Start Guide.

## Setup Project

Let’s create a new Flutter project for this tutorial. I use VSCode to create my project called `flutter_data_persisitence`. Anytime you can get the source code of this tutorial [repository](https://github.com/thepythongeeks/persist-data-flutter).

## **Data Persistence with Shared Preference**

I will use [shared\_preference](https://pub.dev/packages/shared_preferences) plugin to store a small amount of data under key-value format.

The most important thing you should remember while using shared\_preferences is **SMALL DATA**. Do not use it for storing large data or complicated data, it will make your code super complicated because of reading and writing this data from shared\_preferences.

I can list down here some use cases that `shared_preferences` is useful:

- User changes view mode (night mode).
- Changing the app’s language.
- Select the app theme.

So when does the data in SharedPreference was clear? The system will erase your data in SharedPreference whenever the user uninstalls your app. Data still remains after the app closes and reopens.

### **Installing**

Firstly, we add shared\_preferences as dependencies in`pubspec.yaml` as below:

```yaml
dependencies:
  flutter: 
    sdk: flutter 
  shared_preferences: ^0.5.7
```

Run command to install dependencies. Or it will automatically install if you use VSCode.

```shell
flutter pub get
```

**[FYI]**: For anyone who doesn’t know character **^** yet. **^version** means “the range of all versions guaranteed to be backward compatible with the specified version”. For example, **^2.1.0** is equivalent to ‘ **>=2.1.0 <3.0.0** ’. The number version must follow the [semantic versioning](https://dart.dev/tools/pub/versioning#semantic-versions). Back to our project, we are about using `shared_preferences` which version is **^0.5.7** , it means **>=0.5.7** and **< 1.0.0**.

### **Build An Example**

Because of learning purposes, we will just make a simple example.

![](https://i1.wp.com/pythongeeks.net/wp-content/uploads/2020/04/shared_preference.png?fit=580%2C1024&ssl=1)

We have three buttons here. One for generating a random number, one for storing current number to the SharedPreference. And the last one is to read the saving number and show it to UI.

### Implementation

Now open `main.dart` and add below code, I will explain this code clearly in next section:

```dart
import 'dart:math';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  int _savedNumber;
  int _randomNumber;

  @override
  void initState() {
    super.initState();
    _savedNumber = 0;
    _randomNumber = 0;
  }

  void _generateRandomNumber() {
    setState(() {
      _randomNumber = Random().nextInt(pow(2, 31));
    });
  }

  void _saveNumber() async {
    final SharedPreferences prefs = await _prefs;
    prefs.setInt('savedNumber', _randomNumber);
    setState(() {
      _savedNumber = _randomNumber;
    });
  }

  void _loadNumber() async {
    final SharedPreferences prefs = await _prefs;
    final savedNumber = prefs.getInt('savedNumber') ?? 0;
    setState(() {
      _randomNumber = savedNumber;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have generated random number:',
              ),
              Text(
                '$_randomNumber',
                style: Theme.of(context).textTheme.display2,
              ),
              Text(
                'You saved this number in SharedPreference',
              ),
              Text(
                '$_savedNumber',
                style: Theme.of(context).textTheme.display1,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  OutlineButton(
                      onPressed: () => _loadNumber(), child: Text('Load')),
                  OutlineButton(
                      onPressed: () => _generateRandomNumber(),
                      child: Text('Random')),
                  OutlineButton(
                      onPressed: () => _saveNumber(), child: Text('Save')),
                ],
              ),
            ],
          ),
        ) // This trailing comma makes auto-formatting nicer for build methods.
        );
  }
}
```

### Explanation

Although the code is not too long, it’s still hard to focus on the main point. So in this section, I will explain the important block of code.

Because we are talking about SharedPreference, so I will focus on it mostly. After install `shared_preference` plugin, we need to `import` and initialize it first.

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

...

Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
```

For saving data to SharedPreference, we create a new function. Because `getInstance()` return a `Future` object, it means everything is doing asynchronously. Therefore, we have to use `await` syntax to wait until `getInstance()` function finishes before doing anything.

```dart
 void _saveNumber() async {
    final SharedPreferences prefs = await _prefs;
    prefs.setInt('savedNumber', _randomNumber);
    setState(() {
      _savedNumber = _randomNumber;
    });
  }
```

`shared_preference` offers us many handy built-in functions to `set` and `get` persistence data. In my example, I use `setInt('savedNumber', _randomNumber)` to save a key `savedNumber` that has an integer value to ShareReference.

Now we create a new function that uses `getInt('savedNumber')`to get what I have saved.

```dart
 void _loadNumber() async {
    final SharedPreferences prefs = await _prefs;
    final savedNumber = prefs.getInt('savedNumber') ?? 0;
    setState(() {
      _randomNumber = savedNumber;
    });
  }
```

## **Saving to a local database**

To develop an app that needs to persist a large amount of data, structure data, you should consider using a local database. Basically, we can query and write easier.

In Android, developer can use SQLite to serve as a local database. With iOS, it is Core Data. However, we’re still able to use the SQLite database in iOS to avoid the complicated of handling Core Data. In Flutter, we simply use a plugin named [sqflite](https://pub.dev/packages/sqflite) for both iOS and Android app.

### Installing

Add `sqflite` and `path` dependencies in `pubspec.yaml`

```yaml
dependencies:
  flutter:
    sdk: flutter
  sqflite: ^1.3.0
  path: ^1.6.4
```

I usually use `sqflite` with `path` plugin. While sqflite interacts with SQLite database, path helps us get exactly directory where the database is.

### **Build An Example**

Continue with the previous example app, we add two more buttons to insert and load data from database.

![persistence data in Flutter using sqflite](https://i1.wp.com/pythongeeks.net/wp-content/uploads/2020/04/database-1.png?fit=580%2C1024&ssl=1)

### Implementation

Now we implement a simple example to show how `sqflite` works. When working with SQLite database, we should follow these points:

- You should create a Database Service to interact with the database. Keep it separated with other files for clear and maintain it.
- It’s highly recommended to make Database Service as a singleton object. It means we will open only a connection to the database. Avoid too many connections can reduce your app performance.

Firstly, we create a model class to reflect the data structure of a table. Although, it’s not required to work with SQLite, we can use data easier within the model. It is also great to define some helper function to verify data or converting data to Map before insert to database.

Now we define a model class `RandomNumber`, named the file as `random_number.dart`

```dart
class RandomNumber {
  int value;
  DateTime createdTime;
  RandomNumber(this.value, this.createdTime);

  RandomNumber.fromMap(Map<String, dynamic> map)
      : assert(map["value"] != null),
        assert(map["createdTime"] != null),
        value = map["value"],
        createdTime = map["createdTime"] is String
            ? DateTime.parse(map["createdTime"])
            : map["createdTime"];

  Map<String, dynamic> toMap() {
    return {
      "value": this.value,
      "createdTime": this.createdTime.toString(),
    };
  }
}
```

And then, we will create a Service to do database action such as open connection, insert, update, delete, or close the connection.

So why we define a separate service to do these jobs. We define a database service because of two below points:

- Firstly, every business code that related together should be placed in one file. So it makes a clean, clear, and readable code. Furthermore, if we change plugin from `sqflite` to another one day, we just need to update this service.
- And then, we will make this service as a singleton class. It means we only open one connection to the database and we can use this service everywhere around your app.

Below is my example for a database service:

```dart
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'random_number.dart';

final tableRandomNumber = "randoms";

class DatabaseService {
  static final DatabaseService _instance = DatabaseService._internal();
  Future<Database> database;

  factory DatabaseService() {
    return _instance;
  }

  DatabaseService._internal() {
    initDatabase();
  }

  initDatabase() async {
    database = openDatabase(
      join(await getDatabasesPath(), 'beautiful_alarm.db'),
      // When the database is first created, create a table to store data.
      onCreate: (db, version) {
        db.execute(
          '''CREATE TABLE $tableRandomNumber(
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            value INTEGER,
            createdTime DATETIME)
          ''',
        );
      },
      // Set the version. This executes the onCreate function and provides a
      // path to perform database upgrades and downgrades.
      version: 1,
    );
  }

  Future<int> insertNumber(RandomNumber number) async {
    Database db = await database;
    int id = await db.insert(tableRandomNumber, number.toMap());
    return id;
  }

  Future<RandomNumber> getNumber(int id) async {
    Database db = await database;
    List<Map> datas = await db.query(tableRandomNumber,
        where: 'id = ?',
        whereArgs: [id]);
    if (datas.length > 0) {
      return RandomNumber.fromMap(datas.first);
    }
    return null;
  }
}
```

And the last one is adding an insert and query button to `main.dart`. When user clicks to button, it calls database service to get saved number.

```dart
import 'dart:math';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'database_service.dart';
import 'random_number.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  DatabaseService();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  DatabaseService dbService = DatabaseService();
  int _savedNumber;
  int _randomNumber;
  int _numberInDatabase;
  int _numberInDatabaseId;

  @override
  void initState() {
    super.initState();
    _savedNumber = 0;
    _randomNumber = 0;
    _numberInDatabase = 0;
    _numberInDatabaseId = 0;
  }

  void _generateRandomNumber() {
    setState(() {
      _randomNumber = Random().nextInt(pow(2, 31));
    });
  }

  void _saveNumber() async {
    final SharedPreferences prefs = await _prefs;
    prefs.setInt('savedNumber', _randomNumber);
    setState(() {
      _savedNumber = _randomNumber;
    });
  }

  void _loadNumber() async {
    final SharedPreferences prefs = await _prefs;
    final savedNumber = prefs.getInt('savedNumber') ?? 0;
    setState(() {
      _randomNumber = savedNumber;
    });
  }

  void _saveNumberToDb() async {
    final number = RandomNumber.fromMap({
      "value": _randomNumber,
      "createdTime": DateTime.now()
    });
    final int id = await dbService.insertNumber(number);
    setState(() {
      _numberInDatabase = _randomNumber;
      _numberInDatabaseId = id;
    });
  }

  void _loadNumberFromDb() async {
    final RandomNumber number = await dbService.getNumber(_numberInDatabaseId);
    setState(() {
      _randomNumber = number.value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have generated random number:',
              ),
              Text(
                '$_randomNumber',
                style: Theme.of(context).textTheme.display2,
              ),
              Text(
                'You saved this number in SharedPreference',
              ),
              Text(
                '$_savedNumber',
                style: Theme.of(context).textTheme.display1,
              ),
              Text(
                'You saved this number in SQLite',
              ),
              Text(
                '$_numberInDatabase',
                style: Theme.of(context).textTheme.display1,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  OutlineButton(
                      onPressed: () => _loadNumber(), child: Text('Load')),
                  OutlineButton(
                      onPressed: () => _generateRandomNumber(),
                      child: Text('Random')),
                  OutlineButton(
                      onPressed: () => _saveNumber(), child: Text('Save')),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  OutlineButton(
                      onPressed: () => _loadNumberFromDb(), child: Text('Load from SQLite')),
                  OutlineButton(
                      onPressed: () => _saveNumberToDb(), child: Text('Save to SQLite')),
                ],
              ),
            ],
          ),
        ) // This trailing comma makes auto-formatting nicer for build methods.
        );
  }
}
```

## Writing and reading a file

In some cases, database can’t fit well your data. You may need to write to a local file and read it later. I can list down here the cases:

- Save data across app launches.
- Save log file.
- Export database as a CSV file

To write and read a file, we use `path_provider` plugin to get the directory to save file and write file by using `dart:io` built-in library.

We should note that there are two types of local directory:

- **Temporary directory** : It usually use for cache. It can be clear anytime. On iOS, it called [NSCachesDirectory](https://developer.apple.com/documentation/foundation/nssearchpathdirectory/nscachesdirectory). And cache directory in Android.
- **Document directory** : A directory that app can store files that only it can read. We should use this type to persist data. It’s named [AppData](https://developer.android.com/guide/topics/data) on Android, and [NSDocumentDirectory](https://developer.apple.com/documentation/foundation/nssearchpathdirectory/nsdocumentdirectory) on iOS.

### Installing

We add `path_provider` as project dependencies:

```
dependencies: 
  path_provider: ^1.6.7
```

### **Build An Example**

Now continue to add the other two buttons to this example which allow us to write and read data. Below is an example image

![](https://i0.wp.com/pythongeeks.net/wp-content/uploads/2020/04/localfile.png?fit=580%2C1024&ssl=1)

### Implementation

We add below function to `main.dart` to get the document directory and the specific file to read and write.

```dart
  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
    return directory.path;
  }

  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/randoms.txt');
  }
```

In the code, we use `getApplicationDocumentsDirectory()` function to get the document directory which was discussed above. You can get temporary directory by using `getTemporaryDirectory()`.

We also hard-coded file path as `$path/randoms.txt`, you can change to whatever you want. Now create two buttons to read and write data to file.

You can get full source code from the [repository](https://github.com/thepythongeeks/persist-data-flutter).

## Conclusion

After a long post, we know three different methods to persist data in Flutter. Each method shows its pros and cons in a specific circumstance. Therefore, understand it well can help you choose your best option and make your app performance better.

The post [Step by step to store data locally in Flutter](https://pythongeeks.net/flutter-tutorials/step-by-step-to-store-data-locally-in-flutter/) appeared first on [Python Geeks](https://pythongeeks.net).