Hive in Flutter: A Detailed Guide with Injectable, Freezed, and Cubit in Clean Architecture

--

Peace upon you …
When building Flutter applications, data persistence is a crucial part of maintaining a smooth user experience. The Hive package is a lightweight and efficient NoSQL database designed specifically for Flutter and Dart. It offers a fast, reliable, and easy-to-use data storage solution that works offline and is ideal for apps with local storage needs.

This article will dive into Hive in detail and guide you through integrating it with Injectable for dependency injection, Freezed for immutable data classes, and Cubit for state management in the context of Clean Architecture.

What is Hive?

Hive is a lightweight key-value database for Flutter. Unlike other databases, Hive doesn’t require any complex setup or SQL queries. It’s a NoSQL solution that allows you to store and retrieve data in a simple way.

Key Features of Hive:

  • Cross-platform: Works on Android, iOS, and the web.
  • Lightweight: Hive is designed to be small and fast.
  • NoSQL: You store data as key-value pairs.
  • Offline support: Hive operates fully offline.
  • Binary storage: Hive uses binary storage, making it highly efficient.

1. Setting Up Hive in Flutter

Let’s start by setting up Hive in your Flutter project.

Step 1: Add Hive dependencies

First, include the necessary Hive dependencies in your pubspec.yaml file:

dependencies:
hive: ^2.0.0
hive_flutter: ^1.1.0
hive_generator: ^1.1.0
path_provider: ^2.0.0
freezed_annotation: ^2.1.0
injectable: ^2.0.0
flutter_bloc: ^8.0.1

dev_dependencies:
build_runner: ^2.1.0
freezed: ^2.1.0

Step 2: Initialize Hive

In the main function of your Flutter app, you need to initialize Hive and register adapters (if you use custom types):

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Initialize Hive
await Hive.initFlutter();

// Register Hive Adapters (for custom types)??


runApp(MyApp());
}

2. Using Hive with Custom Types

Hive works best with primitive types like int, String, List, and Map. However, for complex or custom data types, you need to create a TypeAdapter to serialize and deserialize your custom classes.

Let’s say we want to store a User model in Hive. We’ll use Freezed to create an immutable data class for the User and generate the necessary Hive adapter.

Step 1: Define the User Model with Freezed

Create a user.dart file and define your User class using Freezed annotations:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
@HiveType(typeId: 0) // Hive adapter type ID
const factory User({
@HiveField(0) required String id,
@HiveField(1) required String name,
@HiveField(2) required int age,
}) = _User;

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

In the above code:

  • @freezed: This annotation generates the required boilerplate for the immutable data class.
  • @HiveType and @HiveField: These annotations define the model for Hive, where typeId is a unique identifier for the type, and HiveField indicates the position of each field.

Step 2: Generate the User Model and Adapter

Run the following command to generate the Hive adapter and the Freezed class:

dart run build_runner watch --delete-conflicting-outputs

This will generate the required .g.dart and .freezed.dart files for your User class.

Step 3: Register the User Adapter in Hive

In the main.dart file, register the UserAdapter with Hive during initialization:

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Initialize Hive
await Hive.initFlutter();

// Register the User adapter
Hive.registerAdapter(UserAdapter());

runApp(MyApp());
}

3. Integrating Hive in Clean Architecture with Cubit, Injectable, and Freezed

Now that Hive is set up, we can integrate it into a Clean Architecture framework using Cubit for state management and Injectable for dependency injection.

Clean Architecture Structure

We’ll use the following structure:

  • Data Layer: Handles data access (Hive in this case).
  • Domain Layer: Contains business logic (Use cases).
  • Presentation Layer: The UI and state management using Cubit.

Step 1: Setting Up Dependency Injection with Injectable

Create an injection.dart file to configure the Injectable package:

import 'package:injectable/injectable.dart';
import 'package:get_it/get_it.dart';
import 'injection.config.dart';

final getIt = GetIt.instance;

@InjectableInit()
void configureInjection(String env) => $initGetIt(getIt);

Run this command to generate the injection.config.dart:

dart run build_runner watch --delete-conflicting-outputs

Now, you can inject Hive services or repositories into any class.

Step 2: Create a Repository for Hive in the Data Layer

In the data layer, create a user_repository.dart file to handle Hive operations:

import 'package:hive/hive.dart';
import 'package:injectable/injectable.dart';

import '../domain/user.dart';

abstract class IUserRepository {
Future<void> saveUser(User user);
User? getUser(String id);
}

@LazySingleton(as: IUserRepository)
class UserRepository implements IUserRepository {
final Box<User> _userBox;

UserRepository(this._userBox);

@override
Future<void> saveUser(User user) async {
await _userBox.put(user.id, user);
}

@override
User? getUser(String id) {
return _userBox.get(id);
}
}

In this code, we inject the Hive Box of User type and use it to store and retrieve users.

Step 3: Create Use Cases in the Domain Layer

Create use cases for saving and retrieving users:

import 'package:injectable/injectable.dart';
import '../entities/user.dart';
import '../repositories/i_user_repository.dart';

@lazySingleton
class SaveUser {
final IUserRepository _repository;

SaveUser(this._repository);

Future<void> call(User user) async {
await _repository.saveUser(user);
}
}

@lazySingleton
class GetUser {
final IUserRepository _repository;

GetUser(this._repository);

User? call(String id) {
return _repository.getUser(id);
}
}

Step 4: Set Up Cubit in the Presentation Layer

Create a Cubit to handle the business logic and UI state:

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import '../domain/user.dart';
import '../use_cases/get_user.dart';
import '../use_cases/save_user.dart';

part 'user_state.dart';

@injectable
class UserCubit extends Cubit<UserState> {
final SaveUser _saveUser;
final GetUser _getUser;

UserCubit(this._saveUser, this._getUser) : super(UserInitial());

Future<void> saveUser(User user) async {
emit(UserLoading());
await _saveUser(user);
emit(UserSaved());
}

Future<void> loadUser(String id) async {
emit(UserLoading());
final user = _getUser(id);
if (user != null) {
emit(UserLoaded(user));
} else {
emit(UserError('User not found'));
}
}
}

The Cubit handles loading and saving users using the GetUser and SaveUser use cases.

Step 5: Use Cubit in Your UI

In your UI, you can use BlocBuilder to interact with the Cubit:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'user_cubit.dart';
import 'user.dart';

class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User')),
body: BlocBuilder<UserCubit, UserState>(
builder: (context, state) {

state.whenOrNull(

UserLoading:()=>CircularProgressIndicator(),

UserLoaded:()=>Text('User: ${state.user.name}'),

UserLoaded:()=>Text('Error: ${state.message}'),

orElse: () => Center(child: Text('No user data'),

);

}),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Save a user
context.read<UserCubit>().saveUser(User(id: '1', name: 'John', age: 30));
},
child: Icon(Icons.save),
),
);
}
}

Conclusion

By integrating Hive with Injectable, Freezed, and Cubit in a Clean Architecture structure, you can efficiently manage data persistence in your Flutter apps while maintaining a clear separation of concerns. This approach allows you to build scalable, maintainable applications with robust local storage and dependency injection patterns.

--

--

No responses yet