State persistence is a crucial aspect of app development. It ensures that user preferences, progress, or any other important data remain intact even after the app is closed and reopened. Flutter, being a versatile framework, offers numerous ways to persist data, and one of the fastest and most efficient solutions is the get_storage package.

In this guide, we will explore how to use get_storage for state persistence. We'll dive deep into its features, architecture, implementation, and practical use cases with detailed and updated examples.

Why Persist State in Flutter Applications?

Persisting state is essential for providing a seamless user experience. Here are some common scenarios where persistence is critical:

  1. User Preferences: Saving theme modes, language settings, or notification preferences ensures the app behaves consistently for users.
  2. Progress Tracking: For instance, coins earned in a game or reading progress in an eBook app. Users expect their progress to remain saved between sessions.
  3. Authentication Data: Persisting login tokens or user information reduces friction by keeping users logged in.

Without state persistence, users may experience frustration, which could impact engagement and retention.

Why Choose get_storage for State Persistence?

The get_storage package is popular among Flutter developers for several reasons:

  1. Speed:
  • get_storage saves data in memory first and asynchronously writes it to disk in the background.
  • This mechanism ensures quick read/write operations and better app performance.

2. Simplicity:

  • It has a straightforward API with just two primary methods: read and write.
  • Supports primitive types (String, int, bool) and collections like lists and maps of primitives.

3. JSON Support:

  • Storing complex objects is easy after converting them to and from JSON format.
  • Learning JSON manipulation is a skill every Flutter developer should master.

4. Integration with GetX:

  • If you are using the GetX ecosystem, get_storage seamlessly complements it.

Setting Up get_storage

1. Installation

Add the package to your pubspec.yaml file:

dependencies:
  get_storage: ^2.1.1 # Replace with the latest version

Or run the following command:

flutter pub add get_storage

2. Initialization

Initialize get_storage in the main method:

import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';

void main() async {
  await GetStorage.init(); // Initialize GetStorage
  runApp(MyApp());
}

Implementing State Persistence

1. Saving and Loading Primitive Types

Service Layer

We'll create a StorageService to encapsulate all storage operations:

import 'package:get_storage/get_storage.dart';

abstract class StorageServiceInterface {
  String? loadString(String key);
  int loadInt(String key);
  bool loadBool(String key);
  void save(String key, dynamic value);
  void deleteAll();
}

class GetStorageService implements StorageServiceInterface {
  final box = GetStorage();

  @override
  String? loadString(String key) => box.read(key);

  @override
  int loadInt(String key) => box.read<int>(key) ?? 0;

  @override
  bool loadBool(String key) => box.read<bool>(key) ?? false;

  @override
  void save(String key, dynamic value) => box.write(key, value);

  @override
  void deleteAll() => box.erase();
}

Controller Layer

Let's create a controller to manage the state:

import 'package:get/get.dart';

class HomeController extends GetxController {
  final StorageServiceInterface storage;

  String name = "Default";
  int number = 0;
  bool isEnabled = false;

  HomeController({required this.storage});

  @override
  void onInit() {
    name = storage.loadString('name') ?? name;
    number = storage.loadInt('number');
    isEnabled = storage.loadBool('enabled');
    super.onInit();
  }

  void updateState() {
    name = "Updated Name";
    number = 42;
    isEnabled = true;

    storage.save('name', name);
    storage.save('number', number);
    storage.save('enabled', isEnabled);
  }

  void clearState() => storage.deleteAll();
}

View Layer

Here's the UI for interacting with the state:

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class HomeView extends GetView<HomeController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("State Persistence")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("Name: ${controller.name}"),
            Text("Number: ${controller.number}"),
            Text("Enabled: ${controller.isEnabled}"),
            ElevatedButton(
              onPressed: controller.updateState,
              child: Text("Update State"),
            ),
            ElevatedButton(
              onPressed: controller.clearState,
              child: Text("Clear State"),
            ),
          ],
        ),
      ),
    );
  }
}

2. Storing Lists of Primitives

Expand the service to handle lists:

class GetStorageService implements StorageServiceInterface {
  // Other methods...

  List<String> loadStringList(String key) {
    return box.read<List<dynamic>>(key)?.cast<String>() ?? [];
  }

  void saveStringList(String key, List<String> value) {
    box.write(key, value);
  }
}

3. Managing Composite Objects

Use JSON conversion for storing complex objects:

Model Class

import 'dart:convert';

class User {
  final String name;
  final int age;

  User({required this.name, required this.age});

  factory User.fromJson(Map<String, dynamic> json) =>
      User(name: json['name'], age: json['age']);

  Map<String, dynamic> toJson() => {'name': name, 'age': age};
}

String userToJson(User user) => json.encode(user.toJson());
User userFromJson(String jsonString) => User.fromJson(json.decode(jsonString));

Service Implementation

class GetStorageService implements StorageServiceInterface {
  // Other methods...

  void saveUser(String key, User user) {
    box.write(key, userToJson(user));
  }

  User? loadUser(String key) {
    final jsonString = box.read<String>(key);
    return jsonString != null ? userFromJson(jsonString) : null;
  }
}

Additional Features of get_storage

  1. Multiple Storage Boxes:
final box1 = GetStorage('box1');
final box2 = GetStorage('box2');

2. Write In-Memory Only:

box.writeInMemory('tempKey', 'temporaryValue');

3. Remove Specific Key:

box.remove('keyToRemove');

4. Erase All Data:

box.erase();

Conclusion

The get_storage package is a powerful tool for state persistence in Flutter. Its speed, simplicity, and flexibility make it an excellent choice for small to medium-sized applications.

Whether you're storing basic primitives, lists, or complex objects, get_storage handles it all with ease. By following best practices and utilizing the examples shared, you can implement robust state management in your Flutter apps.

Happy coding! 🚀

What are your go-to tools for state persistence in Flutter? Share your thoughts and experiences below!