Data persistence in flutter

edolubis21

Edo Lubis

Posted on June 3, 2024

Data persistence in flutter

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 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);
}
Enter fullscreen mode Exit fullscreen mode

Read data

Future<bool?> readTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final bool? isLightMode = prefs.getBool('isLightMode');
    return isLightMode;
}
Enter fullscreen mode Exit fullscreen mode

Delete data

Future<void> deleteTheme() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('isLightMode');
}
Enter fullscreen mode Exit fullscreen mode

SharedPreferences is limited to only supporting certain data types like int, double, bool, String, and List. 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;
  }
}
Enter fullscreen mode Exit fullscreen mode
  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');
  }
Enter fullscreen mode Exit fullscreen mode

2. sqflite
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,
      };
}
Enter fullscreen mode Exit fullscreen mode
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]));
  }
}
Enter fullscreen mode Exit fullscreen mode

3. hive
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');
}
Enter fullscreen mode Exit fullscreen mode

If your project uses the flutter_bloc library for state management, you can easily cache data locally using Hive. The creator of flutter_bloc has developed the 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());
}
Enter fullscreen mode Exit fullscreen mode
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};
}
Enter fullscreen mode Exit fullscreen mode

4. isar
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
Enter fullscreen mode Exit fullscreen mode
part 'isar_page.g.dart';

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

  late String title;

  late DateTime date;
}
Enter fullscreen mode Exit fullscreen mode

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);
  });
}
Enter fullscreen mode Exit fullscreen mode

5. flutter_secure_storage
If you want to store sensitive data, then 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;
  }
}
Enter fullscreen mode Exit fullscreen mode
  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');
  }
Enter fullscreen mode Exit fullscreen mode

6. get_storage
If you're using GetX as state management, then you might be familiar with 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;
  }
}
Enter fullscreen mode Exit fullscreen mode
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');
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
edolubis21
Edo Lubis

Posted on June 3, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related