ObjectBox
ObjectBox is a high-performance lightweight NoSQL database. Optimized for mobile and IoT devices with an object-oriented approach, it achieves up to 70x faster performance than other options for create and update operations. With Dart-native type-safe API, it provides excellent development experience for Flutter applications.
Library
ObjectBox
Overview
ObjectBox is a high-performance lightweight NoSQL database. Optimized for mobile and IoT devices with an object-oriented approach, it achieves up to 70x faster performance than other options for create and update operations. With Dart-native type-safe API, it provides excellent development experience for Flutter applications.
Details
ObjectBox 2025 edition is established as the optimal solution for performance-focused projects. Adoption is increasing in enterprise-level offline-first applications, particularly showing strength in mobile applications requiring real-time capabilities. While equipped with enterprise-grade features such as vector search, time-series data, and sync functionality, it operates efficiently even on resource-constrained devices.
Key Features
- Ultra-high Performance: Up to 70x faster than other solutions
- Lightweight Design: Minimal memory footprint
- Type-safe API: Complete type support with Dart-native
- Offline-first: Local-first architecture
- ACID Compliant: Transaction integrity guarantee
- Data Sync: Device-to-device sync via ObjectBox Sync
Pros and Cons
Pros
- Outstanding performance (especially write operations)
- Minimal boilerplate code
- Excellent type safety and code generation
- Battery-efficient design
- Real-time queries and reactive API
- Ideal for edge computing
Cons
- Commercial license required (open-source version has limitations)
- Learning curve for developers familiar with SQL-based queries
- Limited advanced query features compared to relational databases
- Relatively small community due to being newer
- Complex migration strategies
References
Examples
Basic Setup
# pubspec.yaml
dependencies:
objectbox: ^2.0.0
objectbox_flutter_libs: ^2.0.0
dev_dependencies:
build_runner: ^2.0.0
objectbox_generator: ^2.0.0
// lib/models/user.dart
import 'package:objectbox/objectbox.dart';
@Entity()
class User {
@Id()
int id = 0;
String name;
String email;
int? age;
@Property(type: PropertyType.date)
DateTime createdAt;
// Relations
final posts = ToMany<Post>();
User({
required this.name,
required this.email,
this.age,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
}
@Entity()
class Post {
@Id()
int id = 0;
String title;
String content;
@Property(type: PropertyType.date)
DateTime createdAt;
int viewCount;
// Reverse relation
final author = ToOne<User>();
Post({
required this.title,
required this.content,
this.viewCount = 0,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
}
// Run code generation
// flutter pub run build_runner build
Basic CRUD Operations
import 'package:objectbox/objectbox.dart';
import 'objectbox.g.dart'; // Generated file
class ObjectBoxDatabase {
late final Store store;
late final Box<User> userBox;
late final Box<Post> postBox;
ObjectBoxDatabase._create(this.store) {
userBox = Box<User>(store);
postBox = Box<Post>(store);
}
static Future<ObjectBoxDatabase> create() async {
final store = await openStore();
return ObjectBoxDatabase._create(store);
}
// CREATE - Create new records
int createUser(User user) {
return userBox.put(user);
}
// Batch creation
List<int> createUsers(List<User> users) {
return userBox.putMany(users);
}
// READ - Read records
User? getUserById(int id) {
return userBox.get(id);
}
List<User> getAllUsers() {
return userBox.getAll();
}
// Query builder
List<User> searchUsers({
String? nameContains,
int? minAge,
int? maxAge,
}) {
final queryBuilder = userBox.query();
if (nameContains != null) {
queryBuilder.contains(User_.name, nameContains);
}
if (minAge != null) {
queryBuilder.greaterOrEqual(User_.age, minAge);
}
if (maxAge != null) {
queryBuilder.lessOrEqual(User_.age, maxAge);
}
return queryBuilder
.order(User_.name)
.build()
.find();
}
// UPDATE - Update records
void updateUser(User user) {
userBox.put(user);
}
// Partial update
void updateUserAge(int userId, int newAge) {
final user = userBox.get(userId);
if (user != null) {
user.age = newAge;
userBox.put(user);
}
}
// DELETE - Delete records
bool deleteUser(int id) {
return userBox.remove(id);
}
// Multiple deletion
int deleteUsers(List<int> ids) {
return userBox.removeMany(ids);
}
// Conditional deletion
int deleteOldUsers(DateTime before) {
final query = userBox.query(
User_.createdAt.lessThan(before.millisecondsSinceEpoch)
).build();
final users = query.find();
query.close();
return userBox.removeMany(users.map((u) => u.id).toList());
}
void close() {
store.close();
}
}
Advanced Features
// Relation operations
class BlogRepository {
final ObjectBoxDatabase db;
BlogRepository(this.db);
// Create post and associate with author
int createPost(Post post, int authorId) {
final author = db.userBox.get(authorId);
if (author == null) {
throw Exception('Author not found');
}
post.author.target = author;
return db.postBox.put(post);
}
// Get all posts for a user
List<Post> getUserPosts(int userId) {
final user = db.userBox.get(userId);
return user?.posts.toList() ?? [];
}
// Get posts with author information
List<PostWithAuthor> getPostsWithAuthors() {
final posts = db.postBox.getAll();
return posts.map((post) {
return PostWithAuthor(
post: post,
authorName: post.author.target?.name ?? 'Unknown',
);
}).toList();
}
// Transaction processing
void transferPosts(int fromUserId, int toUserId) {
db.store.runInTransaction(TxMode.write, () {
final fromUser = db.userBox.get(fromUserId);
final toUser = db.userBox.get(toUserId);
if (fromUser == null || toUser == null) {
throw Exception('User not found');
}
// Move all posts to new user
final posts = fromUser.posts.toList();
for (final post in posts) {
post.author.target = toUser;
db.postBox.put(post);
}
});
}
}
// Reactive queries
class ReactiveQueries {
final ObjectBoxDatabase db;
ReactiveQueries(this.db);
// Watch user list as stream
Stream<List<User>> watchUsers() {
return db.userBox
.query()
.order(User_.name)
.watch(triggerImmediately: true)
.map((query) => query.find());
}
// Watch users with specific conditions
Stream<List<User>> watchActiveUsers() {
final thirtyDaysAgo = DateTime.now().subtract(Duration(days: 30));
return db.userBox
.query(User_.createdAt.greaterThan(thirtyDaysAgo.millisecondsSinceEpoch))
.watch()
.map((query) => query.find());
}
// Watch post count changes
Stream<int> watchPostCount() {
return db.postBox
.query()
.watch()
.map((query) => query.count());
}
}
// Performance optimization
class PerformanceOptimization {
final ObjectBoxDatabase db;
PerformanceOptimization(this.db);
// Indexed queries
List<User> fastEmailSearch(String email) {
// Speed up by adding @Index() annotation to email field
return db.userBox
.query(User_.email.equals(email))
.build()
.find();
}
// Pagination
List<User> getPaginatedUsers({
required int page,
required int pageSize,
}) {
final offset = (page - 1) * pageSize;
return db.userBox
.query()
.order(User_.name)
.build()
.find()
.skip(offset)
.take(pageSize)
.toList();
}
// Aggregate functions
UserStatistics getUserStatistics() {
final query = db.userBox.query();
final totalUsers = query.build().count();
final ageQuery = query.build();
final ages = ageQuery.property(User_.age).find();
ageQuery.close();
final validAges = ages.whereType<int>().toList();
return UserStatistics(
totalUsers: totalUsers,
averageAge: validAges.isEmpty
? 0
: validAges.reduce((a, b) => a + b) / validAges.length,
maxAge: validAges.isEmpty ? 0 : validAges.reduce((a, b) => a > b ? a : b),
minAge: validAges.isEmpty ? 0 : validAges.reduce((a, b) => a < b ? a : b),
);
}
}
// DTO classes
class PostWithAuthor {
final Post post;
final String authorName;
PostWithAuthor({
required this.post,
required this.authorName,
});
}
class UserStatistics {
final int totalUsers;
final double averageAge;
final int maxAge;
final int minAge;
UserStatistics({
required this.totalUsers,
required this.averageAge,
required this.maxAge,
required this.minAge,
});
}
Practical Example
// Flutter application usage example
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider definitions
final objectBoxProvider = Provider<ObjectBoxDatabase>((ref) {
throw UnimplementedError('Initialize in main()');
});
final userListProvider = StreamProvider<List<User>>((ref) {
final db = ref.watch(objectBoxProvider);
return db.userBox
.query()
.order(User_.name)
.watch(triggerImmediately: true)
.map((query) => query.find());
});
// Main function
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final objectBox = await ObjectBoxDatabase.create();
runApp(
ProviderScope(
overrides: [
objectBoxProvider.overrideWithValue(objectBox),
],
child: MyApp(),
),
);
}
// User list screen
class UserListScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(userListProvider);
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: usersAsync.when(
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Text('Age: ${user.age ?? "N/A"}'),
onTap: () => _showUserDetails(context, user),
);
},
),
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addUser(context, ref),
child: Icon(Icons.add),
),
);
}
void _showUserDetails(BuildContext context, User user) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => UserDetailScreen(userId: user.id),
),
);
}
void _addUser(BuildContext context, WidgetRef ref) {
showDialog(
context: context,
builder: (context) => AddUserDialog(),
);
}
}
// Add user dialog
class AddUserDialog extends ConsumerStatefulWidget {
@override
_AddUserDialogState createState() => _AddUserDialogState();
}
class _AddUserDialogState extends ConsumerState<AddUserDialog> {
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _ageController = TextEditingController();
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Add User'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Name'),
),
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
),
TextField(
controller: _ageController,
decoration: InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: _saveUser,
child: Text('Save'),
),
],
);
}
void _saveUser() {
final db = ref.read(objectBoxProvider);
final user = User(
name: _nameController.text,
email: _emailController.text,
age: int.tryParse(_ageController.text),
);
db.createUser(user);
Navigator.pop(context);
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_ageController.dispose();
super.dispose();
}
}
// User detail screen
class UserDetailScreen extends ConsumerWidget {
final int userId;
UserDetailScreen({required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final db = ref.watch(objectBoxProvider);
final user = db.getUserById(userId);
if (user == null) {
return Scaffold(
appBar: AppBar(title: Text('User Not Found')),
body: Center(child: Text('User not found')),
);
}
return Scaffold(
appBar: AppBar(
title: Text(user.name),
actions: [
IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteUser(context, ref),
),
],
),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Email: ${user.email}', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
Text('Age: ${user.age ?? "Not specified"}', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
Text('Created: ${user.createdAt}', style: TextStyle(fontSize: 14)),
SizedBox(height: 24),
Text('Posts (${user.posts.length})', style: Theme.of(context).textTheme.headlineSmall),
Expanded(
child: ListView.builder(
itemCount: user.posts.length,
itemBuilder: (context, index) {
final post = user.posts[index];
return Card(
child: ListTile(
title: Text(post.title),
subtitle: Text(post.content, maxLines: 2, overflow: TextOverflow.ellipsis),
trailing: Text('Views: ${post.viewCount}'),
),
);
},
),
),
],
),
),
);
}
void _deleteUser(BuildContext context, WidgetRef ref) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Delete User'),
content: Text('Are you sure you want to delete this user?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () {
final db = ref.read(objectBoxProvider);
db.deleteUser(userId);
Navigator.of(context)..pop()..pop();
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: Text('Delete'),
),
],
),
);
}
}