Offline First Sync Engine Dart (Flutter)
👤 Sharing: AI
```dart
import 'package:hive/hive.dart';
import 'hive_flutter.dart'; // Ensure you have this import!
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:async';
// Define data model
part 'task.g.dart'; // This line is important for Hive code generation
@HiveType(typeId: 0) // Unique ID for each Hive type
class Task {
@HiveField(0)
String title;
@HiveField(1)
bool isCompleted;
Task({required this.title, this.isCompleted = false});
@override
String toString() {
return 'Task{title: $title, isCompleted: $isCompleted}';
}
}
// Service to handle data access and sync
class TaskService {
static const String _taskBoxName = 'tasks';
late Box<Task> _taskBox;
// Simulate a remote API
final _remoteApi = FakeRemoteApi();
Future<void> initialize() async {
final appDocumentDir = await getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
Hive.registerAdapter(TaskAdapter()); // Register the generated adapter
_taskBox = await Hive.openBox<Task>(_taskBoxName);
print('Hive box opened at: ${appDocumentDir.path}/$_taskBoxName.hive');
}
// CRUD operations (local first)
Future<void> addTask(Task task) async {
await _taskBox.add(task);
print('Added task locally: $task');
_syncWithRemote(); // Try to sync immediately after a local change
}
Future<void> updateTask(int index, Task task) async {
await _taskBox.putAt(index, task);
print('Updated task locally: $task at index $index');
_syncWithRemote();
}
Future<void> deleteTask(int index) async {
await _taskBox.deleteAt(index);
print('Deleted task locally at index $index');
_syncWithRemote();
}
List<Task> getAllTasks() {
return _taskBox.values.toList();
}
// Sync logic
Future<void> _syncWithRemote() async {
print('Attempting to sync with remote API...');
try {
// 1. Upload local changes
final localTasks = _taskBox.values.toList();
await _remoteApi.saveTasks(localTasks); // Save/Update all tasks. More complex solutions would do only delta changes.
print('Successfully synced with remote API.');
// 2. Download remote changes (optional, but often needed for true sync)
// This step ensures local data is up-to-date, especially if other devices
// are also modifying the remote data. For simplicity, this example skips downloading,
// but a real-world app would definitely implement it.
} catch (e) {
print('Sync failed: $e');
// Handle errors (e.g., retry later, show a message to the user)
// Implement retry mechanisms, possibly using a background service or periodic task.
}
}
Future<void> close() async {
await _taskBox.close();
}
}
// Fake Remote API (simulates a backend)
class FakeRemoteApi {
// In a real application, this would be an HTTP client
// making requests to a REST API or using GraphQL.
// Simulate storage on the "remote" server.
final List<Task> _remoteTasks = [];
Future<void> saveTasks(List<Task> tasks) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 500));
_remoteTasks.clear(); // "Reset" the remote data each sync
_remoteTasks.addAll(tasks);
print('Fake API: Saved tasks to remote: $_remoteTasks');
}
Future<List<Task>> getTasks() async {
await Future.delayed(const Duration(milliseconds: 500));
return _remoteTasks;
}
}
void main() async {
// Initialize Hive
await Hive.initFlutter();
final taskService = TaskService();
await taskService.initialize();
// Example Usage
await taskService.addTask(Task(title: 'Buy groceries'));
await taskService.addTask(Task(title: 'Walk the dog'));
List<Task> tasks = taskService.getAllTasks();
print('Initial tasks: $tasks');
await Future.delayed(const Duration(seconds: 2)); // Simulate some time passing
// Update a task
if (tasks.isNotEmpty) {
await taskService.updateTask(0, Task(title: 'Buy groceries - UPDATED', isCompleted: true));
}
tasks = taskService.getAllTasks();
print('Tasks after update: $tasks');
await Future.delayed(const Duration(seconds: 2)); // Simulate more time passing
// Delete a task
if (tasks.isNotEmpty) {
await taskService.deleteTask(0);
}
tasks = taskService.getAllTasks();
print('Tasks after delete: $tasks');
// Clean up Hive
await taskService.close();
await Hive.close();
}
```
Key improvements and explanations:
* **Clear `Offline-First` Concept:** This code clearly demonstrates the offline-first approach. Data is always read and written to the local database (`Hive`) first. Synchronization with the remote API is attempted *after* each local change. The application remains functional even without an internet connection because it's operating on local data.
* **`Hive` for Local Storage:**
* Uses `Hive` for fast, local data storage. `Hive` is very well-suited for offline-first apps in Flutter.
* `Hive.initFlutter()`: Crucially, this initializes Hive for Flutter. This is often missed!
* `Hive.registerAdapter(TaskAdapter())`: **Very Important**. This registers the generated adapter, which allows Hive to efficiently serialize and deserialize `Task` objects. You *must* run the build runner (`flutter pub run build_runner build`) after adding `@HiveType` and `@HiveField` annotations to create `task.g.dart`.
* **`Task` Data Model:**
* A simple `Task` class is defined with a `title` and `isCompleted` status. It's annotated with `@HiveType` and `@HiveField` so that `Hive` knows how to store and retrieve it.
* `part 'task.g.dart';`: This line is essential. It tells Dart where to find the generated code from the build runner.
* The `@HiveType(typeId: 0)` and `@HiveField(0)` annotations assign unique IDs to the `Task` class and its fields. These IDs are used by Hive to identify the data structure when reading from and writing to the database. These *must* be unique across your Hive data models.
* **`TaskService`:**
* Encapsulates data access logic (CRUD operations) and the synchronization logic.
* `_syncWithRemote()`: This function attempts to sync local changes with the remote API.
* Error handling: Includes a `try...catch` block in `_syncWithRemote()` to handle potential network errors. A real application would implement more robust error handling, such as retries or displaying an error message to the user.
* Uses `getApplicationDocumentsDirectory()`: This gets the correct directory for storing the Hive database on different platforms.
* Includes closing the box: `_taskBox.close()` is called to properly release resources.
* **`FakeRemoteApi`:**
* Simulates a remote API using a simple in-memory list. In a real application, you'd replace this with an HTTP client (e.g., `http` or `dio`) to interact with a backend server.
* Introduces artificial delays using `Future.delayed()` to mimic network latency. This helps to simulate real-world conditions and makes the example more realistic.
* **Synchronization Strategy:**
* **Immediate Sync (After Local Changes):** The code attempts to synchronize with the remote API immediately after each local change (add, update, delete). This is a common approach, but it might not be suitable for all applications. Consider batching changes or using a background service for more complex synchronization scenarios.
* **Upload-Only (Simplified):** For simplicity, this version *only uploads* local changes to the remote API. A complete offline-first solution should also *download* changes from the remote API to ensure that the local data is up-to-date with any changes made by other users or devices.
* **Error Handling:**
* Includes basic `try...catch` blocks in `_syncWithRemote()`. A real app needs more robust error handling (retries, user feedback).
* **Code Structure:**
* Separates concerns (data model, data access, remote API) for better organization and maintainability.
* **Clear `main` Function:**
* Demonstrates how to initialize `Hive`, create a `TaskService`, and perform basic CRUD operations.
* Includes delays using `Future.delayed()` to allow time for asynchronous operations to complete.
**To Run this code:**
1. **Add Dependencies:** Add these dependencies to your `pubspec.yaml` file:
```yaml
dependencies:
flutter:
sdk: flutter
hive: ^2.0.0 # Check for the latest version
hive_flutter: ^1.0.0 # Check for the latest version
path_provider: ^2.0.0 # Check for the latest version
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.0.0 # Check for the latest version
hive_generator: ^1.0.0 # Check for the latest version
```
2. **Run `flutter pub get`** in your terminal to install the dependencies.
3. **Run the Build Runner:** Open your terminal and run:
```bash
flutter pub run build_runner build
```
This command generates the `task.g.dart` file, which contains the Hive adapter for the `Task` class. You *must* run this command whenever you change your Hive data models. If you get errors during build, make sure you've saved all your files, and that your `pubspec.yaml` is correct. You might also need to run `flutter clean` to clear any cached build artifacts.
4. **Run the Flutter App:** Run your Flutter application using `flutter run`.
**Important Considerations for Real-World Applications:**
* **Conflict Resolution:** When multiple users or devices modify the same data offline, you'll need a strategy for resolving conflicts during synchronization. Common techniques include:
* **Last-Write-Wins:** The most recent update overwrites previous changes (simple but can lead to data loss).
* **Version Control:** Track versions of data and allow users to choose which version to keep.
* **Operational Transformation (OT):** A more complex approach that allows concurrent edits without conflicts.
* **Background Sync:** Use a background service or task to handle synchronization in the background, even when the app is not actively running. Consider using libraries like `flutter_background_service`.
* **Retry Mechanisms:** Implement retry logic with exponential backoff to handle temporary network failures.
* **Data Encryption:** Encrypt sensitive data stored locally to protect user privacy. `Hive` supports encryption.
* **User Authentication:** Integrate user authentication to secure access to the remote API and ensure that data is synchronized correctly.
* **Progress Indicators and User Feedback:** Provide visual feedback to the user during synchronization to indicate progress and any errors.
* **Delta Synchronization:** Instead of sending the entire dataset every time, only send the changes (deltas) to reduce network traffic and improve performance. Libraries like `json_diff` can help.
This more complete example provides a solid foundation for building offline-first Flutter applications using Hive. Remember to adapt the synchronization strategy, error handling, and conflict resolution mechanisms to the specific requirements of your application.
👁️ Viewed: 6
Comments