Data persistence in flutter
Tags: flutter, mobile, android, ios
Published: 2024-06-03 11:19:52

When you develop an application, usually, the data used to display information originates from the internet. However, there are scenarios where the data from the internet needs to be stored somewhere on your local device. This can be for functionality purposes or to enhance performance and user experience while using the application.

Take the Facebook application as an example: when you open the main page or page detail, then close the application and reopen it, you will find yourself on the last visited page, and the page immediately displays data as if there were no loading process. This can happen because the data is stored locally, and when the application is opened, it uses the stored data to display the main or page detail while loading data from the internet to get the latest information.

In Flutter itself, there are many libraries designed to store data on the local device. In this article, I will share How to persist data using the best libraries (in my opinion) for storing data locally in Flutter.

**1. shared_preferences**
[shared_preferences](https://pub.dev/packages/shared_preferences) is a plugin for reading and writing simple key-value pairs. It uses NSUserDefaults on iOS and SharedPreferences on Android. shared_preferences is a best choice for storing small and non-sensitive data, such as theme preferences, language settings, settings.

implementation example:
write data
```
Future<void> saveTheme(bool isLightMode) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setBool('isLightMode', isLightMode);
}
```
Read data
```
Future<bool?> readTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final bool? isLightMode = prefs.getBool('isLightMode');
    return isLightMode;
}
```
Delete data
```
Future<void> deleteTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('isLightMode');
}
```
SharedPreferences is limited to only supporting certain data types like int, double, bool, String, and List<String>. However, we can store objects in SharedPreferences by converting them into a string. One common way to do this is by using JSON. We can convert an object to a JSON string when saving it and then convert it back to an object when retrieving it.

implementation example:
```
class Setting {
  bool? isLightMode;
  String? lang;
  int? counter;

  Setting({this.isLightMode, this.lang, this.counter});

  Setting.fromJson(Map<String, dynamic> json) {
    isLightMode = json['isLightMode'];
    lang = json['lang'];
    counter = json['counter'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['isLightMode'] = isLightMode;
    data['lang'] = lang;
    data['counter'] = counter;
    return data;
  }
}
```
```
  Future<void> saveSetting(Setting setting) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('setting', jsonEncode(setting.toJson()));
  }

  Future<Setting?> readSetting() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? json = prefs.getString('setting');
    if (json == null) return null;
    return Setting.fromJson(jsonDecode(json));
  }

  Future<void> deleteSetting() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('setting');
  }
```

**2. sqflite**
[sqflite](https://pub.dev/packages/sqflite) is a Flutter plugin that provides access to SQLite, a lightweight, serverless relational database commonly used in mobile applications for local data storage. Sqflite is a popular choice for applications requiring local data storage with more complex and relational structures.
implementation example:
```
class Item {
  final int id;
  final String title;
  final String description;

  const Item({
    required this.title,
    required this.description,
    required this.id,
  });

  factory Item.fromJson(Map<String, dynamic> json) => Item(
        id: json['id'],
        title: json['title'],
        description: json['description'],
      );

  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'description': description,
      };
}
```
```
class DatabaseHelper {
  static const int _version = 1;
  static const String _dbName = "items.db";

  static Future<Database> _getDB() async {
    return openDatabase(
      join(await getDatabasesPath(), _dbName),
      onCreate: (db, version) async => await db.execute(
          "CREATE TABLE Item(id INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL);"),
      version: _version,
    );
  }

  static Future<int> addItem(Item item) async {
    final db = await _getDB();
    return await db.insert("Item", item.toJson(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  static Future<int> updateItem(Item item) async {
    final db = await _getDB();
    return await db.update(
      "Item",
      item.toJson(),
      where: 'id = ?',
      whereArgs: [item.id],
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  static Future<int> deleteItem(Item item) async {
    final db = await _getDB();
    return await db.delete(
      "Item",
      where: 'id = ?',
      whereArgs: [item.id],
    );
  }

  static Future<List<Item>?> getAllItems() async {
    final db = await _getDB();
    final List<Map<String, dynamic>> maps = await db.query("Item");
    if (maps.isEmpty) return null;
    return List.generate(maps.length, (index) => Item.fromJson(maps[index]));
  }
}
```

**3. hive**
[Hive](https://pub.dev/packages/hive) is a key-value database written in Dart. Hive excels in write and delete operations compared to SQLite and SharedPreferences, and it is on par with SharedPreferences in read performance, while SQLite is significantly slower. Hive has built-in encryption using AES-256, making it suitable for storing sensitive data.
implementation example:
```
Future<void> saveUser(Map<String, dynamic> user) async {
  var box = await Hive.openBox('myBox');
  await box.put('user', user);
}

Future<Map<String, dynamic>?> readUser() async {
  var box = await Hive.openBox('myBox');
  final Map<String, dynamic>? json = box.get('user');
  return json;
}

Future<void> deleteUser() async {
  var box = await Hive.openBox('myBox');
  await box.delete('user');
}
```
If your project uses the [flutter_bloc](https://pub.dev/packages/flutter_bloc) library for state management, you can easily cache data locally using Hive. The creator of flutter_bloc has developed the [hydrated_bloc](https://pub.dev/packages/hydrated_bloc) library, which includes HydratedStorage that comes with Hive implemented.
```
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorage.webStorageDirectory
        : await getTemporaryDirectory(),
  );
  runApp(const MyApp());
}
```
```
class UserCubit extends HydratedCubit<User?> {
  UserCubit() : super(null);

  void login(User user) => emit(user);

  @override
  User? fromJson(Map<String, dynamic> json) =>
      json['user'] != null ? User.fromJson(json['user']) : null;

  @override
  Map<String, dynamic> toJson(User? state) => {'user': state};
}
```

**4. isar**
[Isar](https://pub.dev/packages/isar) was created by the creator of Hive to overcome some limitations present in Hive and to offer a more advanced and efficient solution for local data storage in Flutter and Dart applications.

Isar is a fast, cross-platform NoSQL database that is well-integrated with Flutter and Dart. It is designed to provide high performance and ease of use.
implementation example:
```
dependencies:
  flutter:
    sdk: flutter
  isar: ^2.5.0
  isar_flutter_libs: ^2.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.2.0
  isar_generator: ^2.5.0
```
```
part 'isar_page.g.dart';

@Collection()
class Post {
  int id = Isar.autoIncrement;

  late String title;

  late DateTime date;
}
```
run  flutter pub run build_runner
```
late Isar isar;

@override
void initState() {
  super.initState();
  openInstance();
}

void openInstance() async {
  final dir = await getApplicationSupportDirectory();
  isar = await Isar.open(
    schemas: [PostSchema],
    directory: dir.path,
    inspector: true,
  );
}

void readPost() async {
  final posts = await isar.posts
      .filter()
      .titleContains('awesome', caseSensitive: false)
      .sortByDateDesc()
      .limit(10)
      .findAll();
  print(posts);
}

void createPost() async {
  final newPost = Post()
    ..title = 'awesome new database'
    ..date = DateTime.now();

  await isar.writeTxn((isar) async {
    newPost.id = await isar.posts.put(newPost);
  });
}
```

**5. flutter_secure_storage**
If you want to store sensitive data, then [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) is one of the best solutions. flutter_secure_storage is a Flutter package that provides a secure way to store sensitive data on the device, using Keychain on iOS and a KeyStore based solution on Android.
implementation example:
```
class Auth {
  String? token;
  String? id;

  Auth({this.token, this.id});

  Auth.fromJson(Map<String, dynamic> json) {
    token = json['token'];
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['token'] = token;
    data['id'] = id;
    return data;
  }
}
```
```
  final storage = const FlutterSecureStorage();

  Future<void> saveAuth(Auth auth) async {
    await storage.write(key: 'auth', value: jsonEncode(auth.toJson()));
  }

  Future<Auth?> readAuth() async {
    final String? json = await storage.read(key: 'auth');
    if (json == null) return null;
    return Auth.fromJson(jsonDecode(json));
  }

  Future<void> deleteAuth() async {
    await storage.delete(key: 'auth');
  }
```

**6. get_storage**
If you're using GetX as state management, then you might be familiar with [get_storage](https://pub.dev/packages/get_storage). get_storage is a local data storage package developed by the GetX team for Flutter. It's a lightweight and easy-to-use storage solution, allowing you to persistently store data in key-value pairs on the user's device.
implementation example:
```
class User {
  String? name;
  String? id;

  User({this.name, this.id});

  User.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['name'] = name;
    data['id'] = id;
    return data;
  }
}
```
```
final box = GetStorage();

Future<void> saveUser(User user) async {
  await box.write('user', jsonEncode(user.toJson()));
}

User? readUser() {
  final String? json = box.read('user');
  if (json == null) return null;
  return User.fromJson(jsonDecode(json));
}

Future<void> deleteUser() async {
  await box.remove('user');
}
```
get_storage is well-suited for applications with simple and fast data storage needs. However, for more complex requirements such as indexing or ensuring disk write confirmation, it's recommended to consider using sqflite, Hive, isar, etc.

**Conclusion**
There are the best libraries for persist data locally, The usage of the library typically depends on the type of data being stored, whether it is sensitive data, data with relationships, or just simple API responses.