DEV Community

Cover image for Persistent State Management in Flutter with hydrated_bloc
مصعب يوسف
مصعب يوسف

Posted on Originally published at Medium on

Persistent State Management in Flutter with hydrated_bloc

Persistent State Management in Flutter with hydrated_bloc

In modern Flutter app development, managing state efficiently is a cornerstone of building high-performance, intuitive, and responsive applications. State management determines how data flows within your app and how the UI updates based on that data. One of the most effective and widely adopted patterns in Flutter is the BLoC (Business Logic Component) architecture. This pattern promotes separation of concerns by isolating business logic from the UI layer.

While the flutter_bloc package is a powerful tool for implementing this pattern, combining it with hydrated_bloc takes things a step further. It introduces automatic state persistence, significantly improving app experience and resilience. This guide explores what hydrated_bloc is, why you should use it, how to implement it, and provides practical examples to help you harness its full potential.

What is hydrated_bloc?

hydrated_bloc is a companion package to flutter_bloc that automatically persists and restores BLoC and Cubit states. It leverages local storage through hydrated_storage, typically powered by platform-specific file paths via the path_provider package. This allows the application to restore its previous state even after being closed or forcefully terminated.

By serializing state into JSON and handling storage behind the scenes, hydrated_bloc eliminates the need to manually manage shared preferences, files, or databases for simple data persistence.

Benefits of Using hydrated_bloc

Here are several compelling reasons to incorporate hydrated_bloc into your Flutter apps:

  1. Automatic State Persistence: Your app retains state seamlessly between launches, offering a smoother user experience.
  2. Improved User Experience: Prevents loss of user input, settings, or progress during app restarts.
  3. Reduced Boilerplate Code: Handles persistence without requiring repetitive save/load logic.
  4. Effortless Integration: Easily integrates with existing flutter_bloc projects with minimal changes.
  5. Cross-platform Support: Works reliably on both Android and iOS.
  6. Optional Encryption: Supports encrypted storage for sensitive data.
  7. Custom Serialization: Flexible enough to handle complex object structures.

How to Use hydrated_bloc

Step 1: Add Dependencies

Update your pubspec.yaml:

# pubspec.yaml
dependencies:
  flutter_bloc: ^8.1.2
  hydrated_bloc: ^9.1.0
  path_provider: ^2.0.11
Enter fullscreen mode Exit fullscreen mode

Step 2: Initialize Hydrated Storage

Ensure storage is initialized before running the app in main.dart:

import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'app.dart';


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final storage = await HydratedStorage.build(
    storageDirectory: await getApplicationDocumentsDirectory(),
  );

HydratedBlocOverrides.runZoned(
    () => runApp(MyApp()),
    storage: storage,
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Hydrated Cubit

Below is a simple counter example:

import 'package:hydrated_bloc/hydrated_bloc.dart';

class CounterCubit extends HydratedCubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  @override
  int? fromJson(Map<String, dynamic> json) => json['value'] as int?;
  @override
  Map<String, dynamic>? toJson(int state) => {'value': state};
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Connect the Cubit to the UI

Use BlocProvider and BlocBuilder to make the Cubit accessible in the widget tree:

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hydrated Counter')),
      body: Center(
        child: BlocBuilder<CounterCubit, int>(
          builder: (context, count) => Text(
            '$count',
            style: TextStyle(fontSize: 36),
          ),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'decrement',
            onPressed: () => context.read<CounterCubit>().decrement(),
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            heroTag: 'increment',
            onPressed: () => context.read<CounterCubit>().increment(),
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

hydrated_bloc can greatly enhance various features in real-world applications, such as:

  • Theme Mode: Persist light/dark mode preferences.
  • Login Sessions: Retain user authentication tokens and session states.
  • Language Preferences: Store and reapply selected locale on app launch.
  • E-commerce Carts: Keep cart contents across user sessions.
  • Game Data: Persist game levels, points, and achievements.
  • Form Data: Retain partially completed form inputs for later continuation.

Comparison: hydrated_bloc vs. SharedPreferences vs. Local Databases

When choosing how to persist data in Flutter apps, it’s helpful to compare different approaches. Below is a breakdown of the three main options, organized by feature:

1. Ease of Use

  • hydrated_bloc: Very easy to use. It integrates directly with BLoC and requires minimal setup.
  • SharedPreferences: Simple API, but you need to handle saving and loading manually.
  • Local Databases (e.g., sqflite, hive): More complex setup with schema management and queries.

2. Integration with BLoC

  • hydrated_bloc: Built specifically for BLoC and Cubit. Works out of the box.
  • SharedPreferences: No native integration; requires manual coordination with state logic.
  • Local Databases: Not designed for BLoC, integration must be done manually.

3. State Serialization

  • hydrated_bloc: Built-in JSON serialization.
  • SharedPreferences: Requires manual JSON handling.
  • Local Databases: Supports complex serialization; depends on the database used.

4. Performance

  • hydrated_bloc: Excellent for simple to medium-sized data.
  • SharedPreferences: Fast for small key-value pairs.
  • Local Databases: Best suited for large-scale or relational data.

5. Flexibility

  • hydrated_bloc: Moderate flexibility, ideal for common app use cases.
  • SharedPreferences: Limited to simple types (int, bool, string, etc.).
  • Local Databases: Highly flexible and customizable, supports relationships and indexing.

6. Security

  • hydrated_bloc: Optional encryption is available.
  • SharedPreferences: Basic or no encryption, depending on platform.
  • Local Databases: Depends on the library; some (like Hive) support encryption.

7. Complex Data Support

  • hydrated_bloc: Supports complex objects via toJson / fromJson methods.
  • SharedPreferences: Not well-suited for complex or nested data.
  • Local Databases: Excellent support for deeply nested and structured data.

Final Recommendation

Use hydrated_bloc :

  • if you're already using BLoC/Cubit and want seamless, automatic state persistence with minimal boilerplate.

Use SharedPreferences :

  • for simple configuration settings or preferences that don’t change often.
  • You don’t need deep integration with BLoC or Cubit.

Use a local database : like sqflite or hive

  • when your app requires structured data, advanced queries, or offline-first capabilities.
  • Your app requires advanced querying or CRUD operations.
  • You’re storing larger or more complex data sets.

Conclusion

The hydrated_bloc package is a powerful tool that bridges the gap between stateless UI and persistent application data. It enables you to build apps that remember their last state with minimal effort, contributing to a consistent and reliable user experience.

Whether you’re building small utility apps or large-scale enterprise solutions, integrating hydrated_bloc into your BLoC architecture ensures better state continuity, reduced boilerplate, and cleaner architecture. With support for complex serialization and optional encryption, it's suitable for a wide range of use cases.

Top comments (0)