When building Flutter applications that require fast and efficient local storage, ObjectBox stands out as a compelling solution. ObjectBox is a super-fast, lightweight, and embedded NoSQL database designed for mobile and IoT devices. This blog post explores how to integrate and use ObjectBox in Flutter for robust local data persistence.
What is ObjectBox?
ObjectBox is a modern NoSQL database built for performance. Its key features include:
- Speed: Extremely fast read and write operations.
- Zero-copy objects: Reduces memory footprint and improves performance by allowing direct access to objects.
- Simple API: Easy to learn and use.
- Multiplatform: Supports Android, iOS, Linux, macOS, and Windows, making it versatile for cross-platform development.
- Strong Relationships: Efficient management of object relationships.
- Full ACID semantics: Ensures data integrity with ACID (Atomicity, Consistency, Isolation, Durability) compliance.
Why Use ObjectBox in Flutter?
- Performance-Critical Applications: Ideal for apps where data access speed is essential.
- Offline Functionality: Perfect for apps requiring robust offline data handling.
- Real-Time Data: Suitable for real-time applications where immediate data availability is important.
- Complex Data Structures: Well-suited for apps dealing with complex and interconnected data models.
How to Integrate ObjectBox into a Flutter Application
Step 1: Add Dependencies
First, add the necessary ObjectBox dependencies to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
objectbox: ^1.7.1 # Use the latest version
path_provider: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
objectbox_generator: ^1.7.1 # Use the latest version
build_runner: ^2.0.0
Here’s what these dependencies do:
objectbox
: The core ObjectBox Flutter package.path_provider
: Helps determine file paths for storing the database.objectbox_generator
: Generates Dart code from your ObjectBox entity definitions.build_runner
: A general-purpose code generation tool.
After adding the dependencies, run:
flutter pub get
Step 2: Define Your Data Model
Create a Dart class representing your data model. Annotate it with @Entity()
to mark it as an ObjectBox entity. Use @Id()
to define the primary key.
import 'package:objectbox/objectbox.dart';
@Entity()
class Task {
@Id()
int id = 0;
String description;
bool isCompleted;
Task({required this.description, this.isCompleted = false});
}
Step 3: Generate ObjectBox Code
Run the ObjectBox code generator to create the necessary code for your data model. Execute the following command in your terminal:
flutter pub run build_runner build --delete-conflicting-outputs
This command generates the objectbox.g.dart
file, which contains the necessary ObjectBox code for your entities.
Step 4: Initialize ObjectBox
Initialize ObjectBox in your app. This involves creating an ObjectBox
instance and opening a box for each entity you want to store.
import 'package:objectbox/objectbox.dart';
import 'objectbox.g.dart'; // Import the generated file
import 'package:path_provider/path_provider.dart';
import 'dart:io';
class ObjectBoxService {
late final Store store;
ObjectBoxService._create(this.store) {
// Add any additional setup here. For example, initial data seeding
}
static Future create() async {
final docsDir = await getApplicationDocumentsDirectory();
final store = await openStore(directory: pJoin(docsDir.path, "obx-example"));
return ObjectBoxService._create(store);
}
}
Step 5: Use ObjectBox to Store and Retrieve Data
Now, use the Box
instance to perform CRUD (Create, Read, Update, Delete) operations.
import 'package:objectbox/objectbox.dart';
import 'objectbox.g.dart';
class TaskService {
final Box _taskBox;
TaskService(Store store) : _taskBox = Box(store);
// Create
int addTask(String description) {
final task = Task(description: description);
return _taskBox.put(task);
}
// Read
Task? getTask(int id) {
return _taskBox.get(id);
}
List getAllTasks() {
return _taskBox.getAll();
}
// Update
void updateTask(Task task) {
_taskBox.put(task);
}
// Delete
bool deleteTask(int id) {
return _taskBox.remove(id);
}
}
Complete Example: Implementing a Simple Task List
Let’s combine everything to create a simple task list application.
Step 1: Set Up the UI
import 'package:flutter/material.dart';
import 'objectbox.dart';
import 'task.dart';
class TaskListScreen extends StatefulWidget {
final Store store;
const TaskListScreen({Key? key, required this.store}) : super(key: key);
@override
_TaskListScreenState createState() => _TaskListScreenState();
}
class _TaskListScreenState extends State {
late final TaskService _taskService;
List _tasks = [];
@override
void initState() {
super.initState();
_taskService = TaskService(widget.store);
_loadTasks();
}
Future _loadTasks() async {
setState(() {
_tasks = _taskService.getAllTasks();
});
}
void _addTask(String description) {
_taskService.addTask(description);
_loadTasks();
}
void _toggleTaskCompletion(Task task) {
task.isCompleted = !task.isCompleted;
_taskService.updateTask(task);
_loadTasks();
}
void _deleteTask(int id) {
_taskService.deleteTask(id);
_loadTasks();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ObjectBox Task List')),
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
final task = _tasks[index];
return ListTile(
title: Text(task.description),
leading: Checkbox(
value: task.isCompleted,
onChanged: (bool? newValue) {
_toggleTaskCompletion(task);
},
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_deleteTask(task.id);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showAddTaskDialog(context);
},
child: const Icon(Icons.add),
),
);
}
Future _showAddTaskDialog(BuildContext context) async {
TextEditingController descriptionController = TextEditingController();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add New Task'),
content: TextField(
controller: descriptionController,
decoration: const InputDecoration(hintText: 'Task description'),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
_addTask(descriptionController.text);
Navigator.pop(context);
},
child: const Text('Add'),
),
],
);
},
);
}
}
Step 2: Integration in main.dart
import 'package:flutter/material.dart';
import 'objectbox.dart';
import 'objectbox_service.dart';
import 'task_list_screen.dart';
late ObjectBoxService objectBoxService;
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
objectBoxService = await ObjectBoxService.create();
runApp(MyApp(store: objectBoxService.store));
}
class MyApp extends StatelessWidget {
final Store store;
const MyApp({Key? key, required this.store}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ObjectBox Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: TaskListScreen(store: store),
);
}
}
Best Practices
- Background Processing: For complex data operations, use background isolates to prevent UI blocking.
- Error Handling: Implement robust error handling for database operations to manage exceptions and ensure data integrity.
- Database Management: Regularly manage your database (e.g., through migrations) to optimize performance and maintain compatibility with newer versions.
Conclusion
ObjectBox offers a powerful and efficient solution for local storage in Flutter applications, providing unmatched speed and performance. By following this guide, you can integrate ObjectBox into your Flutter project and leverage its capabilities to build fast, reliable, and offline-capable applications. Embrace ObjectBox to enhance the performance and user experience of your Flutter apps.