Onuoha Ifeanyi I am a mobile developer who is passionate about building cool stuff with Flutter.

Comparing Hive to other Flutter app database options

7 min read 2024

Comparing Hive to other Flutter app database options

In today’s always online world, there are still lots of use cases for storing data offline; especially when it comes to mobile development, but which database should you use?

In Flutter, you have an array of options to pick from if you need an offline database solution. Which option you ultimately go for is up to you, but there are factors that affect this decision (aside from the unique features they might provide) that typically boil down to how simple it is to get started and their speed, both of which we will cover in this article.

In this blog post, we’ll be looking at how the setup works for Hive and other databases, and compare and contrast basic functionalities every database will be expected to feature (specifically CRUD, i.e., create, read, update, delete).

We’ll do some benchmarking to determine how long it takes each database option to perform these operations, so you can have a clear understanding of which database option is right for your Flutter app project, Hive or otherwise.

Let’s get started.

CRUD

The CRUD operations referred to previously will be carried out using a User object, i.e., writing n number of user objects to the database, etc. This is what the user object would look like:

class UserModel {
  final int id;
  final DateTime createdAt;
  final String username;
  final String email;
  final int age;

  UserModel(...);

  factory UserModel.fromMap(Map<String, dynamic> map) {
    return UserModel(...);
  }
  Map<String, dynamic> toMap() {
    return {...};
  }
}

To make use of an offline database in an application, the first thing to do is to get the location of the app directory which is where the database will be stored. The Flutter path_provider and path packages can be used to easily retrieve this information:

import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

final dir = await getApplicationDocumentsDirectory();
// For hive, isar and objectbox
final dbPath = dir.path;
// For sembast and sqflite
final dbPath = path.join(dir.path, 'databaseName.db');

Hive

Hive is a key-value database written in pure Dart, with no native dependencies. This makes it truly cross-platform, as it is able to run on all platforms that support the Dart programming language.

Initializing the database is usually done immediately once the application launches. Once you have the path to where the database should be stored, initializing the Hive database is as easy as calling:

Hive.init(dbPath);

To perform CRUD operations using Hive, an instance of a Box is needed.

final box = await Hive.openBox('users');

To add/update a user to the database (Create/Update), you have to provide a unique key and a value to the put() method:

final user = UserModel(...);
await box.put(user.id, user.toMap());

To get a user from the database (Read), you simply request the user using the key provided, like so:

final userMap = box.get(user.id);
return UserModel.fromMap(userMap);

To delete a user from the database (Delete), you pass the key to the delete method.

final user = UserModel(...);
await box.delete(user.id);

Sembast

Sembast is a NoSQL database that makes use of a text document where each entry is a JSON object. Sembast makes it easy to build reactive apps, as you can listen and trigger certain actions when a document changes.

With the database path ready, you can initialize the database by calling:

final db = await databaseFactoryIo.openDatabase(dbPath);

To perform CRUD operations using Sembast, an instance of a StoreRef is needed:

final store = StoreRef('users');

To add/update a user to the database, you have to pass a key to the record function and chain the put method, which takes the database and value as parameters:

final user = UserModel(...);
await store.record(user.id).put(db, user.toMap());

To get a user from the database, you must pass the key to record and chain the get method:

final userMap = await store.record(user.id).get(db);
return UserModel.fromMap(userMap);

To delete a user from the database, you pass the key to record and chain the delete method:

final user = UserModel(...);
await store.record(user.id).delete(db);

Sqflite

Sqflite is a SQL database that provides the ability to write raw SQL commands, which are quite powerful when you know what to do. You also have the option to make use of helper functions.

With the database path ready, initialize the database and create a table to store users by doing the following:

static const String USER_TABLE = "users";
final db = await openDatabase(
      dbPath,
      onCreate: (db, version) async {
        await db.execute(
          'CREATE TABLE $USER_TABLE (id TEXT PRIMARY KEY, createdAt TEXT, 
          username TEXT, email TEXT, age INTEGER)',
        );
      },
      version: 1,
    );

To add/update a user to the database, you have to pass the table name and value to be inserted to the insert or update helper functions.



final user = UserModel(...);
await db.insert(USER_TABLE, user.toMap());
// to update the user defined above
await db.update(USER_TABLE, user.toMap(), where: "id = ?", whereArgs: [user.id]);

To get a user from the database, you can use a query string to search it. This will return a list of matches, along with a user ID that is unique; it should return a Map containing only one item:

final users = await db.query(USER_TABLE, where: "id = ?", whereArgs: [user.id]);
return UserModel.fromMap(users.first);

To delete a user from the database, you pass to the delete method, the table name, and a query string to identify the user to be deleted:

final user = UserModel(...);
await db.delete(USER_TABLE, where: "id = ?", whereArgs: [user.id]);

To continue with Isar and ObjectBox, we’ll need more dependencies for the project, as they both use type annotations and code generation, which enables you to read/write Dart objects directly to the database (Hive also provides this as an option).

Isar

Isar is a feature-rich offline database with powerful queries, filters, and sort functionalities.

You can also build very reactive apps with it, as it is able to listen to data changes. With Isar, this is what the dependencies and model classes would look like:

dependencies:
  ...
  isar: $latest
  isar_flutter_libs: $latest
dev_dependencies:
  ...
  build_runner: $latest
  isar_generator: $latest
import 'package:isar/isar.dart';
part 'isar_user.g.dart';

@Collection() // Anotate the user model using Collection()
class UserModel {
  int id;
  ...
  UserModel(...);
}

To get the autogenerated code, simply run flutter pub run build_runner build.

With the database path ready, initialize the database by calling:

final isar = await Isar.open(
        schemas: [UserModelSchema], // A list of anotated collections
        directory: dbPath,
      );

To add/update a user to the database, you have to pass the user model directly to the put method:

final user = UserModel(...);
isar.writeTxn((isar) async {
  await isar.isarUserModels.put(user);
}

To get a user from the database, you use the get method, which takes the id of the user:

final user = await isar.isarUserModels.get(user.id);

To delete a user from the database, you use the delete method, which takes the id of the user:

isar.writeTxn((isar) async {
  await isar.isarUserModels.delete(user.id);
}

ObjectBox

ObjectBox is a NoSQL database written with pure Dart. With ObjectBox, this is what the dependencies and model classes look like:

dependencies:
  ...
  objectbox: $latest
  objectbox_flutter_libs: $latest
dev_dependencies:
  ...
  build_runner: $latest
  objectbox_generator: $latest
@Entity() // Anotate the user model using Entity()
class UserModel {
  int id;
  ...
  UserModel(...);
}

To get the autogenerated code simply run flutter pub run build_runner build, exactly the same as with Isar. With the database path ready, you can initialize the database by calling:

final store = await openStore(directory: dbPath);
final box = store.box<UserModel>();

To add/update a user to the database, you have to pass the UserModel directly to the put method:

final user = UserModel(...);
box.put(user);;

To get a user from the database, you use the get method, which takes the id of the user:

final user = box.get(user.id);

To delete a user from the database, you use the remove method, which takes the id of the user;

box.remove(user.id);

Benchmark results

Firstly, we’ll be looking at how long it takes each database option to perform n number of writes, reads, and deletes.

Secondly, seeing as some of these database options (Hive, Isar, and ObjectBox) have more optimized ways to perform CRUD operations — like writing, reading, or deleting multiple items using a single method — we’ll also look at how long it takes to write, read, and delete n number of users from the database.

final users = [UserModel(...), UserModel(...), UserModel(...)];
// Hive
box.putAll(data), .deleteAll(userIds)
// Isar
isar.isarUserModels.putAll(users), .getAll(userIds), .deleteAll(userIds)
// Objectbox
box.putMany(users), .getMany(userIds), .removeMany(userIds)

Thirdly, because of the variation on different device operating systems, the benchmark will be done on both Android and iOS physical devices, while running in release mode for optimal performance.


More great articles from LogRocket:


iOS v15.6 (iPhone 12)

The following benchmark results were received while running the project in release mode on an iPhone 12 running iOS v15.6.

Below are the results (time in milliseconds) from 10 consecutive runs when 1,000 individual write, read, and delete operations were performed.

Write

Avg (ms)
Isar 65 73 68 68 71 69 73 69 68 70 69.7
Hive 86 85 97 93 91 87 85 90 91 100 90.5
Sembast 241 252 263 257 258 240 236 253 257 246 250.3
Sqflite 774 653 665 697 757 757 769 836 758 819 751.2
ObjectBox 18686 18705 18806 18790 18767 18724 18763 18717 18739 18744 18744.1

Read

Avg (ms)
Hive 0 0 0 0 0 0 0 0 0 0 0.0
Sembast 2 2 2 2 2 2 2 2 2 2 2.0
ObjectBox 14 24 24 23 30 24 24 24 23 23 23.3
Isar 103 99 98 116 99 98 111 98 98 108 102.8
Sqflite 135 133 136 151 134 132 133 131 155 140 138.0

Delete

Avg (ms)
Isar 41 32 33 34 36 32 34 33 36 36 34.7
Hive 73 86 73 78 92 76 80 64 65 71 75.8
Sembast 485 507 491 481 503 491 497 523 503 515 499.6
Sqflite 733 750 743 741 748 743 749 754 842 830 763.3
ObjectBox 18771 18784 18684 18698 18761 18680 18738 18683 18744 18739 18782.2

Single Run iOS

Below are the results (time in milliseconds) from 10 consecutive runs when optimized methods are used (for Hive, Isar, and ObjectBox) to write, read, and delete 1,000 users.

Write

Avg (ms)
Isar 6 3 6 5 7 5 5 5 5 6 5.3
Hive 14 16 12 15 15 17 15 15 14 13 14.6
ObjectBox 20 19 23 18 19 23 20 20 19 20 20.1

Read

Avg (ms)
Hive 0 0 0 0 0 0 0 0 0 0 0.0
ObjectBox 0 0 0 0 0 0 0 0 0 0 0.0
Isar 1 0 1 1 0 1 1 1 1 1 0.8

Delete

Avg (ms)
Hive 1 1 1 2 1 1 1 1 2 2 1.3
Isar 1 3 1 2 1 1 1 1 2 4 1.7
ObjectBox 19 19 21 19 19 20 18 19 19 18 19.1

Single Optimized Run iOS

Android 11 (Galaxy A31)

The following benchmark results were received while running the project in release mode on a physical Samsung Galaxy A31 running Android 11.

Below are the results (time in milliseconds) from 10 consecutive runs when 1,000 individual write, read, and delete operations are performed.

Write

Avg (ms)
Hive 322 321 322 402 380 287 340 303 300 320 329.7
Isar 382 431 311 351 346 377 323 363 262 363 350.9
ObjectBox 1614 1525 1608 1502 1473 1522 1583 1522 1619 1521 1548.9
Sembast 2666 2352 2600 2507 2416 2297 2712 2641 2399 2508 2509.8
Sqflite 3968 5281 4122 3448 3767 3641 4280 3609 3828 4026 3997.0

Read

Avg (ms)
Hive 1 1 1 1 1 1 1 1 1 1 1.0
Sembast 17 17 16 16 18 17 16 17 15 15 16.4
ObjectBox 18 22 19 17 21 18 20 20 17 10 19.2
Isar 1142 1497 1380 1162 1305 1200 1240 1194 1206 1349 1267.5
Sqflite 3148 3275 3209 2696 2691 2723 2731 2660 2680 2654 2846.7

Delete

Avg (ms)
Isar 358 380 322 347 354 375 341 321 318 353 346.9
Hive 763 873 860 721 879 801 848 819 868 772 820.4
ObjectBox 1566 1740 1580 1574 1650 2167 1575 1546 1586 1572 1655.6
Sqflite 3896 4026 3946 3878 3610 3889 3558 4315 3554 3509 3818.1
Sembast 6349 6729 7375 6575 6585 6980 6321 6770 6256 6756 6689.0

Single Run Android

Below are the results (time in milliseconds) from 10 consecutive runs when optimized methods are used (for Hive, Isar, and ObjectBox) to write, read, and delete 1,000 users.

Write

Avg (ms)
ObjectBox 4 6 6 10 5 4 5 5 5 5 5.5
Isar 9 10 9 9 8 7 7 9 7 8 8.3
Hive 14 20 18 14 13 16 15 14 16 13 15.3

Read

Avg (ms)
Hive 0 0 0 0 0 0 0 0 0 0 0.0
ObjectBox 1 1 1 1 1 1 1 1 1 1 1.0
Isar 6 7 3 5 6 4 6 4 3 4 4.8

Delete

Avg (ms)
ObjectBox 3 2 2 2 3 3 2 2 3 3 2.5
Hive 5 6 7 5 5 7 5 6 6 4 5.6
Isar 8 8 5 4 4 5 5 8 9 3 5.9

Single Optimized Run Android

Final results

For write operations on iOS, Isar was the fastest. Performing 1,000 individual write operations in an average of 69.7ms and writing 1,000 users in an average of 5.3ms.

On Android, meanwhile, Hive was the fastest with an average of 329.7ms when performing 1,000 individual write operations — though, when optimized, ObjectBox was able to write 1,000 users to the database in 5.5ms, performing better than both Hive (15.3ms) and Isar (8.3ms).

For read operations, Hive was the fastest on both iOS and Android. Performing both 1,000 individual read operations and reading 1,000 users in an average of 0ms (less than 1ms). ObjectBox also read 1,000 users in an average of 0ms on iOS and 1ms on Android, but performed 1,000 individual read operations in an average of 23.3ms on iOS and 19.2ms on Android.

For delete operations, Isar was the fastest on both iOS and Android. Performing 1,000 individual delete operations in an average of 34.7ms on iOS and 346.9 on Android. Hive performed slightly better when deleting 1,000 users (average of 1.3ms) as compared to Isar (average of 1.7ms) on iOS while on Android. ObjectBox was the fastest when deleting 1,000 users from the database, with an average of 2.5ms.

Conclusion

Setup was quite straightforward for all options discussed, so ease of use probably comes down to syntax preference, which in my personal opinion would be Hive. Isar and ObjectBox might require extra setup, but do allow you to read and write Dart objects directly to the database (Hive also provides an option for this, with the same extra setup of annotations and code generation).

Thank you for making it to the bottom of my experiment — I hope this article has helped you come to a decision about which Flutter app database works best for your project. If you would like to run the benchmarks yourself, here’s the link to the project on GitHub for your convenience.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Onuoha Ifeanyi I am a mobile developer who is passionate about building cool stuff with Flutter.

One Reply to “Comparing Hive to other Flutter app database options”

Leave a Reply