Using NoSQL Databases Like Hive and Isar for Efficient Local Storage in Flutter

Flutter, Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, offers developers various options for data persistence. While relational databases like SQLite are commonly used, NoSQL databases are becoming increasingly popular for their flexibility, scalability, and performance, especially for local storage in Flutter apps. This article explores the benefits and usage of NoSQL databases such as Hive and Isar in Flutter applications.

Understanding NoSQL Databases

NoSQL databases (often interpreted as “Not Only SQL”) are non-relational databases that provide a mechanism for storage and retrieval of data that is modeled in means other than the tabular relations used in relational databases like SQLite. NoSQL databases are highly suitable for handling large volumes of unstructured or semi-structured data. Their flexible schemas, horizontal scalability, and simpler design make them a compelling choice for modern application development.

Why Use NoSQL Databases in Flutter?

  • Flexibility: NoSQL databases offer flexible schemas, allowing you to store and retrieve data without predefined structures. This is particularly useful for handling dynamic and evolving data models.
  • Performance: NoSQL databases are designed for high-performance read and write operations, making them suitable for applications that require fast data access and storage.
  • Scalability: NoSQL databases can scale horizontally across multiple nodes, allowing you to handle large amounts of data and high traffic loads.
  • Simplicity: Many NoSQL databases offer simple APIs and data models, making them easier to learn and use compared to traditional relational databases.

Overview of Hive and Isar

Hive and Isar are two prominent NoSQL database solutions for Flutter. Both databases offer excellent performance and ease of use, but they have different characteristics that make them suitable for different use cases.

Hive

Hive is a lightweight and fast NoSQL database for Flutter, written in pure Dart. It is ideal for storing simple key-value pairs and objects locally on the device.

Key Features of Hive:
  • Pure Dart: Hive is written entirely in Dart, making it compatible with Flutter’s single-language philosophy.
  • Simple API: Hive offers a simple and intuitive API for storing and retrieving data.
  • Fast Performance: Hive provides excellent read and write performance, making it suitable for high-performance applications.
  • Encryption: Hive supports encryption to protect sensitive data stored on the device.
  • No Native Dependencies: Since Hive is written in Dart, it has no native dependencies, simplifying deployment and reducing the risk of platform-specific issues.

Isar

Isar is a fast, scalable, and easy-to-use NoSQL database for Flutter. It is built on top of the high-performance ObjectBox database engine and provides advanced features such as object relations, indexing, and querying.

Key Features of Isar:
  • High Performance: Isar is built on top of ObjectBox, which is known for its high performance and low latency.
  • Object Relations: Isar supports object relations, allowing you to define relationships between different data models.
  • Indexing: Isar supports indexing to speed up query performance.
  • Asynchronous API: Isar offers an asynchronous API for non-blocking data access.
  • Multiplatform Support: Isar supports multiple platforms, including iOS, Android, and desktop.

Using Hive in Flutter

Here’s a step-by-step guide on how to use Hive for efficient local storage in a Flutter application.

Step 1: Add Dependency

Add the hive and hive_flutter dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  hive: ^2.2.3
  hive_flutter: ^1.1.2
dev_dependencies:
  flutter_test:
    sdk: flutter
  hive_generator: ^2.0.1
  build_runner: ^2.4.6

Step 2: Initialize Hive

Initialize Hive in your main function:

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  runApp(MyApp());
}

Step 3: Define a Data Model

Create a Dart class to represent your data model and annotate it with @HiveType. You also need to define @HiveField annotations for each field you want to store in the database:

import 'package:hive/hive.dart';

part 'person.g.dart';

@HiveType(typeId: 0)
class Person {
  @HiveField(0)
  String name;

  @HiveField(1)
  int age;

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

Run flutter pub run build_runner build to generate the .g.dart file. This file contains the necessary adapters for Hive to serialize and deserialize your data model.

Step 4: Register the Adapter

Register the generated adapter in your main function:

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'person.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  Hive.registerAdapter(PersonAdapter());
  runApp(MyApp());
}

Step 5: Open a Box

Open a Hive box to store and retrieve data:

late Box personBox;

Future openBoxes() async {
  personBox = await Hive.openBox('peopleBox');
}

Step 6: Store and Retrieve Data

Use the put and get methods to store and retrieve data:

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'person.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  final TextEditingController nameController = TextEditingController();
  final TextEditingController ageController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hive Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: InputDecoration(labelText: 'Name'),
            ),
            TextField(
              controller: ageController,
              decoration: InputDecoration(labelText: 'Age'),
              keyboardType: TextInputType.number,
            ),
            ElevatedButton(
              onPressed: () {
                final person = Person(
                  name: nameController.text,
                  age: int.parse(ageController.text),
                );
                Hive.box('peopleBox').add(person);
                nameController.clear();
                ageController.clear();
              },
              child: Text('Add Person'),
            ),
            Expanded(
              child: ValueListenableBuilder(
                valueListenable: Hive.box('peopleBox').listenable(),
                builder: (context, Box box, _) {
                  if (box.values.isEmpty) {
                    return Center(
                      child: Text('No data available'),
                    );
                  }
                  return ListView.builder(
                    itemCount: box.values.length,
                    itemBuilder: (context, index) {
                      final person = box.getAt(index) as Person;
                      return ListTile(
                        title: Text(person.name),
                        subtitle: Text('Age: ${person.age}'),
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Using Isar in Flutter

Here’s a step-by-step guide on how to use Isar for efficient local storage in a Flutter application.

Step 1: Add Dependency

Add the isar and isar_flutter_libs dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  isar: ^3.1.0+1
  isar_flutter_libs: ^3.1.0+1
dev_dependencies:
  flutter_test:
    sdk: flutter
  isar_generator: ^3.1.0+1
  build_runner: ^2.4.6

Step 2: Define a Data Model

Create a Dart class to represent your data model and annotate it with @Collection. Add @Id() annotation to your primary key. This can be either int or String type. Note that your class can have only one @Id() field.:

import 'package:isar/isar.dart';

part 'dog.g.dart';

@Collection()
class Dog {
  Id id = Isar.autoIncrement; // you can also use int? id
  
  String? name;

  int? age;
}

Run flutter pub run build_runner build to generate the .g.dart file. This file contains the necessary adapters for Isar to serialize and deserialize your data model.

Step 3: Initialize Isar

Initialize Isar in your main function:

import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'dog.dart';

late Isar isar;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dir = await getApplicationDocumentsDirectory();
  isar = await Isar.open(
    [DogSchema],
    directory: dir.path,
  );
  runApp(MyApp());
}

Step 4: Store and Retrieve Data

Use the isar.writeTxn and isar.dogs.put methods to store data and isar.dogs.where().findAll methods to retrieve data:

import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'dog.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  final TextEditingController nameController = TextEditingController();
  final TextEditingController ageController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Isar Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: InputDecoration(labelText: 'Name'),
            ),
            TextField(
              controller: ageController,
              decoration: InputDecoration(labelText: 'Age'),
              keyboardType: TextInputType.number,
            ),
            ElevatedButton(
              onPressed: () async {
                final dog = Dog()
                  ..name = nameController.text
                  ..age = int.parse(ageController.text);

                await isar.writeTxn(() async {
                  await isar.dogs.put(dog); // insert & update
                });
                nameController.clear();
                ageController.clear();
                setState(() {}); // refresh the list
              },
              child: Text('Add Dog'),
            ),
            Expanded(
              child: FutureBuilder(
                future: isar.dogs.where().findAll(),
                builder: (context, AsyncSnapshot> snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return CircularProgressIndicator();
                  } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  } else {
                    final dogs = snapshot.data;
                    if (dogs == null || dogs.isEmpty) {
                      return Center(child: Text('No dogs available.'));
                    }
                    return ListView.builder(
                      itemCount: dogs.length,
                      itemBuilder: (context, index) {
                        final dog = dogs[index];
                        return ListTile(
                          title: Text(dog.name ?? 'Unnamed'),
                          subtitle: Text('Age: ${dog.age ?? 0}'),
                        );
                      },
                    );
                  }
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    isar.close();
    super.dispose();
  }
}

Conclusion

NoSQL databases like Hive and Isar provide efficient and flexible solutions for local storage in Flutter applications. Hive is lightweight and easy to use, making it suitable for simple key-value storage, while Isar offers high performance, object relations, and indexing, making it suitable for more complex data models. Choosing the right NoSQL database depends on your specific requirements and the complexity of your data. By leveraging these databases, you can build high-performance, scalable, and flexible Flutter applications that efficiently manage local data.