Introduction
Offline mode is a crucial requirement for almost every app, from e-commerce apps to social media apps. This involves storing data locally on a user's device for later use.
For example:
Current logged-in user information like username, email, profile avatar URL etc.
REST API returned data like a list of products, feeds, or articles.
Cart items.
There are several options available to persist data locally like SharedPreference, SQLite, Room etc.
In this article, we will explore what Hive is and how to persist data in Flutter apps using Hive.
Hive
Hive is a lightweight, fast key-value, NoSQL database written in pure Dart.
Hive has a high benchmark, it has no native dependencies as it is written in Dart.
It also supports all platforms supported by Flutter.
A NoSQL database (aka "not only SQL") is a non-relational database that stores data in an unstructured form without following a fixed schema.
If you need to model your data with many relationships, in that case, it is recommended to use SQLite
.
Let's get started by adding the hive_flutter dependency to our pubspec.yaml
dependencies:
hive_flutter: ^1.1.0
Create a DatabaseService
class and add initializeDatabase()
method. Inside this initializeDatabase()
method, we are going to initialize Hive.
class DatabaseService {
Future<void> initializeDatabase() async {
await Hive.initFlutter();
}
}
Boxes
Hive stores its data in boxes containing key-value sets. Hence, we can open a box to store user details and another box to store a list of cart items.
Open box
Before accessing a box, we need to first open it.
await Hive.openBox('testBox');
await Hive.openBox<DataModel>('dataModelBox');
Get the opened box
var testBox = Hive.box('testBox');
var dataModelBox = Hive.box<DataModel>('dataModelBox');
Write to the opened box
testBox.put("email", "johndoe@gmail.com");
testBox.put("username", "johndoe");
Read from the opened box
If the key is not present in the box, null
will be returned. Hence, we can use Dart
Null safety feature to make our email and username variables nullable. By default, Dart variables are non-nullable, and passing null
value to a non-nullable variable will crash our app. Optionally, the box get
method has a named parameter called defaultValue
, where we can pass a default value.
Null safety prevents errors that result from unintentional access of variables set to
null
.
String? email = testBox.get("email");
String? username = testBox.get("username");
String email = testBox.get("email", defaultValue: "");
String username = testBox.get("username", defaultValue: "");
Type Adapters
Hive supports all primitive types, String
, int
, List
, Map
, DateTime
and Uint8List
. To store objects, we need to register a TypeAdapter
which converts the object from and to binary form.
Create a user model class
import 'package:hive_flutter/hive_flutter.dart';
part 'user.g.dart';
@HiveType(typeId: 0)
class User {
@HiveField(0)
String userId;
@HiveField(1)
String username;
@HiveField(2)
String email;
@HiveField(3)
String photoUrl;
@HiveField(4)
String bio;
User(this.userId, this.username, this.email, this.photoUrl, this.bio);
@override
String toString() {
return 'User(userId: $userId, username: $username, email: $email, photoUrl: $photoUrl, bio: $bio)';
}
}
In the User class above, we imported the hive_flutter
package; also, we annotated our User
data class with the @HiveType
and provided a typeId
.
The part "user.g.dart";
will be generated automatically for us.
Add the following dependencies below to your pubspec.yaml
We are going to use these dependencies to auto-generate some classes later on.
dev_dependencies:
hive_generator: ^1.1.0
build_runner: ^2.1.11
Run the build runner command below to auto-generate a UserAdapter class inside a file called user.g.dart
.
flutter pub run build_runner build --delete-conflicting-outputs
Register Adapter
String boxName = "UserBox";
class DatabaseService {
Future<void> initializeDatabase() async {
// ...
await Hive.openBox<User>(boxName);
Hive.registerAdapter<User>(UserAdapter());
}
}
Get data
Here we just check if the box contains data, we return the first item else we just return null
.
class DatabaseService {
late Box<User> userBox;
Future<void> initializeDatabase() async {
//...
userBox = Hive.box(boxName);
}
User? read() {
return userBox.values.isNotEmpty
? userBox.values.first
: null;
}
}
Insert or update data
Here, we first clear the box by calling userBox.clear()
before inserting the updated user object. This is to ensure that we only have one user object in the box. I.e., we can only have one logged-in user at a time.
class DatabaseService {
//...
Future<void> insertOrUpdate(User user) async {
await userBox.clear();
await userBox.add(user);
}
}
Delete data
class DatabaseService {
//...
Future<void> delete() async {
await userBox.clear();
}
}
We are going to use the get_it package to register and retrieve an object of our DatabaseService
class.
The
get_it
package is a service locator for Dart and Flutter applications. This package allows you to register and retrieve objects (Services) from anywhere within your app and also makes it easier to manage dependencies and decouple different parts of your code.
dependencies:
get_it: ^7.6.0
Create a file called locator.dart.
Inside this file, add a function called setupLocator()
and register the DatabaseService
class as a Singleton
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
setupLocator() {
locator.registerSingleton<DatabaseService>(
DatabaseService(),
);
}
Inside the main()
method, call setupLocator();
to register the services with get_it
.
We are going to use locator<
DatabaseService>()
to get the instance of DatabaseService
class and call the initializeDatabase()
to initialize our Hive
Database and open our UserBox
Future<void> main() async {
setupLocator();
await locator<DatabaseService>().initializeDatabase();
runApp(const MyApp());
}
We can play around with the Hive Database by inserting, updating, reading, and deleting data.
// Retrieve DatabaseService object
DatabaseService databaseService = locator<DatabaseService>();
// Create a user object
User user =
User("1", "johndoe", "johndoe@jd.com", "https://cdn.pixabay.com/photo/2021/10/24/21/34/profile-pic-6739366_1280.png", "Software Engineer");
// Insert user object into the database
await databaseService.insertOrUpdate(user);
// Read stored user object
User? dbUser = databaseService.read();
// Update current user username
user.username = "johndoe.eth";
await databaseService.insertOrUpdate(user);
// Delete current user data (E.g When a user logs out)
await databaseService.delete();
Conclusion
This article provided a comprehensive overview of Hive, a popular NoSQL database solution for Flutter applications. We discussed the process of persisting data in Flutter apps using Hive and also explored various CRUD operations, including creating, reading, updating and deleting data in Hive.
You can consider using Hive in your next Flutter project due to its lightweight nature and support for key-value pairs and complex data structures. Additionally, Hive's compatibility with both Android and iOS platforms ensures cross-platform support.
Thank you for reading.