Offline-First App di Flutter dengan Hive: Sinkronisasi Data Lokal dan Remote
Aplikasi mobile sering mengalami kondisi koneksi yang tidak stabil. Pengguna mengharapkan data tetap tersedia meskipun sedang offline. Hive adalah solusi penyimpanan lokal yang ringan dan cepat untuk Flutter, dirancang khusus untuk performa yang lebih baik dibandingkan SQLite atau SharedPreferences.
Keunggulan utama Hive terletak pada zero-latency access. Data tersimpan dalam memory setelah box dibuka, sehingga query tidak memblokir UI thread. Kemampuan ini sangat penting untuk aplikasi yang membutuhkan respons cepat seperti todo list, note-taking, atau inventory management. Hive juga mendukung enkripsi built-in dan type adapters untuk object serialization. Kita dapat menyimpan object Dart langsung tanpa mengkonversi ke JSON string terlebih dahulu, mengurangi overhead parsing.
Memahami Hive dan Keunggulannya untuk Offline-First
Hive adalah key-value database yang ditulis dalam pure Dart. The library tidak memerlukan native dependencies yang kompleks, sehingga setup lebih sederhana dibandingkan SQLite. Kecepatan read/write Hive mendekati level native karena data diserialisasi dalam binary format yang efisien.
Arsitektur offline-first membutuhkan dua sumber data yang selaras: lokal sebagai sumber utama dan remote sebagai backup serta sharing mechanism. Hive menempati posisi ideal di layer ini karena ukuran library yang kecil dan API yang straightforward. The tool juga compatible dengan semua platform yang didukung Flutter, termasuk iOS, Android, macOS, Windows, dan Linux.
Setup Hive di Project Flutter
Integrasi Hive dimulai dengan menambahkan dependency di pubspec.yaml. Setelah itu, kita perlu menginisialisasi Hive di main.dart sebelum aplikasi berjalan.
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
dio: ^5.4.0
connectivity_plus: ^5.0.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.8// main.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
// Register adapter sebelum membuka box
Hive.registerAdapter(TodoAdapter());
await Hive.openBox<Todo>('todos');
runApp(const MyApp());
}Inisialisasi Hive.initFlutter() menyiapkan direktori penyimpanan di device. The method openBox membuka container data yang akan kita gunakan untuk operasi CRUD. Penting untuk membuka box sebelum aplikasi menampilkan screen yang membutuhkan data tersebut. Jika box belum terbuka saat widget build, aplikasi akan melempar error runtime.
Mendifinisikan Hive Type Adapters
Hive menyimpan data dalam binary format, sehingga Dart object harus dikonversi melalui adapter. Kita dapat membuat adapter secara manual atau menggunakan code generation dengan anotasi @HiveType.
// models/todo.dart
import 'package:hive/hive.dart';
part 'todo.g.dart';
@HiveType(typeId: 0)
class Todo extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String title;
@HiveField(2)
String description;
@HiveField(3)
bool isCompleted;
@HiveField(4)
bool isSynced;
@HiveField(5)
DateTime createdAt;
Todo({
required this.id,
required this.title,
this.description = '',
this.isCompleted = false,
this.isSynced = true,
required this.createdAt,
});
}Field isSynced menjadi penanda penting dalam arsitektur offline-first. Data yang baru dibuat saat offline akan memiliki nilai false, sehingga aplikasi dapat mengidentifikasi record yang perlu dikirim ke server ketika koneksi tersedia. The class extends HiveObject agar mendapatkan method save() dan delete() secara langsung.
Advanced Flutter State Management with BLoC
Master advanced Flutter state management by building a production-ready applicat...
Setelah mendefinisikan model, jalankan build_runner untuk menghasilkan adapter:
flutter packages pub run build_runner buildFile todo.g.dart akan ter-generate otomatis berisi class TodoAdapter dengan method write dan read untuk binary serialization.
Implementasi CRUD dengan Hive
Operasi CRUD di Hive bersifat synchronous setelah box terbuka, sehingga kita tidak membutuhkan async/await untuk read dan write dasar.
class TodoRepository {
final Box<Todo> _box = Hive.box<Todo>('todos');
List<Todo> getAll() => _box.values.toList();
Future<void> add(Todo todo) async {
await _box.put(todo.id, todo);
}
Future<void> update(Todo todo) async {
await _box.put(todo.id, todo);
}
Future<void> delete(String id) async {
await _box.delete(id);
}
List<Todo> getUnsynced() {
return _box.values.where((todo) => !todo.isSynced).toList();
}
}Method put menggunakan key berupa id untuk menggantikan data yang sudah ada atau membuat yang baru. Karena Todo extends HiveObject, kita juga bisa memanggil todo.save() langsung jika object sudah berasal dari box. Method getUnsynced menyaring data yang belum tersinkronisasi — logika ini menjadi fondasi sync engine.
Untuk UI yang reactive, Hive menyediakan ValueListenableBuilder yang akan rebuild widget secara otomatis ketika data berubah.
ValueListenableBuilder(
valueListenable: Hive.box<Todo>('todos').listenable(),
builder: (context, Box<Todo> box, _) {
final todos = box.values.toList();
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return TodoItem(todo: todos[index]);
},
);
},
)Widget ini listen terhadap perubahan box secara realtime tanpa memerlukan state management library tambahan untuk layer data lokal. The mechanism menggunakan Stream di balik layar, sehingga konsumsi resource tetap efisien meskipun data berubah frekuen.
Sinkronisasi Data Lokal dengan Remote API
Arsitektur sync terdiri dari dua arah: upload perubahan lokal ke server dan download data terbaru dari server. Kita akan menggunakan Dio sebagai HTTP client dan connectivity_plus untuk memantau status koneksi.
class SyncService {
final TodoRepository _repository;
final Dio _dio;
SyncService(this._repository, this._dio);
Future<void> syncToRemote() async {
final unsynced = _repository.getUnsynced();
for (final todo in unsynced) {
try {
await _dio.post('/todos', data: {
'id': todo.id,
'title': todo.title,
'description': todo.description,
'isCompleted': todo.isCompleted,
'createdAt': todo.createdAt.toIso8601String(),
});
todo.isSynced = true;
await todo.save();
} catch (e) {
// Log error dan coba lagi di sync cycle berikutnya
debugPrint('Sync failed for ${todo.id}: $e');
}
}
}
Future<void> syncFromRemote() async {
try {
final response = await _dio.get('/todos');
final remoteData = response.data as List;
for (final json in remoteData) {
final todo = Todo(
id: json['id'],
title: json['title'],
description: json['description'] ?? '',
isCompleted: json['isCompleted'],
isSynced: true,
createdAt: DateTime.parse(json['createdAt']),
);
await _repository.add(todo);
}
} catch (e) {
debugPrint('Fetch remote failed: $e');
}
}
}Flow upload dimulai dengan mengambil semua record yang memiliki isSynced = false. Setiap record dikirim ke server melalui POST request. Jika berhasil, flag isSynced diperbarui menjadi true. Flow download mengambil semua data dari endpoint GET dan menyimpannya ke Hive dengan flag synced langsung bernilai true.
Sync cycle dapat dipicu oleh event connectivity change atau secara periodic menggunakan Workmanager untuk background sync.
Strategi Conflict Resolution
Ketika data sama diedit di device dan server secara bersamaan, terjadi konflik yang harus diselesaikan. Terdapat beberapa strategi yang umum digunakan.
Last-Write-Wins (LWW) adalah pendekatan paling sederhana. Data dengan timestamp terbaru akan menggantikan versi lama. Strategi ini cocok untuk aplikasi di mana pengguna jarang mengedit record yang sama secara bersamaan.
Future<void> resolveConflict(Todo local, Map<String, dynamic> remote) async {
final remoteUpdatedAt = DateTime.parse(remote['updatedAt']);
if (remoteUpdatedAt.isAfter(local.createdAt)) {
// Update lokal dengan data remote
local.title = remote['title'];
local.description = remote['description'];
local.isCompleted = remote['isCompleted'];
local.isSynced = true;
await local.save();
} else {
// Data lokal lebih baru, biarkan flag isSynced tetap false
// agar diupload di cycle berikutnya
}
}Merge Strategy digunakan ketika field yang berbeda diedit secara paralel. Misalnya, judul diedit di device dan deskripsi diedit di server. The library dapat menggabungkan kedua perubahan tanpa kehilangan data.
Untuk aplikasi yang lebih kompleks, Operational Transformation (OT) atau Conflict-free Replicated Data Types (CRDT) dapat dipertimbangkan. CRDT memastikan bahwa semua node akan mencapai state yang konsisten tanpa memerlukan central coordination. Implementasi CRDT membutuhkan overhead yang lebih besar, namun menjamin konsistensi pada skala distributed.
Pendekatan yang paling praktis untuk kebanyakan aplikasi Flutter adalah kombinasi LWW dengan soft delete. Record yang dihapus diberi flag isDeleted alih-alih dihapus permanen, sehingga delete operation juga dapat dipropagasikan ke server dengan aman.
Mau membangun aplikasi mobile yang andal dengan arsitektur offline-first? Bergabunglah dengan Flutter Bootcamp di Rumah Coding. Kurikulum hands-on dengan proyek real-world dan mentorship dari developer berpengalaman.
Course Terkait
Advanced Flutter State Management with BLoC
Master advanced Flutter state management by building a production-ready application. This intermediate course uses a top-down, problem-driven approach, plunging you into real-world engineering challenges. You will learn to architect scalable applications, handle complex reactive states, manage multi-BLoC communication, synchronize real-time data, and implement optimistic UI updates using industry-standard BLoC patterns.
TaskSync: Real-Time Collaborative Task Manager
- Role-Based Authentication: Secure login and session management, dynamically reflecting user states across the entire application.
- Real-Time Task Board: A Kanban-style board that instantly updates across all devices when any team member creates, moves, or deletes a task.
- Advanced Search & Filtering: High-performance local search with event debouncing to prevent unnecessary API calls.
Flutter Mobile Development
Launch your mobile development journey with this immersive, project-based Flutter course. Designed specifically for beginners, this program takes you from coding fundamentals in Dart to deploying a fully functional mobile app. You will learn to craft beautiful, responsive UIs, handle global state management, and integrate cloud backends. By the end of the course, you will have built a real-world, cloud-synced application from scratch.
DailyQuest: Gamified Habit Tracker
- Secure Authentication: User registration and login functionality using email and password.
- Cloud Data Synchronization: Real-time database integration (using Supabase or Firebase) to securely store and retrieve user habits.
- Full CRUD Operations: The ability for users to Create, Read, Update, and Delete their daily tasks and habits.