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.

NoSQLDartHigh-PerformanceLightweightMobileIoTObject-Database

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'),
          ),
        ],
      ),
    );
  }
}