ISAR is a modern, cross-platform NoSQL database built specifically for Flutter and Dart. Written in Rust and compiled to native binaries, it delivers exceptional performance for local data storage while offering a fully type-safe query API, real-time reactive streams, and zero-boilerplate schema generation. This guide covers ISAR from the ground up with real-world Flutter examples.
Why ISAR Matters for Mobile Developers?
Most Flutter apps eventually need local caching, offline-first logic, or high-frequency writes. ISAR was purpose-built for exactly this. It runs on Android, iOS, macOS, Linux, and Windows with native performance on every platform. Note: Flutter Web is not supported by ISAR.
Mastering ISAR helps you:
- Build offline-first apps that sync automatically when connectivity is restored.
- Query large datasets without blocking the UI thread
- Replace SharedPreferences or Hive wherever complex querying is needed.
- Eliminate boilerplate with auto-generated schema code via build_runner
- Auto-update UI using reactive streams when data changes.
Prerequisites
Before diving in, make sure you have:
- Flutter SDK installed (3.x or later recommended)
- Dart 3.x compatible project
- Basic understanding of async/await in Dart
- Familiarity with code generation (build_runner)
1. Setting Up ISAR in Flutter
Add the required packages to your pubspec.yaml:
dependencies:
isar: ^3.1.0
isar_flutter_libs: ^3.1.0 # Bundles native binaries
path_provider: ^2.0.0
dev_dependencies:
isar_generator: ^3.1.0
build_runner: ^2.4.0
Note: isar_flutter_libs bundles native binaries and is required only for Flutter mobile/desktop apps. Pure Dart projects use a different setup and refer to the ISAR documentation for non-Flutter environments.
Install the packages by running:
flutter pub get
After defining your collection classes, generate the schema code:
dart run build_runner build
2. ISAR Collections (The Data Blueprint)
In ISAR, a Collection is the equivalent of a database table. Each collection maps to a Dart class annotated with @collection. ISAR auto-generates all schema metadata and typed query extensions from this annotation.
Common use-case collections in an enterprise Flutter app:
| Collection Class | Use Case |
| Employee | HR data — name, role, department |
| AttendanceRecord | Clock-in/out times, location |
| LeaveRequest | Leave type, status, date range |
| Payslip | Salary breakdown per period |
| SaleOrder | Order lines, totals, customer ref |
Defining a Collection
Each collection must include the part directive pointing to its generated file:
import 'package:isar/isar.dart';
part 'employee.g.dart'; // Auto-generated
@collection
class Employee {
Id id = Isar.autoIncrement;
late String name;
late String role;
late String department;
bool isActive = true;
@Index(type: IndexType.value)
late String email;
}
This tells ISAR to auto-increment the ID for every new record. Running build_runner generates employee.g.dart with all schema metadata and typed query extensions.
3. ISAR Field Types (The Data Shape)
Fields define the data a collection holds. ISAR supports a rich set of native Dart types with automatic serialization, no manual mapping required.
Supported Field Types
| Dart Type | ISAR Type | Notes |
| int | Long | 64-bit integer; use for IDs and counts |
| double | Double | Floating-point; suitable for prices and GPS coords |
| bool | Bool | true / false flags |
| String | String | UTF-8 text; supports full-text indexing |
| DateTime | DateTime | Stored as UTC microseconds internally |
| List<T> | List | Lists of any supported primitive type |
| Embedded object | Object | Nested schema — no foreign key needed |
Embedded Objects (Nested Schemas)
ISAR supports embedding sub-objects directly inside a collection. These are ideal for nested address, location, or metadata structures with no join required:
@embedded
class Address {
late String street;
late String city;
late String country;
}
@collection
class Employee {
Id id = Isar.autoIncrement;
late String name;
Address? address; // Embedded — stored inline, no join
}
Indexes
Indexes speed up query performance on frequently filtered or sorted fields. Declare them using the @Index annotation:
@collection
class AttendanceRecord {
Id id = Isar.autoIncrement;
@Index(type: IndexType.value)
late int employeeId;
@Index(type: IndexType.hash, composite: [CompositeIndex('date')])
late String status; // 'present', 'absent', 'late'
late DateTime date;
DateTime? clockIn;
DateTime? clockOut;
}
Index types in ISAR:
- IndexType.value: Exact match and range queries (default; best for numbers and enums)
- IndexType.hash: Equality-only lookups on Strings (faster writes, no range support)
- IndexType.hashElements: For List fields, indexes each element individually
4. Opening the ISAR Database
ISAR must be opened once at app startup, and the instance reused throughout the app lifecycle. Typically done inside main() or a top-level provider:
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
late Isar isar;
Future<void> initIsar() async {
final dir = await getApplicationDocumentsDirectory();
isar = await Isar.open(
[EmployeeSchema, AttendanceRecordSchema], // Register all schemas
directory: dir.path,
name: 'appDatabase',
);
}
Pro Tip: ISAR Inspector: During development, ISAR provides a built-in browser-based Inspector that lets you browse collections, run queries, and inspect records live. It launches automatically in debug mode. So, open http://localhost:8080 in your browser while the app is running on a simulator/emulator.
Call initIsar() before runApp() in your main function:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initIsar();
runApp(const MyApp());
}5. CRUD Operations
All write operations in ISAR must run inside a write transaction. Reads are performed freely without a transaction.
Create / Update (Write)
Future<void> saveEmployee(Employee employee) async {
await isar.writeTxn(() async {
await isar.employees.put(employee); // Insert or update by Id
});
}Read (Query)
Future<List<Employee>> getActiveEmployees() async {
return await isar.employees
.filter()
.isActiveEqualTo(true)
.findAll();
}
// Fetch a single record by ID
Future<Employee?> getEmployeeById(int id) async {
return await isar.employees.get(id);
}Delete
Future<void> deleteEmployee(int id) async {
await isar.writeTxn(() async {
await isar.employees.delete(id);
});
}
// Delete all inactive employees
Future<void> deleteInactive() async {
await isar.writeTxn(() async {
await isar.employees
.filter()
.isActiveEqualTo(false)
.deleteAll();
});
}6. Querying Data (Filter Logic)
ISAR provides a type-safe query builder that compiles to native queries; no raw SQL, no string-based parsing, and no runtime errors from typos.
Common Filter Operations
| Operation | Example Method | Description |
| Equality | .nameEqualTo('Ali') | Exact match on field |
| Contains | .nameContains('ahmed') | Substring search |
| Range | .salaryBetween(3000, 8000) | Between two values |
| Greater / Less | .salaryGreaterThan(5000) | Comparison operators |
| Multiple (AND) | .and() | Chained AND conditions |
| Multiple (OR) | .or() | Chained OR conditions |
| Not | .not() | Negate the next filter |
| List contains | .tagsAnyEqualTo('flutter') | Match inside a List field |
Chained Filter Example
Future<List<AttendanceRecord>> getLateArrivals({
required int employeeId,
required DateTime from,
required DateTime to,
}) async {
return await isar.attendanceRecords
.filter()
.employeeIdEqualTo(employeeId)
.and()
.statusEqualTo('late')
.and()
.dateBetween(from, to)
.sortByDateDesc()
.findAll();
}Sorting and Pagination
// Sort by date descending and paginate (use .where() only when filtering by an index)
final records = await isar.attendanceRecords
.filter()
.offset(0) // Skip N records
.limit(20) // Fetch only 20
.sortByDateDesc()
.findAll();
7. Reactive Streams (Watchers)
One of ISAR's most powerful Flutter features is its built-in reactive stream support. Watch a collection or a filtered query and rebuild widgets automatically whenever the underlying data changes, perfect for real-time dashboards, attendance trackers, or live order lists.
Watching a Collection
Stream<void> watchEmployees() {
return isar.employees.watchLazy();
// Emits whenever the employees collection changes
}Watching a Filtered Query
Stream<List<AttendanceRecord>> watchTodayAttendance() {
final today = DateTime.now();
final start = DateTime(today.year, today.month, today.day);
final end = start.add(const Duration(days: 1));
return isar.attendanceRecords
.filter()
.dateBetween(start, end)
.watch(fireImmediately: true); // Emits initial result immediately
}Using StreamBuilder in Flutter
StreamBuilder<List<AttendanceRecord>>(
stream: watchTodayAttendance(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
final records = snapshot.data!;
return ListView.builder(
itemCount: records.length,
itemBuilder: (context, index) => ListTile(
title: Text('Employee: ${records[index].employeeId}'),
subtitle: Text('Status: ${records[index].status}'),
),
);
},
)
8. Real-World Example: Offline Attendance Tracker
Below is a complete practical example combining collections, write transactions, queries, and streams. This scenario mirrors the attendance module pattern found in Odoo-integrated Flutter apps.
Collection Definition
part 'attendance_record.g.dart';
@collection
class AttendanceRecord {
Id id = Isar.autoIncrement;
@Index(type: IndexType.value)
late int employeeId;
late DateTime checkIn;
DateTime? checkOut;
@Index(type: IndexType.hash)
late String status; // 'present', 'late', 'absent'
bool synced = false; // Track Odoo sync state
}
Repository Layer
class AttendanceRepository {
final Isar isar;
AttendanceRepository(this.isar);
// 1. Clock In
Future<void> clockIn(int employeeId) async {
final record = AttendanceRecord()
..employeeId = employeeId
..checkIn = DateTime.now()
..status = 'present'
..synced = false;
await isar.writeTxn(() async {
await isar.attendanceRecords.put(record);
});
}
// 2. Clock Out
Future<void> clockOut(int recordId) async {
await isar.writeTxn(() async {
final record = await isar.attendanceRecords.get(recordId);
if (record != null) {
record.checkOut = DateTime.now();
await isar.attendanceRecords.put(record);
}
});
}
// 3. Get unsynced records to push to Odoo
Future<List<AttendanceRecord>> getUnsynced() async {
return await isar.attendanceRecords
.filter()
.syncedEqualTo(false)
.findAll();
}
// 4. Mark records as synced after successful Odoo push
Future<void> markSynced(List<int> ids) async {
await isar.writeTxn(() async {
final records = await isar.attendanceRecords.getAll(ids);
for (final r in records) {
if (r != null) {
r.synced = true;
await isar.attendanceRecords.put(r);
}
}
});
}
// 5. Watch live attendance stream
Stream<List<AttendanceRecord>> watchAttendance(int employeeId) {
return isar.attendanceRecords
.filter()
.employeeIdEqualTo(employeeId)
.watch(fireImmediately: true);
}
}This pattern “store locally first, sync later” is the backbone of any robust offline-first Odoo Flutter integration. The synced flag allows a background service to push pending records whenever network connectivity is restored.
9. When to Use Each ISAR Feature
| Feature | Purpose | Mobile Benefit |
| Collections | Define data structure | Type-safe, auto-generated schema |
| Embedded Objects | Nested data without joins | Simpler model, faster reads |
| Indexes | Speed up filtered queries | Instant lookups on large datasets |
| Write Transactions | Atomic multi-record writes | Data integrity even on crash |
| Filter Builder | Type-safe query chaining | No raw SQL, no runtime errors |
| Watchers / Streams | Reactive UI updates | Auto-rebuild on data change |
| synced Flag Pattern | Offline-first sync tracking | Reliable Odoo integration |
ISAR is the definitive local database for serious Flutter development. Its combination of native performance, expressive queries, reactive streams, and zero boilerplate makes it ideal for enterprise-grade mobile apps.
- Collections define what data you store.
- Fields and Embedded Objects define how data is shaped.
- Indexes make large-scale queries fast.
- The Filter Builder handles complex query logic type-safely
- Watchers turn your database into a reactive data source.
Whether you are developing an HR attendance tracker system, a field-service sales application, or any offline-first solution integrated with Odoo, ISAR provides reliable tools for fast and efficient local data management. Although official ISAR development has slowed as of 2026, the isar_community fork continues to offer ongoing updates and improvements.
To read more about How to Integrate RESTful APIs in Flutter Applications, refer to our blog How to Integrate RESTful APIs in Flutter Applications.