Membangun Aplikasi Mobile yang Offline-Aware
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.
| Kategori | Contoh | Perilaku tanpa koneksi |
|---|---|---|
| Read cached | Materi dan riwayat | Tampilkan data terakhir |
| Queueable write | Absensi atau catatan | Simpan lalu sinkronkan |
| Online only | Pembayaran atau verifikasi | Jelaskan 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.