When building Flutter applications, managing local data efficiently is critical. You want a database that is lightweight, fast, and easy to integrate, especially if your app will work offline. Isar is one such database. It is a high-performance, easy-to-use NoSQL embedded database tailored for Flutter. With features like reactive queries, indexes, relationships, migrations, and transactions, Isar makes local data persistence both powerful and developer-friendly.
In this article, you’lll learn how to integrate Isar into a Flutter project, set up a data model, and perform the full range of CRUD (Create, Read, Update, Delete) operations. To make this practical, you’ll build a simple to-do app that allows users to create, view, update, and delete tasks.
Table of Contents
Prerequisites
Before starting, ensure you have the following:
Flutter SDK installed (version 3.0 or above recommended).
Check your version with:flutter --version
Dart knowledge: Familiarity with Dart syntax, classes, and async programming.
Flutter basics: You should know how to set up a Flutter project, build widgets, and use
FutureBuilder
orsetState
for state management.Code editor: VS Code or Android Studio is recommended.
If these are in place, we are ready to begin.
What We Are Building
We will create a Task Manager App that lets users:
Add new tasks.
View all tasks in a list.
Update existing tasks.
Delete tasks.
By the end, you will have a fully functioning CRUD app built with Flutter and Isar.
How to Set Up Isar in a Flutter Project
Step 1: Add dependencies
Open your pubspec.yaml
file and add the following:
dependencies:
flutter:
sdk: flutter
isar: ^3.1.0
isar_flutter_libs: ^3.1.0
dev_dependencies:
isar_generator: ^3.1.0
build_runner: any
isar
: The core Isar package.isar_flutter_libs
: Required for Flutter integration.isar_generator
: Used to generate code for your models.build_runner
: Runs the code generator.
Run:
flutter pub get
Step 2: Create and initialize Isar
Create a file named isar_setup.dart
. This will handle the opening of the Isar database.
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'task.dart'; // we will create this model soon
late final Isar isar;
Future<void> initializeIsar() async {
final dir = await getApplicationDocumentsDirectory();
isar = await Isar.open(
[TaskSchema],
directory: dir.path,
);
}
Explanation:
getApplicationDocumentsDirectory()
provides a storage location for the database file.Isar.open()
initializes the database and registers ourTask
schema.late final Isar isar;
ensures we can access the database instance globally after initialization.
How to Create the Task Model
Now let’s define our data model for tasks. Create a file named task.dart
.
import 'package:isar/isar.dart';
part 'task.g.dart';
@Collection()
class Task {
Id id = Isar.autoIncrement; // auto-incrementing primary key
late String name;
late DateTime createdAt;
Task(this.name) : createdAt = DateTime.now();
}
Explanation:
@Collection()
tells Isar this class represents a database collection.Id id = Isar.autoIncrement;
creates a unique identifier automatically.late String name;
stores the task name.late DateTime createdAt;
stores the creation timestamp.part 'task.g.dart';
links to the generated code, which will be created after running the code generator.
Generate the code with:
flutter pub run build_runner build
This generates task.g.dart
, which contains the necessary schema code.
How to Build the Repository for CRUD Operations
Create a new file called task_repository.dart
. This will house the methods for interacting with the database.
import 'package:isar/isar.dart';
import 'task.dart';
import 'isar_setup.dart';
class TaskRepository {
Future<void> addTask(String name) async {
final task = Task(name);
await isar.writeTxn(() async {
await isar.tasks.put(task);
});
}
Future<List<Task>> getAllTasks() async {
return await isar.tasks.where().findAll();
}
Future<void> updateTask(Task task) async {
await isar.writeTxn(() async {
await isar.tasks.put(task);
});
}
Future<void> deleteTask(Task task) async {
await isar.writeTxn(() async {
await isar.tasks.delete(task.id);
});
}
}
Explanation:
addTask
: Creates a new task and saves it.getAllTasks
: Reads all tasks from the database.updateTask
: Updates an existing task by calling.put()
again.deleteTask
: Removes a task by itsid
.isar.writeTxn
: Ensures operations run inside a transaction for safety and consistency.
How to Integrate CRUD into the Flutter UI
Now, let’s connect everything inside main.dart
.
import 'package:flutter/material.dart';
import 'isar_setup.dart';
import 'task_repository.dart';
import 'task.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeIsar(); // initialize Isar before runApp
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TaskListScreen(),
);
}
}
class TaskListScreen extends StatefulWidget {
@override
_TaskListScreenState createState() => _TaskListScreenState();
}
class _TaskListScreenState extends State<TaskListScreen> {
final TaskRepository _taskRepository = TaskRepository();
late Future<List<Task>> _tasksFuture;
@override
void initState() {
super.initState();
_tasksFuture = _taskRepository.getAllTasks();
}
Future<void> _addTask() async {
await _taskRepository.addTask('New Task');
setState(() {
_tasksFuture = _taskRepository.getAllTasks();
});
}
Future<void> _deleteTask(Task task) async {
await _taskRepository.deleteTask(task);
setState(() {
_tasksFuture = _taskRepository.getAllTasks();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Isar CRUD Example')),
body: FutureBuilder<List<Task>>(
future: _tasksFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final tasks = snapshot.data ?? [];
if (tasks.isEmpty) {
return Center(child: Text('No tasks yet.'));
}
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return ListTile(
title: Text(task.name),
subtitle: Text('Created at: ${task.createdAt}'),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteTask(task),
),
);
},
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: _addTask,
child: Icon(Icons.add),
),
);
}
}
Explanation:
initializeIsar()
: Ensures the database is ready before the app runs._tasksFuture
: Holds a future of the list of tasks._addTask
: Adds a new task and refreshes the list._deleteTask
: Deletes a task and refreshes the list.FutureBuilder
: Automatically rebuilds the UI when the future completes.ListView.builder
: Displays all tasks dynamically.
This gives you a simple yet complete CRUD app using Isar.
Beyond CRUD: Advanced Features of Isar
Once you are comfortable with CRUD, Isar provides advanced tools to optimize and extend your application:
Reactive Queries:
Instead of usingFutureBuilder
, you can listen for changes directly.final stream = isar.tasks.where().watch(fireImmediately: true);
Indexes:
Improve query performance by indexing fields.@Collection() class Task { Id id = Isar.autoIncrement; @Index() late String name; }
Relations:
Link one collection to another (for example,Project
with manyTasks
).Custom Queries:
Perform complex filtering, sorting, and pagination.Migrations:
Safely evolve your schema as the app grows.Batch Operations:
Insert or update many records in one transaction.
Conclusion
We built a simple Flutter to-do app with Isar that supports creating, reading, updating, and deleting tasks. Along the way, we learned how to:
Add Isar dependencies.
Define a model with annotations.
Generate schema code.
Implement CRUD operations in a repository.
Connect Isar to the Flutter UI.
With its performance, developer-friendly API, and advanced features, Isar is an excellent choice for local persistence in Flutter applications.
For further learning, consult the official docs: