Isar
Isar is "a super fast NoSQL database for Flutter/Dart" developed as a cross-platform database with native performance. Its core engine written in C++ achieves overwhelming read/write performance that surpasses SQLite, communicating directly with native code through Dart FFI (Foreign Function Interface). With type-safe and intuitive query APIs, powerful indexing capabilities, automatic schema migration, and reactive stream monitoring, it establishes a new standard for high-performance local storage solutions in modern Flutter application development.
GitHub Overview
isar/isar
Extremely fast, easy to use, and fully async NoSQL database for Flutter
Topics
Star History
Library
Isar
Overview
Isar is "a super fast NoSQL database for Flutter/Dart" developed as a cross-platform database with native performance. Its core engine written in C++ achieves overwhelming read/write performance that surpasses SQLite, communicating directly with native code through Dart FFI (Foreign Function Interface). With type-safe and intuitive query APIs, powerful indexing capabilities, automatic schema migration, and reactive stream monitoring, it establishes a new standard for high-performance local storage solutions in modern Flutter application development.
Details
Isar 2025 edition fully supports Flutter 3.16+ and Dart 3.0, providing a next-generation database engine that maximizes the benefits of null safety and solid type systems. Advanced indexing strategies enable super-fast execution of complex queries, allowing performance optimization through composite indexes, partial indexes, and full-text search indexes. With encryption functionality, backup/restore capabilities, schema migration, flexible relationship management through links and backlinks, and powerful query builders, it provides all features necessary for enterprise-level mobile application development.
Key Features
- Ultra-Fast Performance: Up to 10x faster than SQLite with C++ core
- Powerful Indexing: Composite, partial, and full-text search index support
- Type-Safe Queries: Safe data access with compile-time type checking
- Reactive Streams: Automatic monitoring of data changes and UI updates
- Schema Migration: Support for both automatic and manual migrations
- Encryption Support: AES-256 encryption and secure data storage
Pros and Cons
Pros
- Overwhelming read/write performance far exceeding SQLite
- Perfect integration with Dart/Flutter ecosystem and type safety
- High-speed execution of complex queries through powerful indexing features
- Excellent affinity with reactive programming
- Improved development efficiency through automatic schema migration
- Rich query functionality and full-text search support
Cons
- Relatively new library with limited production track record
- Dependency on Rust/C++ can make debugging difficult
- Less documentation and community than Hive
- Complex relationship expressions require technique
- Larger binary size, unsuitable for lightweight apps
- Platform-specific compilation required
Reference Pages
Code Examples
Setup
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
isar: ^3.1.0+1
isar_flutter_libs: ^3.1.0+1
dev_dependencies:
isar_generator: ^3.1.0+1
build_runner: ^2.4.9
// main.dart - Initialization
import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'package:isar_flutter_libs/isar_flutter_libs.dart';
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Isar
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema, PostSchema, CategorySchema],
directory: dir.path,
);
runApp(MyApp(isar: isar));
}
class MyApp extends StatelessWidget {
final Isar isar;
const MyApp({super.key, required this.isar});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Isar Demo',
home: HomePage(isar: isar),
);
}
}
Basic Usage
// Collection definitions
import 'package:isar/isar.dart';
part 'user.g.dart'; // Generated file
@collection
class User {
Id id = Isar.autoIncrement;
@Index()
late String name;
@Index(unique: true)
late String email;
late int age;
late DateTime createdAt;
@Index()
late bool isActive;
List<String> hobbies = [];
@ignore
String? temporaryData; // Not saved to database
// Links (relationships)
final posts = IsarLinks<Post>();
final profile = IsarLink<UserProfile>();
// Computed property
@Index()
String get nameEmail => '$name $email';
// Object constructor
User({
required this.name,
required this.email,
required this.age,
DateTime? createdAt,
this.isActive = true,
this.hobbies = const [],
}) : createdAt = createdAt ?? DateTime.now();
}
@collection
class Post {
Id id = Isar.autoIncrement;
@Index()
late String title;
late String content;
@Index()
late DateTime publishedAt;
@Index()
late bool isPublished;
List<String> tags = [];
@Index()
late int categoryId;
// Backlink
@Backlink(to: 'posts')
final author = IsarLink<User>();
}
@collection
class Category {
Id id = Isar.autoIncrement;
@Index(unique: true)
late String name;
late String description;
late DateTime createdAt;
}
@collection
class UserProfile {
Id id = Isar.autoIncrement;
late String bio;
String? website;
String? location;
DateTime? birthDate;
// Backlink
@Backlink(to: 'profile')
final user = IsarLink<User>();
}
// Adapter generation command:
// dart run build_runner build
Data Operations
// Database operations class
class UserService {
final Isar isar;
UserService(this.isar);
// Create user
Future<User> createUser({
required String name,
required String email,
required int age,
List<String> hobbies = const [],
}) async {
final user = User(
name: name,
email: email,
age: age,
hobbies: hobbies,
);
await isar.writeTxn(() async {
await isar.users.put(user);
});
return user;
}
// Update user
Future<void> updateUser(User user) async {
await isar.writeTxn(() async {
await isar.users.put(user);
});
}
// Delete user
Future<bool> deleteUser(int id) async {
return await isar.writeTxn(() async {
return await isar.users.delete(id);
});
}
// Get single user
Future<User?> getUser(int id) async {
return await isar.users.get(id);
}
// Get all users
Future<List<User>> getAllUsers() async {
return await isar.users.where().findAll();
}
// Search user by email
Future<User?> getUserByEmail(String email) async {
return await isar.users.filter().emailEqualTo(email).findFirst();
}
// Search by name (partial match)
Future<List<User>> searchUsersByName(String nameQuery) async {
return await isar.users
.filter()
.nameContains(nameQuery, caseSensitive: false)
.findAll();
}
// Filter by age range
Future<List<User>> getUsersByAgeRange(int minAge, int maxAge) async {
return await isar.users
.filter()
.ageBetween(minAge, maxAge)
.findAll();
}
// Get active users only
Future<List<User>> getActiveUsers() async {
return await isar.users
.filter()
.isActiveEqualTo(true)
.sortByCreatedAtDesc()
.findAll();
}
// Complex query example
Future<List<User>> getActiveAdultUsersWithHobbies() async {
return await isar.users
.filter()
.isActiveEqualTo(true)
.and()
.ageGreaterThan(18)
.and()
.hobbiesIsNotEmpty()
.sortByName()
.findAll();
}
// Pagination
Future<List<User>> getUsersPaged(int offset, int limit) async {
return await isar.users
.where()
.offset(offset)
.limit(limit)
.findAll();
}
// Get count
Future<int> getUserCount() async {
return await isar.users.count();
}
Future<int> getActiveUserCount() async {
return await isar.users.filter().isActiveEqualTo(true).count();
}
}
// Usage example
void demonstrateBasicOperations() async {
final userService = UserService(isar);
// Create user
final user1 = await userService.createUser(
name: 'John Doe',
email: '[email protected]',
age: 30,
hobbies: ['reading', 'programming', 'gaming'],
);
print('Created user: ${user1.id}');
// Create multiple users
final users = [
('Jane Smith', '[email protected]', 25, ['cooking', 'travel']),
('Bob Johnson', '[email protected]', 35, ['sports', 'music']),
('Alice Brown', '[email protected]', 28, ['photography', 'art']),
];
for (final (name, email, age, hobbies) in users) {
await userService.createUser(
name: name,
email: email,
age: age,
hobbies: hobbies,
);
}
// Retrieve data
final allUsers = await userService.getAllUsers();
print('Total users: ${allUsers.length}');
// Search
final searchResults = await userService.searchUsersByName('john');
print('Search results: ${searchResults.length}');
// Age filter
final youngAdults = await userService.getUsersByAgeRange(20, 30);
print('Young adults: ${youngAdults.length}');
}
Links and Relationships
// Relationship operations service
class RelationshipService {
final Isar isar;
RelationshipService(this.isar);
// Add profile to user
Future<void> createUserProfile({
required User user,
required String bio,
String? website,
String? location,
DateTime? birthDate,
}) async {
final profile = UserProfile()
..bio = bio
..website = website
..location = location
..birthDate = birthDate;
await isar.writeTxn(() async {
await isar.userProfiles.put(profile);
user.profile.value = profile;
await user.profile.save();
});
}
// Create user post
Future<Post> createUserPost({
required User user,
required String title,
required String content,
List<String> tags = const [],
required int categoryId,
bool isPublished = false,
}) async {
final post = Post()
..title = title
..content = content
..tags = tags
..categoryId = categoryId
..publishedAt = DateTime.now()
..isPublished = isPublished;
await isar.writeTxn(() async {
await isar.posts.put(post);
user.posts.add(post);
await user.posts.save();
});
return post;
}
// Get user with posts (including relations)
Future<User?> getUserWithPosts(int userId) async {
final user = await isar.users.get(userId);
if (user != null) {
await user.posts.load();
await user.profile.load();
}
return user;
}
// Get post with author
Future<Post?> getPostWithAuthor(int postId) async {
final post = await isar.posts.get(postId);
if (post != null) {
await post.author.load();
}
return post;
}
// Get user posts by category
Future<List<Post>> getUserPostsByCategory(int userId, int categoryId) async {
final user = await isar.users.get(userId);
if (user == null) return [];
await user.posts.load();
return user.posts.where((post) => post.categoryId == categoryId).toList();
}
// Get published posts only
Future<List<Post>> getPublishedPosts({
int? limit,
int? offset,
}) async {
var query = isar.posts
.filter()
.isPublishedEqualTo(true)
.sortByPublishedAtDesc();
if (offset != null) {
query = query.offset(offset);
}
if (limit != null) {
query = query.limit(limit);
}
final posts = await query.findAll();
// Load author information for each post
for (final post in posts) {
await post.author.load();
}
return posts;
}
// Complex relationship query
Future<List<User>> getActiveUsersWithRecentPosts(int days) async {
final cutoffDate = DateTime.now().subtract(Duration(days: days));
return await isar.users
.filter()
.isActiveEqualTo(true)
.and()
.posts((q) => q
.isPublishedEqualTo(true)
.and()
.publishedAtGreaterThan(cutoffDate))
.findAll();
}
// Get user statistics
Future<Map<String, dynamic>> getUserStats(int userId) async {
final user = await isar.users.get(userId);
if (user == null) return {};
await user.posts.load();
final totalPosts = user.posts.length;
final publishedPosts = user.posts.where((p) => p.isPublished).length;
final recentPosts = user.posts
.where((p) => p.publishedAt.isAfter(
DateTime.now().subtract(const Duration(days: 30))))
.length;
return {
'totalPosts': totalPosts,
'publishedPosts': publishedPosts,
'draftPosts': totalPosts - publishedPosts,
'recentPosts': recentPosts,
'joinDate': user.createdAt,
'hasProfile': user.profile.value != null,
};
}
}
Reactive Queries and Streams
// Reactive UI implementation
class UserListWidget extends StatelessWidget {
final Isar isar;
const UserListWidget({super.key, required this.isar});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('User List')),
body: StreamBuilder<List<User>>(
stream: isar.users
.filter()
.isActiveEqualTo(true)
.watch(fireImmediately: true),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final users = snapshot.data ?? [];
if (users.isEmpty) {
return const Center(child: Text('No active users found'));
}
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return UserTile(user: user, isar: isar);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddUserDialog(context),
child: const Icon(Icons.add),
),
);
}
void _showAddUserDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AddUserDialog(isar: isar),
);
}
}
// Individual user tile
class UserTile extends StatelessWidget {
final User user;
final Isar isar;
const UserTile({super.key, required this.user, required this.isar});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: isar.users.watchObject(user.id, fireImmediately: true),
builder: (context, snapshot) {
final currentUser = snapshot.data ?? user;
return ListTile(
title: Text(currentUser.name),
subtitle: Text('${currentUser.age} years old - ${currentUser.email}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
currentUser.isActive ? Icons.pause : Icons.play_arrow,
),
onPressed: () => _toggleActiveStatus(currentUser),
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteUser(currentUser),
),
],
),
leading: CircleAvatar(
child: Text(currentUser.name.substring(0, 1)),
),
onTap: () => _showUserDetails(context, currentUser),
);
},
);
}
void _toggleActiveStatus(User user) async {
await isar.writeTxn(() async {
user.isActive = !user.isActive;
await isar.users.put(user);
});
}
void _deleteUser(User user) async {
await isar.writeTxn(() async {
await isar.users.delete(user.id);
});
}
void _showUserDetails(BuildContext context, User user) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserDetailPage(user: user, isar: isar),
),
);
}
}
// User detail page
class UserDetailPage extends StatelessWidget {
final User user;
final Isar isar;
const UserDetailPage({super.key, required this.user, required this.isar});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(user.name)),
body: StreamBuilder<User?>(
stream: isar.users.watchObject(user.id, fireImmediately: true),
builder: (context, snapshot) {
final currentUser = snapshot.data;
if (currentUser == null) {
return const Center(child: Text('User not found'));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_UserInfoCard(user: currentUser),
const SizedBox(height: 16),
_UserPostsSection(user: currentUser, isar: isar),
],
),
);
},
),
);
}
}
class _UserInfoCard extends StatelessWidget {
final User user;
const _UserInfoCard({required this.user});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.name,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text('Email: ${user.email}'),
Text('Age: ${user.age}'),
Text('Status: ${user.isActive ? "Active" : "Inactive"}'),
Text('Joined: ${_formatDate(user.createdAt)}'),
if (user.hobbies.isNotEmpty) ...[
const SizedBox(height: 8),
Text('Hobbies: ${user.hobbies.join(", ")}'),
],
],
),
),
);
}
String _formatDate(DateTime date) {
return '${date.year}/${date.month}/${date.day}';
}
}
class _UserPostsSection extends StatelessWidget {
final User user;
final Isar isar;
const _UserPostsSection({required this.user, required this.isar});
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Post>>(
stream: isar.posts
.filter()
.author((q) => q.idEqualTo(user.id))
.watch(fireImmediately: true),
builder: (context, snapshot) {
final posts = snapshot.data ?? [];
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Posts (${posts.length})',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
if (posts.isEmpty)
const Text('No posts yet')
else
...posts.map((post) => ListTile(
title: Text(post.title),
subtitle: Text(
'Published: ${_formatDate(post.publishedAt)}',
),
trailing: Icon(
post.isPublished
? Icons.visibility
: Icons.visibility_off,
),
)),
],
),
),
);
},
);
}
String _formatDate(DateTime date) {
return '${date.year}/${date.month}/${date.day}';
}
}
// Add new user dialog
class AddUserDialog extends StatefulWidget {
final Isar isar;
const AddUserDialog({super.key, required this.isar});
@override
State<AddUserDialog> createState() => _AddUserDialogState();
}
class _AddUserDialogState extends State<AddUserDialog> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _ageController = TextEditingController();
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Add New User'),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
TextFormField(
controller: _ageController,
decoration: const InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an age';
}
if (int.tryParse(value) == null) {
return 'Please enter a valid number';
}
return null;
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: _saveUser,
child: const Text('Save'),
),
],
);
}
void _saveUser() async {
if (_formKey.currentState!.validate()) {
final user = User(
name: _nameController.text,
email: _emailController.text,
age: int.parse(_ageController.text),
);
await widget.isar.writeTxn(() async {
await widget.isar.users.put(user);
});
if (mounted) {
Navigator.of(context).pop();
}
}
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_ageController.dispose();
super.dispose();
}
}
Indexing and Performance Optimization
// Advanced indexing strategy
@collection
class OptimizedUser {
Id id = Isar.autoIncrement;
// Basic index
@Index()
late String name;
// Unique index
@Index(unique: true)
late String email;
// Composite index (for fast range queries)
@Index(composite: [CompositeIndex('age')])
late bool isActive;
late int age;
// Case-insensitive index
@Index(caseSensitive: false)
late String username;
// Partial index (conditional index)
@Index(
type: IndexType.hash,
replace: true, // Replace duplicate keys
)
String? phoneNumber;
// Full-text search index
@Index(type: IndexType.words)
late String biography;
late DateTime createdAt;
late DateTime lastLoginAt;
List<String> skills = [];
// Computed property for composite index
@Index(composite: [CompositeIndex('lastLoginAt')])
bool get isRecentlyActive =>
lastLoginAt.isAfter(DateTime.now().subtract(const Duration(days: 7)));
}
// Performance-optimized query service
class OptimizedQueryService {
final Isar isar;
OptimizedQueryService(this.isar);
// High-speed search leveraging indexes
Future<List<OptimizedUser>> findActiveUsersByAge(int minAge, int maxAge) async {
// Leverage composite index (isActive, age)
return await isar.optimizedUsers
.where()
.isActiveAge(true, between: [minAge, maxAge])
.findAll();
}
// Search leveraging hash index
Future<OptimizedUser?> findUserByPhone(String phone) async {
return await isar.optimizedUsers
.where()
.phoneNumberEqualTo(phone)
.findFirst();
}
// Full-text search example
Future<List<OptimizedUser>> searchUsersByBio(String searchTerm) async {
final words = searchTerm.toLowerCase().split(' ');
return await isar.optimizedUsers
.filter()
.biographyWordsAnyStartWith(words)
.findAll();
}
// Case-insensitive search
Future<List<OptimizedUser>> findUsersByUsername(String username) async {
return await isar.optimizedUsers
.where()
.usernameEqualTo(username)
.findAll();
}
// Complex composite query (optimized)
Future<List<OptimizedUser>> getRecentlyActiveSkillfulUsers(
List<String> requiredSkills,
) async {
return await isar.optimizedUsers
.filter()
.isRecentlyActiveEqualTo(true)
.and()
.repeat((q) => q.skillsAnyOf(requiredSkills), requiredSkills.length)
.sortByLastLoginAtDesc()
.findAll();
}
// Performance improvement through batch operations
Future<void> batchUpdateUsers(List<OptimizedUser> users) async {
await isar.writeTxn(() async {
await isar.optimizedUsers.putAll(users);
});
}
// Aggregate query optimization
Future<Map<String, dynamic>> getUserAnalytics() async {
final total = await isar.optimizedUsers.count();
final active = await isar.optimizedUsers
.filter()
.isActiveEqualTo(true)
.count();
final recentlyActive = await isar.optimizedUsers
.filter()
.isRecentlyActiveEqualTo(true)
.count();
// Get age distribution
final ageGroups = <String, int>{};
const ageRanges = [(18, 25), (26, 35), (36, 45), (46, 60), (61, 100)];
for (final (min, max) in ageRanges) {
final count = await isar.optimizedUsers
.filter()
.ageBetween(min, max)
.count();
ageGroups['$min-$max'] = count;
}
return {
'total': total,
'active': active,
'recentlyActive': recentlyActive,
'ageGroups': ageGroups,
'activePercentage': total > 0 ? (active / total * 100).round() : 0,
};
}
}
// Index maintenance and monitoring
class IndexMaintenanceService {
final Isar isar;
IndexMaintenanceService(this.isar);
// Get database statistics
Future<Map<String, dynamic>> getDatabaseStats() async {
final stats = <String, dynamic>{};
// Size of each collection
stats['collections'] = {
'users': await isar.optimizedUsers.count(),
'posts': await isar.posts.count(),
'categories': await isar.categorys.count(),
};
// Database file size (estimated)
stats['estimatedSize'] = await _getEstimatedSize();
return stats;
}
// Compact database
Future<void> compactDatabase() async {
await isar.compact();
}
// Rebuild indexes (if necessary)
Future<void> rebuildIndexes() async {
// Isar automatically manages indexes,
// so manual index rebuilding is usually unnecessary
// Processed automatically during schema changes
}
Future<int> _getEstimatedSize() async {
// In actual implementation, get filesystem size
// Here we return an estimated value
final userCount = await isar.optimizedUsers.count();
final postCount = await isar.posts.count();
// Assume approximately 1KB per user, 2KB per post
return userCount * 1024 + postCount * 2048;
}
}
Error Handling
// Robust database operations service
class RobustIsarService {
final Isar isar;
RobustIsarService(this.isar);
// Safe user creation
Future<Result<User, String>> createUserSafely({
required String name,
required String email,
required int age,
List<String> hobbies = const [],
}) async {
try {
// Input validation
final validation = _validateUserInput(name, email, age);
if (validation != null) {
return Result.error(validation);
}
// Duplicate check
final existingUser = await isar.users
.filter()
.emailEqualTo(email)
.findFirst();
if (existingUser != null) {
return Result.error('User with email $email already exists');
}
// Create user
final user = User(
name: name,
email: email,
age: age,
hobbies: hobbies,
);
await isar.writeTxn(() async {
await isar.users.put(user);
});
return Result.success(user);
} on IsarError catch (e) {
return Result.error('Database error: ${e.message}');
} catch (e) {
return Result.error('Unexpected error: $e');
}
}
// Safe user update
Future<Result<User, String>> updateUserSafely(
int userId,
Map<String, dynamic> updates,
) async {
try {
final user = await isar.users.get(userId);
if (user == null) {
return Result.error('User not found');
}
// Validate and apply update fields
if (updates.containsKey('name')) {
final name = updates['name'] as String?;
if (name == null || name.trim().isEmpty) {
return Result.error('Name cannot be empty');
}
user.name = name.trim();
}
if (updates.containsKey('email')) {
final email = updates['email'] as String?;
if (email == null || !_isValidEmail(email)) {
return Result.error('Invalid email format');
}
// Duplicate check (excluding self)
final existingUser = await isar.users
.filter()
.emailEqualTo(email)
.and()
.not()
.idEqualTo(userId)
.findFirst();
if (existingUser != null) {
return Result.error('Email already exists');
}
user.email = email;
}
if (updates.containsKey('age')) {
final age = updates['age'] as int?;
if (age == null || age < 0 || age > 150) {
return Result.error('Invalid age');
}
user.age = age;
}
if (updates.containsKey('hobbies')) {
final hobbies = updates['hobbies'] as List<String>?;
user.hobbies = hobbies ?? [];
}
if (updates.containsKey('isActive')) {
final isActive = updates['isActive'] as bool?;
user.isActive = isActive ?? true;
}
await isar.writeTxn(() async {
await isar.users.put(user);
});
return Result.success(user);
} on IsarError catch (e) {
return Result.error('Database error: ${e.message}');
} catch (e) {
return Result.error('Unexpected error: $e');
}
}
// Safe batch operations
Future<Result<BatchResult, String>> batchOperationSafely(
List<BatchOperation> operations,
) async {
try {
final results = BatchResult();
await isar.writeTxn(() async {
for (final operation in operations) {
try {
switch (operation.type) {
case BatchOperationType.create:
final user = operation.data as User;
await isar.users.put(user);
results.successful.add(operation);
break;
case BatchOperationType.update:
final userId = operation.id!;
final updates = operation.data as Map<String, dynamic>;
final user = await isar.users.get(userId);
if (user != null) {
_applyUpdates(user, updates);
await isar.users.put(user);
results.successful.add(operation);
} else {
results.failed.add(FailedOperation(operation, 'User not found'));
}
break;
case BatchOperationType.delete:
final userId = operation.id!;
final deleted = await isar.users.delete(userId);
if (deleted) {
results.successful.add(operation);
} else {
results.failed.add(FailedOperation(operation, 'User not found'));
}
break;
}
} catch (e) {
results.failed.add(FailedOperation(operation, e.toString()));
}
}
});
return Result.success(results);
} on IsarError catch (e) {
return Result.error('Database transaction failed: ${e.message}');
} catch (e) {
return Result.error('Batch operation failed: $e');
}
}
// Database integrity check
Future<Result<IntegrityReport, String>> checkDatabaseIntegrity() async {
try {
final report = IntegrityReport();
// User data integrity check
final users = await isar.users.where().findAll();
for (final user in users) {
if (user.name.trim().isEmpty) {
report.errors.add('User ${user.id}: Empty name');
}
if (!_isValidEmail(user.email)) {
report.errors.add('User ${user.id}: Invalid email format');
}
if (user.age < 0 || user.age > 150) {
report.errors.add('User ${user.id}: Invalid age ${user.age}');
}
}
// Duplicate check
final emailCounts = <String, int>{};
for (final user in users) {
emailCounts[user.email] = (emailCounts[user.email] ?? 0) + 1;
}
for (final entry in emailCounts.entries) {
if (entry.value > 1) {
report.errors.add('Duplicate email: ${entry.key} (${entry.value} occurrences)');
}
}
report.totalUsers = users.length;
report.checkCompleted = DateTime.now();
return Result.success(report);
} catch (e) {
return Result.error('Integrity check failed: $e');
}
}
// Private helper methods
String? _validateUserInput(String name, String email, int age) {
if (name.trim().isEmpty) {
return 'Name cannot be empty';
}
if (!_isValidEmail(email)) {
return 'Invalid email format';
}
if (age < 0 || age > 150) {
return 'Age must be between 0 and 150';
}
return null;
}
bool _isValidEmail(String email) {
return RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(email);
}
void _applyUpdates(User user, Map<String, dynamic> updates) {
updates.forEach((key, value) {
switch (key) {
case 'name':
user.name = value as String;
break;
case 'email':
user.email = value as String;
break;
case 'age':
user.age = value as int;
break;
case 'hobbies':
user.hobbies = List<String>.from(value as List);
break;
case 'isActive':
user.isActive = value as bool;
break;
}
});
}
}
// Result type and error handling classes
class Result<T, E> {
final T? _value;
final E? _error;
const Result._(this._value, this._error);
factory Result.success(T value) => Result._(value, null);
factory Result.error(E error) => Result._(null, error);
bool get isSuccess => _value != null;
bool get isError => _error != null;
T get value => _value!;
E get error => _error!;
R fold<R>(R Function(T value) onSuccess, R Function(E error) onError) {
if (isSuccess) {
return onSuccess(value);
} else {
return onError(error);
}
}
}
// Batch operation related classes
enum BatchOperationType { create, update, delete }
class BatchOperation {
final BatchOperationType type;
final int? id;
final dynamic data;
BatchOperation.create(this.data) : type = BatchOperationType.create, id = null;
BatchOperation.update(this.id, this.data) : type = BatchOperationType.update;
BatchOperation.delete(this.id) : type = BatchOperationType.delete, data = null;
}
class BatchResult {
final List<BatchOperation> successful = [];
final List<FailedOperation> failed = [];
}
class FailedOperation {
final BatchOperation operation;
final String error;
FailedOperation(this.operation, this.error);
}
class IntegrityReport {
int totalUsers = 0;
final List<String> errors = [];
DateTime? checkCompleted;
bool get hasErrors => errors.isNotEmpty;
int get errorCount => errors.length;
}