Utilizing ObjectBox in Flutter

Utilizing ObjectBox in Flutter

Beginner's guide to using ObjectBox in a Flutter Application

ObjectBox stands out as a high-performance and lightweight NoSQL embedded database crafted for Flutter applications. Tailored to seamlessly integrate with Flutter, ObjectBox provides developers with a reactive API and advanced features such as indexes, relationships, and migrations. This article will guide you through the process of incorporating ObjectBox into a Flutter project and implementing fundamental CRUD (Create, Read, Update, Delete) operations using the ObjectBox database.

Setting Up a Flutter Project with ObjectBox

To get started, create a new Flutter project or utilize an existing one. Once your project is set up, follow these simplified steps to integrate ObjectBox:

  1. Open pubspec.yaml and add the ObjectBox dependency:
dependencies:
  objectbox: ^2.4.0

Ensure that the version number matches the desired ObjectBox version.

  1. Run the following command to fetch the dependencies:
flutter pub get
  1. Set up ObjectBox in your Flutter app:

Create a new Dart file, for example, objectbox_setup.dart, and initialize ObjectBox within it:

import 'package:objectbox/objectbox.dart';

final store = await openStore();

Make sure to import the necessary packages and use the openStore function to initialize ObjectBox. This step sets up the ObjectBox database for your Flutter project.

Implementing CRUD Operations with ObjectBox

Now that ObjectBox is integrated into your Flutter project, let's proceed with implementing CRUD operations.

  1. Creating Data Model

Define a data model for your entities. For example, let's create a Task model:

@Entity()
class Task {
  int? id;

  late String name;
  late DateTime createdAt;

  Task(this.name) : createdAt = DateTime.now();
}
  1. Adding CRUD Methods

In a separate Dart file, create methods for CRUD operations:

import 'package:objectbox/objectbox.dart';

class TaskRepository {
  final Store _store;
  late final Box<Task> _tasks;

  TaskRepository(this._store) : _tasks = _store.box();

  Future<void> addTask(String name) async {
    await _store.runInTransaction(() async {
      await _tasks.put(Task(name));
    });
  }

  Future<List<Task>> getAllTasks() async {
    return _tasks.getAll();
  }

  Future<void> updateTask(Task task) async {
    await _store.runInTransaction(() async {
      await _tasks.put(task);
    });
  }

  Future<void> deleteTask(Task task) async {
    await _store.runInTransaction(() async {
      await _tasks.remove(task.id);
    });
  }
}
  1. Using CRUD Methods in Flutter UI

Integrate these CRUD methods into your Flutter UI:

import 'package:flutter/material.dart';

void main() async {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TaskListScreen(),
    );
  }
}

class TaskListScreen extends StatefulWidget {
  @override
  _TaskListScreenState createState() => _TaskListScreenState();
}

class _TaskListScreenState extends State<TaskListScreen> {
  final TaskRepository _taskRepository = TaskRepository(store);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ObjectBox CRUD Example'),
      ),
      body: FutureBuilder<List<Task>>(
        future: _taskRepository.getAllTasks(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            final tasks = snapshot.data ?? [];
            return ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (context, index) {
                final task = tasks[index];
                return ListTile(
                  title: Text(task.name),
                  subtitle: Text('Created at: ${task.createdAt}'),
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: () => _deleteTask(task),
                  ),
                );
              },
            );
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _addTask(),
        child: Icon(Icons.add),
      ),
    );
  }

  Future<void> _addTask() async {
    await _taskRepository.addTask('New Task');
    setState(() {});
  }

  Future<void> _deleteTask(Task task) async {
    await _taskRepository.deleteTask(task);
    setState(() {});
  }
}

ObjectBox offers advanced features beyond basic CRUD operations, making it a robust choice for local data management in Flutter applications.

Advanced Features and Best Practices:

Reactive Queries:

ObjectBox supports reactive queries, allowing your UI to automatically update in response to changes in the database. This means that any modifications to the ObjectBox collection will be reflected in the UI without manual intervention.

// Reactive query example
final reactiveTasks = _tasks.query().build().watch();

Indexing:

You can define indexes on specific properties of your models to optimize query performance. Indexing is crucial for speeding up read operations, especially when dealing with large datasets.

@Entity()
class Task {
  @Id(assignable: true)
  int? id;

  @Index()
  late String name;

  late DateTime createdAt;
}

Relations:

ObjectBox supports relationships between different models. You can create links between entities, enabling you to model complex data structures more effectively.

@Entity()
class Project {
  int? id;

  late String name;

  @Backlink(to: 'project')
  final tasks = ToMany<Task>();
}

Custom Queries:

ObjectBox provides a flexible query API, allowing you to perform complex queries with ease. You can filter, sort, and paginate your data efficiently.

// Custom query example
final highPriorityTasks = _tasks.query().greater(Task_.priority, 3).build().find();

Migrations:

ObjectBox supports schema migrations, allowing you to evolve your data model over time. You can define migration steps to handle changes to the schema as your application evolves.

final store = await openStore(
  directory: getApplicationDocumentsDirectory().path,
  model: getObjectBoxModel(),
  onVersionChanged: (store, oldVersion, newVersion) {
    if (oldVersion == 1) {
      // Perform migration steps for version 1 to version 2
    }
  },
);

Transactions:

ObjectBox supports transactions, allowing you to execute a series of operations atomically. This is essential for maintaining data integrity, especially in scenarios where multiple operations need to be executed as a single unit.

await _store.runInTransaction(() async {
  await _tasks.put(Task('New Task'));
  await _tasks.put(Task('Another Task'));
});

Batch Operations:

ObjectBox allows you to perform batch operations efficiently. You can insert, update, or delete multiple items in a single transaction, reducing the overhead of multiple transactions.

await _tasks.runInTransaction(() async {
  await _tasks.putMany([
    Task('Task 1'),
    Task('Task 2'),
    Task('Task 3'),
  ]);
});

Additional Resources:

For more in-depth information and to explore advanced features, refer to ObjectBox's official documentation at https://pub.dev/packages/objectbox or https://docs.objectbox.io

Did you find this article valuable?

Support Atuoha Anthony by becoming a sponsor. Any amount is appreciated!