Using Isar Database for Fast Local Storage in Flutter

When developing Flutter applications that require fast and efficient local storage, Isar Database emerges as a compelling solution. Isar is a NoSQL database designed specifically for mobile applications. It boasts exceptional performance, simplicity, and Flutter compatibility, making it an excellent choice for managing local data within your apps.

What is Isar Database?

Isar is a free, open-source, strongly typed, and cross-platform NoSQL database. Designed for simplicity and speed, it’s particularly well-suited for Flutter and Dart applications. Unlike SQLite, Isar is written in Dart and C++, resulting in faster read and write operations. Its schema is defined in Dart, providing a smooth and intuitive developer experience.

Why Use Isar in Your Flutter App?

  • Performance: Isar’s architecture allows for extremely fast read and write speeds, critical for data-intensive applications.
  • Simplicity: It provides a straightforward and developer-friendly API, making database operations easy to implement.
  • Strongly Typed: Prevents common data-related errors by enforcing strict typing throughout your data models.
  • Multiplatform: Isar supports Android, iOS, and desktop platforms, ensuring consistent performance across different devices.
  • Asynchronous: Designed to be fully asynchronous, Isar integrates seamlessly with Flutter’s async/await pattern.

Getting Started with Isar in Flutter

Step 1: Add Isar Dependencies

First, add the necessary dependencies to your pubspec.yaml file:


dependencies:
  isar: ^3.1.0
  isar_flutter_libs: ^3.1.0

dev_dependencies:
  isar_generator: ^3.1.0
  build_runner: ^2.4.6

Explanation:

  • isar: Core Isar package.
  • isar_flutter_libs: Native Isar libraries optimized for Flutter.
  • isar_generator: Generates code for Isar models.
  • build_runner: A generic package that helps run code generation tools like Isar generator.

Run flutter pub get to fetch the dependencies.

Step 2: Define Your Data Model

Create a Dart class that represents your data model. Annotate the class with @collection to tell Isar that it’s a database collection. Use @Id() to define a unique identifier field:


import 'package:isar/isar.dart';

part 'task.g.dart';

@collection
class Task {
  @Id()
  int? id;

  late String title;

  String? description;

  bool completed = false;
}

Key points:

  • The part 'task.g.dart'; directive is essential because the Isar generator will generate code into that file.
  • The @collection annotation marks this class as a storable collection.
  • @Id() identifies the primary key (Isar automatically assigns IDs).
  • Fields such as title, description, and completed define your data structure.

Step 3: Generate Isar Code

Run the following command in your terminal to generate the necessary Isar code:


flutter pub run build_runner build --delete-conflicting-outputs

This command uses the build_runner package to execute the Isar generator, creating the task.g.dart file, which contains code that enables Isar to manage your Task object.

Step 4: Open the Isar Database

Before you can use Isar, you need to open the database. Typically, you’ll do this when your app starts:


import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';

import 'task.dart';

late Isar isar;

Future openIsar() async {
  final dir = await getApplicationDocumentsDirectory();
  isar = await Isar.open(
    [TaskSchema],
    directory: dir.path,
  );
}

Important:

  • getApplicationDocumentsDirectory() comes from the path_provider package and specifies where Isar should store the database files. Don’t forget to add path_provider to your `pubspec.yaml`.
  • Isar.open() initializes the database with the schema for your collections (in this case, TaskSchema which is auto-generated) and a directory.

Step 5: Perform CRUD Operations

Now you can perform Create, Read, Update, and Delete (CRUD) operations. Here are some examples:

Create

Future createTask(String title, String? description) async {
  final newTask = Task()
    ..title = title
    ..description = description;

  await isar.writeTxn(() async {
    await isar.tasks.put(newTask); // Inserts & updates same
  });
}
Read

Future> getAllTasks() async {
  return await isar.tasks.where().findAll();
}
Update

Future updateTask(int id, String newTitle) async {
  await isar.writeTxn(() async {
    final task = await isar.tasks.get(id);
    if (task != null) {
      task.title = newTitle;
      await isar.tasks.put(task);
    }
  });
}
Delete

Future deleteTask(int id) async {
  await isar.writeTxn(() async {
    await isar.tasks.delete(id);
  });
}

Step 6: Integrate with Your Flutter UI

Use these database operations within your Flutter widgets to manage the state of your application. Employ FutureBuilder or StreamBuilder to handle asynchronous data fetching from Isar effectively.


import 'package:flutter/material.dart';
import 'package:isar_example/task.dart';
import 'package:isar_example/main.dart';

class TaskList extends StatefulWidget {
  const TaskList({Key? key}) : super(key: key);

  @override
  _TaskListState createState() => _TaskListState();
}

class _TaskListState extends State {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Task List')),
      body: FutureBuilder>(
        future: isar.tasks.where().findAll(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            final tasks = snapshot.data!;
            return ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (context, index) {
                final task = tasks[index];
                return ListTile(
                  title: Text(task.title),
                  subtitle: Text(task.description ?? ''),
                );
              },
            );
          } else {
            return const Center(child: Text('No tasks yet.'));
          }
        },
      ),
    );
  }
}

Advanced Isar Features

  • Indexing: Add indexes to frequently queried fields for even faster read operations.
  • Relationships: Define relationships between collections (one-to-one, one-to-many, many-to-many).
  • Encryption: Encrypt your Isar database for enhanced data security.
  • Version Migration: Manage database schema changes gracefully with version migration.

Best Practices

  • Isolate Database Operations: Perform database operations on separate isolates to prevent blocking the main UI thread.
  • Close Isar: Properly close the Isar instance when it’s no longer needed, typically when your app terminates, to release resources. (Although the OS should handle this, it’s good practice.)
  • Error Handling: Implement proper error handling around database operations.
  • Transactions: Use write transactions for multiple operations to ensure data consistency.

Conclusion

Isar Database is a powerful and efficient solution for local storage in Flutter applications. Its speed, simplicity, and type-safety make it an excellent alternative to SQLite, providing a superior developer experience. By following the steps outlined in this guide, you can quickly integrate Isar into your Flutter project, enhancing your app’s performance and data management capabilities.