How to Store and Access Data Locally in a Flutter Mobile App? — PART I

Talha SEZER
Codimis
Published in
6 min readAug 2, 2022

Data is indispensable for our applications. Applications that do not have to store data are rarer. In many cases when we make an application we need to save our data for reuse. We don’t have to just use cloud systems or API to access data. We can also store our data locally. This article will take you to “How to Store Data Locally in the Flutter App?” which will answer the question. I will introduce you to 5 methods where we can store our data locally. I will mention 3 of them in this article. You can read the rest in the next part.

SHARED PREFERENCES

Shared Preferences is used for storing data key-value pairs in Android and iOS. We do not recommend SQLite for saving small values because then you will need to write lengthy codes and supporting classes. This data exists even when the app is shut down and starts up again; we can still retrieve the value as it was. To use SharedPreferences in Flutter, a plugin called Shared Preferences enables us to store data. The plugin wraps NSUserDefaults on iOS and Shared Preferences on Android. Data may be persisted to disk asynchronously. No guarantee that writes will be persisted to the disk after returning, so this plugin must not be used for storing critical data.

If you want to use Shared Preferences, you have to add dependencies to the pubspec.yaml.

Saving value

We have to call the setter methods provided by Shared Preferences if we want to save values. Setter methods are available for various primitive types, such as setInt, setBool, and setString.

Reading value

For each setter, there is a corresponding getter.

READ AND WRITE FILES

Sometimes the easiest way to store data is in a file. You should have already added the path_provider dependency to your pubspec.yaml file when you did the last section.

A directory where files can be stored that is accessible only by the app. The system clears the directory only when the app is deleted. On iOS, this corresponds to the NSDocumentDirectory. On Android, this is the AppData directory. To get the path correctly, we can use a package named path_provider.

Writing to a text file

_write(String text) async {
final Directory directory = await getApplicationDocumentsDirectory();
final File file = File('${directory.path}/my_file.txt');
await file.writeAsString(text);
}

Reading from a text file

Future<String> _read() async {
String text;
try {
final Directory directory = await getApplicationDocumentsDirectory();
final File file = File('${directory.path}/my_file.txt');
text = await file.readAsString();
} catch (e) {
print("Couldn't read file");
}
return text;
}

SQLITE

For large amounts of data, SharedPreferences is not a good option. SQLite is one of the most common methods of storing data locally. In Flutter we can interact with an SQLite database through a plugin called sqflite.

First, we have to add the dependencies to the pubspec.yaml. We will use the path provider plugin to give us the data directory where we can store the database on Android and iOS. As we mentioned before in Android this maps to the AppData directory, and in iOS to NSDocumentsDirectory.

sqflite: ^1.0.0
path_provider: ^0.4.1

databaseHelper file

Paste the following code into the database_helpers.dart file. We created the model class in the database_helpers.dart because flutter allows us to create multiple classes in one .dart file. This is a modification and expansion of the documentation.

import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

// database table and column names
final String tableWords = 'words';
final String columnId = '_id';
final String columnWord = 'word';
final String columnFrequency = 'frequency';

// data model class
class Word {

int id;
String word;
int frequency;

Word();

// convenience constructor to create a Word object
Word.fromMap(Map<String, dynamic> map) {
id = map[columnId];
word = map[columnWord];
frequency = map[columnFrequency];
}

// convenience method to create a Map from this Word object
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
columnWord: word,
columnFrequency: frequency
};
if (id != null) {
map[columnId] = id;
}
return map;
}
}

// singleton class to manage the database
class DatabaseHelper {

// This is the actual database filename that is saved in the docs directory.
static final _databaseName = "MyDatabase.db";
// Increment this version when you need to change the schema.
static final _databaseVersion = 1;

// Make this a singleton class.
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

// Only allow a single open connection to the database.
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}

// open the database
_initDatabase() async {
// The path_provider plugin gets the right directory for Android or iOS.
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
// Open the database. Can also add an onUpdate callback parameter.
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}

// SQL string to create the database
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $tableWords (
$columnId INTEGER PRIMARY KEY,
$columnWord TEXT NOT NULL,
$columnFrequency INTEGER NOT NULL
)
''');
}

In this code, we added the create and read methods which insert() and queryWord(). As you can see we only convert the word object to a map and we add this to the table with the table as the first parameter and the data as the second parameter with the insert() method.

// Database helper methods:

Future<int> insert(Word word) async {
Database db = await database;
int id = await db.insert(tableWords, word.toMap());
return id;
}

Future<Word> queryWord(int id) async {
Database db = await database;
List<Map> maps = await db.query(tableWords,
columns: [columnId, columnWord, columnFrequency],
where: '$columnId = ?',
whereArgs: [id]);
if (maps.length > 0) {
return Word.fromMap(maps.first);
}
return null;
}

// TODO: queryAllWords()
// TODO: delete(int id)
// TODO: update(Word word)
}

If the operations in the database are finished, you can close the database with the following line of code.

await database.close();

Conclusion

In this article, I have explained the basics of local storage in Flutter. This was a small introduction to local storage. I hope this blog will provide you with sufficient information in local storage.

References

https://l24.im/lBG

https://l24.im/WJo4b

--

--