Aplikasi lapangan (field app) tidak bisa bergantung pada internet. Sinyal hilang, jaringan lambat, atau device di area tertutup adalah kondisi nyata. Karena itu, offline‑first adalah pendekatan yang membuat data tetap aman, UX tetap stabil, dan user tetap percaya.
Di artikel ini kita bahas versi praktis: Room + WorkManager + strategi sync yang sederhana tapi kuat.
1) Konsep inti offline‑first (versi simpel)
Prinsipnya cuma 3:
- User tetap bisa bekerja saat offline
- Data selalu disimpan lokal dulu
- Sync berjalan otomatis ketika jaringan tersedia
Kalau kamu menjaga tiga hal ini, 80% masalah offline‑first sudah aman.
2) Arsitektur dasar (Outbox pattern)
Kita pakai pola Outbox:
UI -> Room (local db) -> Outbox -> Worker -> API
Outbox = tabel antrean action yang perlu dikirim ke server. Semua action user masuk ke outbox dulu, bukan langsung call API.
3) Struktur data minimal
Kita butuh 2 tabel utama:
- Data utama (misalnya
TaskEntity) - Outbox untuk antrean sync
Contoh Room Entity:
@Entity(tableName = "tasks")
data class TaskEntity(
@PrimaryKey val id: String,
val title: String,
val status: String,
val updatedAt: Long,
val isDirty: Boolean
)
@Entity(tableName = "outbox")
data class OutboxEntity(
@PrimaryKey val id: String,
val type: String, // create/update/delete
val payload: String,
val createdAt: Long,
val retryCount: Int = 0
)isDirty dipakai untuk menandai data lokal yang belum sinkron.
4) Simpan data lokal dulu (selalu)
Saat user menambah/ubah data, kamu lakukan:
- Simpan ke table utama
- Buat item outbox
suspend fun saveTask(task: TaskEntity) {
taskDao.upsert(task.copy(isDirty = true, updatedAt = System.currentTimeMillis()))
outboxDao.insert(
OutboxEntity(
id = UUID.randomUUID().toString(),
type = "update",
payload = gson.toJson(task),
createdAt = System.currentTimeMillis()
)
)
}User tetap bisa lanjut kerja tanpa menunggu network.
5) Sync Worker dengan WorkManager
Gunakan WorkManager karena:
- otomatis retry
- bisa set constraint (network tersedia)
- tahan terhadap kill/background
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(syncRequest)6) Isi SyncWorker (intinya sederhana)
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val pending = outboxDao.getAllPending()
for (item in pending) {
try {
api.send(item.type, item.payload)
outboxDao.delete(item.id)
} catch (e: Exception) {
outboxDao.incrementRetry(item.id)
return Result.retry()
}
}
return Result.success()
}
}Ini versi minimal. Di produksi kamu bisa tambah:
- batch per 20 item
- retry policy berbeda untuk error 4xx/5xx
- backoff (exponential)
7) Strategi conflict handling (simple & aman)
Masalah: data di server berubah saat kamu offline.
Solusi paling aman untuk awal:
- Last write wins (pakai
updatedAt) - Jika konflik penting → tampilkan notifikasi untuk user
Contoh logic sederhana:
if (remote.updatedAt > local.updatedAt) {
// server lebih baru
taskDao.upsert(remote.copy(isDirty = false))
} else {
// lokal lebih baru
enqueueLocalUpdate(local)
}8) UX yang jelas (wajib!)
Offline‑first bukan cuma soal data. UX harus memberi rasa aman.
Minimal tampilkan:
- Status koneksi (online/offline)
- “Last synced at …”
- Badge kecil pada item yang belum tersinkron
Contoh teks sederhana:
“Offline · data akan tersinkron otomatis”
9) Tips penting agar stabil di lapangan
- Selalu write lokal dulu
- Jangan block UI saat sync
- Gunakan retry + backoff
- Kurangi payload sync (hanya perubahan)
- Log & analytics untuk error sync
10) Checklist produksi
Gunakan checklist ini sebelum release:
- [ ] Semua action user masuk outbox
- [ ] Worker punya constraint jaringan
- [ ] Ada indikator “sync pending”
- [ ] Konflik di-handle (minimal last-write-wins)
- [ ] Auto retry + backoff
Penutup
Offline‑first tidak harus rumit. Pola sederhana (Room + Outbox + WorkManager) sudah cukup kuat untuk mayoritas aplikasi lapangan. Mulai dari versi minimal ini, lalu tambah fitur sesuai kebutuhan (batching, diff sync, conflict UI, dsb). Kesimpulannya: kunci keberhasilan ada di penyimpanan lokal yang konsisten, sync queue yang andal, dan UX yang memberi rasa aman.