← Back to blog

Membangun Aplikasi Mobile yang Offline-Aware

MobileOffline FirstArchitectureUser Experience

Banyak aplikasi mobile dibangun seolah-olah jaringan selalu tersedia. Di dunia nyata, pengguna berpindah jaringan, masuk ke area dengan sinyal lemah, kehabisan kuota, atau menutup aplikasi saat request masih berjalan.

Offline-aware tidak selalu berarti seluruh fitur harus berfungsi tanpa internet. Artinya, aplikasi memahami kondisi koneksi dan tetap memberikan perilaku yang jelas, aman, serta dapat dipulihkan.

Tentukan kemampuan setiap fitur

Mulai dengan mengklasifikasikan fitur, bukan langsung memilih database lokal.

KategoriContohPerilaku tanpa koneksi
Read cachedMateri dan riwayatTampilkan data terakhir
Queueable writeAbsensi atau catatanSimpan lalu sinkronkan
Online onlyPembayaran atau verifikasiJelaskan bahwa koneksi diperlukan

Klasifikasi ini membantu product, design, dan engineering menyepakati pengalaman pengguna. Tidak semua operasi aman untuk dimasukkan ke antrean, terutama jika hasilnya bergantung pada state terbaru di server.

Gunakan local database sebagai sumber tampilan

Pola yang stabil adalah membuat UI membaca dari local database. Network layer mengambil data dari server, lalu menyimpannya secara lokal. Perubahan pada database memicu pembaruan tampilan.

Alurnya menjadi:

UI -> local database -> sync engine -> API

Dengan pola ini, layar tidak harus menunggu request setiap kali dibuka. Pengguna dapat melihat data terakhir sambil aplikasi melakukan refresh di background.

Cache biasa dan local database memiliki tujuan berbeda. Cache cocok untuk data sementara yang boleh hilang. Local database lebih tepat untuk state yang perlu dicari, diperbarui, dan dipertahankan di antara sesi aplikasi.

Modelkan status sinkronisasi

Setiap perubahan lokal perlu memiliki identitas dan status yang jelas. Contoh minimal:

type SyncStatus = 'pending' | 'syncing' | 'synced' | 'failed';

type AttendanceRecord = {
  localId: string;
  serverId?: string;
  userId: string;
  occurredAt: string;
  syncStatus: SyncStatus;
  retryCount: number;
};

localId membuat record tetap dapat dirujuk sebelum server memberikan ID. occurredAt perlu menyimpan waktu kejadian, bukan waktu sinkronisasi, agar data tidak berubah makna hanya karena koneksi terlambat.

Jadikan operasi idempotent

Mobile client tidak selalu mengetahui apakah request sebelumnya berhasil. Koneksi dapat terputus setelah server memproses request tetapi sebelum respons diterima. Jika client mengulang request, server bisa membuat data ganda.

Kirim idempotency key yang stabil untuk setiap operasi. Server menyimpan hasil operasi pertama dan mengembalikan hasil yang sama ketika key tersebut dikirim ulang.

POST /attendance
Idempotency-Key: 018f-local-record-id

Idempotency membuat retry lebih aman dan mengurangi logika deduplikasi yang rapuh di client.

Konflik harus menjadi keputusan produk

Konflik terjadi ketika data yang sama berubah di client dan server sebelum sinkronisasi. Tidak ada satu strategi yang benar untuk semua kasus.

  • Last write wins sederhana, tetapi dapat menghapus perubahan penting
  • Server wins cocok ketika server adalah otoritas utama
  • Client wins cocok untuk draft pribadi yang belum dibagikan
  • Merge diperlukan ketika perubahan dapat digabungkan per field
  • Manual resolution tepat untuk data bernilai tinggi

Strategi konflik harus ditentukan per entity. Menggunakan timestamp untuk semua kasus sering hanya menyembunyikan kehilangan data.

Retry dengan disiplin

Jangan mengulang semua error. 401 Unauthorized membutuhkan autentikasi ulang, bukan retry. Validation error membutuhkan koreksi data. Hanya error sementara seperti timeout atau 503 yang layak dicoba kembali.

Gunakan exponential backoff dan jitter agar banyak perangkat tidak menyerang server secara bersamaan ketika layanan kembali pulih. Batasi jumlah percobaan, lalu tampilkan status gagal dan aksi manual yang jelas.

Buat status terlihat oleh pengguna

Pengguna tidak perlu memahami sync engine, tetapi mereka perlu mengetahui keadaan datanya.

Tampilkan status seperti:

  • Tersimpan di perangkat
  • Menunggu koneksi
  • Sedang disinkronkan
  • Gagal, ketuk untuk mencoba lagi
  • Terakhir diperbarui pada waktu tertentu

Optimistic UI boleh digunakan ketika kegagalan dapat dipulihkan. Untuk tindakan berisiko tinggi, lebih baik menunggu konfirmasi server daripada memberi kesan bahwa proses telah selesai.

Uji kondisi yang tidak nyaman

Pengujian offline tidak cukup dengan mematikan Wi-Fi. Simulasikan juga:

  • Koneksi terputus di tengah upload
  • Aplikasi ditutup saat antrean berjalan
  • Token kedaluwarsa sebelum sinkronisasi
  • Request berhasil tetapi respons tidak diterima
  • Data berubah di dua perangkat
  • Jam perangkat tidak akurat
  • Antrean besar setelah beberapa hari offline

Skenario ini sering menemukan masalah yang tidak terlihat pada koneksi development yang stabil.

Kesimpulan

Arsitektur offline-aware adalah kombinasi antara data model, API contract, sync engine, dan desain pengalaman pengguna. Menambahkan penyimpanan lokal saja belum menyelesaikan masalah.

Aplikasi terasa dapat dipercaya ketika pengguna selalu tahu apakah datanya tersimpan, sedang dikirim, atau membutuhkan tindakan. Pada mobile, kejelasan tersebut sama pentingnya dengan kecepatan.