Strategi caching Flutter untuk cache lokal, data kadaluarsa, dan aturan refresh: apa yang disimpan, kapan diinvalidasi, dan bagaimana menjaga konsistensi layar.

Caching di aplikasi mobile berarti menyimpan salinan data di dekat (di memori atau di perangkat) sehingga layar berikutnya dapat dirender segera tanpa menunggu jaringan. Data itu bisa berupa daftar item, profil pengguna, atau hasil pencarian.
Yang membuatnya sulit adalah data cache seringkali sedikit salah. Pengguna cepat menyadarinya: harga yang tidak berubah, hitungan badge yang terasa macet, atau layar detail yang menunjukkan info lama segera setelah mereka mengubahnya. Yang membuatnya menyakitkan untuk di-debug adalah timing. Endpoint yang sama bisa tampak benar setelah pull-to-refresh, tapi salah setelah navigasi kembali, resume app, atau pergantian akun.
Ada tradeoff nyata. Jika selalu mengambil data segar, layar terasa lambat dan melompat-lompat, serta membuang baterai dan data. Jika Anda agresif melakukan cache, aplikasi terasa cepat, tetapi orang berhenti mempercayai apa yang mereka lihat.
Tujuan sederhana membantu: buat kesegaran bisa diprediksi. Tentukan apa yang boleh ditampilkan tiap layar (segar, sedikit kadaluarsa, atau offline), berapa lama data boleh hidup sebelum di-refresh, dan event mana yang harus menginvalidasi.
Bayangkan alur umum: pengguna membuka pesanan, lalu kembali ke daftar pesanan. Jika daftar diambil dari cache, ia mungkin masih menunjukkan status lama. Jika Anda refresh setiap kali, daftar bisa berkedip dan terasa lambat. Aturan jelas seperti “tampilkan cache segera, refresh di background, dan perbarui kedua layar saat respons datang” membuat pengalaman konsisten saat navigasi.
Cache bukan sekadar “data yang disimpan.” Itu adalah salinan yang disimpan plus aturan kapan salinan itu masih valid. Jika Anda menyimpan payload tapi melewatkan aturannya, Anda akan berakhir dengan dua versi realitas: satu layar menunjukkan info baru, layar lain menunjukkan info kemarin.
Model praktis adalah menempatkan setiap item cache ke dalam salah satu dari tiga status:
Pembingkai ini membuat UI Anda dapat diprediksi karena ia dapat merespon dengan cara yang sama setiap kali melihat status tertentu.
Aturan kesegaran sebaiknya berdasarkan sinyal yang bisa Anda jelaskan kepada rekan tim. Pilihan umum adalah expiry berbasis waktu (mis. 5 menit), perubahan versi (schema atau versi aplikasi), aksi pengguna (pull to refresh, submit, delete), atau petunjuk dari server (ETag, timestamp last-updated, atau respons "cache invalid" eksplisit).
Contoh: layar profil memuat data pengguna dari cache secara instan. Jika itu stale-but-usable, tampilkan nama dan avatar yang di-cache, lalu refresh diam-diam. Jika pengguna baru saja mengedit profilnya, itu adalah momen must-refresh. Aplikasi harus memperbarui cache segera sehingga setiap layar tetap konsisten.
Tentukan siapa yang memegang aturan ini. Di sebagian besar aplikasi, default terbaik adalah: lapisan data yang mengelola kesegaran dan invalidasi, UI hanya bereaksi (tampilkan cache, tampilkan loading, tampilkan error), dan backend memberi petunjuk bila bisa. Ini mencegah tiap layar menemukan aturannya sendiri.
Caching yang baik dimulai dengan satu pertanyaan: jika data ini sedikit tua, apakah itu merugikan pengguna? Jika jawabannya “mungkin tidak apa-apa,” biasanya cocok untuk caching lokal.
Data yang banyak dibaca dan berubah pelan umumnya layak dicache: feed dan daftar yang sering discroll, konten katalog (produk, artikel, template), dan data referensi seperti kategori atau negara. Pengaturan dan preferensi juga masuk sini, bersama info profil dasar seperti nama dan URL avatar.
Sisi berisiko adalah apa pun yang terkait uang atau yang kritis waktu. Saldo, status pembayaran, ketersediaan stok, slot janji, ETA pengiriman, dan “terakhir terlihat online” bisa menyebabkan masalah nyata jika kadaluarsa. Anda masih bisa mencache untuk kecepatan, tapi perlakukan cache sebagai placeholder sementara dan paksa refresh pada titik keputusan (mis. sebelum mengonfirmasi pesanan).
Status UI turunan adalah kategori tersendiri. Menyimpan tab yang dipilih, filter, query pencarian, urutan sortir, atau posisi scroll bisa membuat navigasi terasa mulus. Ini juga bisa membingungkan ketika pilihan lama muncul kembali secara tak terduga. Aturan sederhana bekerja baik: simpan state UI di memori selama pengguna berada dalam alur itu, tapi reset ketika mereka sengaja “memulai ulang” (mis. kembali ke layar utama).
Hindari caching data yang menimbulkan risiko keamanan atau privasi: rahasia (password, API key), token sekali pakai (kode OTP, token reset password), dan data pribadi sensitif kecuali Anda benar-benar butuh akses offline. Jangan pernah mencache detail kartu penuh atau apa pun yang meningkatkan risiko penipuan.
Di aplikasi belanja, mencache daftar produk adalah keuntungan besar. Namun layar checkout harus selalu merefresh total dan ketersediaan tepat sebelum pembelian.
Sebagian besar aplikasi Flutter pada akhirnya membutuhkan cache lokal agar layar cepat dan tidak berkedip kosong saat jaringan bangun. Keputusan utama adalah di mana data cache tinggal, karena tiap lapisan punya kecepatan, batas ukuran, dan perilaku pembersihan yang berbeda.
Cache di memori adalah yang tercepat. Cocok untuk data yang baru Anda ambil dan akan digunakan ulang selama aplikasi tetap terbuka, seperti profil pengguna saat ini, hasil pencarian terakhir, atau produk yang baru saja dilihat. Tradeoff-nya jelas: ia hilang ketika aplikasi dimatikan, jadi tidak membantu cold start atau penggunaan offline.
Penyimpanan key-value di disk cocok untuk item kecil yang Anda inginkan melintasi restart. Pikirkan preferensi dan blob sederhana: feature flags, “tab terakhir yang dipilih,” dan respons JSON kecil yang jarang berubah. Jaga agar ukurannya sengaja kecil. Begitu Anda mulai memasukkan daftar besar ke key-value storage, pembaruan menjadi berantakan dan bloat mudah terjadi.
Database lokal terbaik untuk data yang lebih besar, terstruktur, atau membutuhkan perilaku offline. Ini juga membantu saat Anda butuh query (“semua pesan belum dibaca,” “item dalam keranjang,” “pesanan bulan lalu”) daripada memuat satu blob besar dan memfilternya di memori.
Untuk menjaga caching dapat diprediksi, pilih satu store utama untuk tiap tipe data dan hindari menyimpan dataset yang sama di tiga tempat.
Aturan praktis singkat:
Juga rencanakan ukuran. Tentukan apa arti “terlalu besar”, berapa lama menyimpan item, dan bagaimana membersihkan. Contoh: batasi hasil pencarian yang dicache ke 20 query terakhir, dan hapus secara rutin record yang lebih tua dari 30 hari agar cache tidak tumbuh diam-diam selamanya.
Aturan refresh sebaiknya cukup sederhana sehingga bisa dijelaskan dalam satu kalimat per layar. Di sinilah caching masuk akal: pengguna mendapat layar cepat, dan aplikasi tetap dipercaya.
Aturan paling sederhana adalah TTL (time to live). Simpan data dengan timestamp dan anggap itu fresh selama, misalnya, 5 menit. Setelah itu, ia menjadi stale. TTL bekerja baik untuk data “nice to have” seperti feed, kategori, atau rekomendasi.
Penyempurnaan yang berguna adalah memisah TTL menjadi soft TTL dan hard TTL.
Dengan soft TTL, Anda menampilkan data cache segera, lalu refresh di background dan memperbarui UI jika berubah. Dengan hard TTL, Anda berhenti menampilkan data lama setelah kedaluwarsa. Anda harus memblokir dengan loader atau menampilkan state “offline/coba lagi”. Hard TTL cocok untuk kasus di mana salah lebih buruk dari lambat, seperti saldo, status pesanan, atau izin.
Jika backend mendukung, pilih “refresh hanya saat berubah” menggunakan ETag, updatedAt, atau field versi. Aplikasi Anda bisa menanyakan “apakah ini berubah?” dan melewatkan pengunduhan payload penuh jika tidak ada yang baru.
Default ramah pengguna untuk banyak layar adalah stale-while-revalidate: tampilkan sekarang, refresh diam-diam, dan render ulang hanya jika hasil berbeda. Ini memberi kecepatan tanpa flicker acak.
Kesegaran per-layar seringkali berakhir seperti ini:
Pilih aturan berdasarkan biaya kalau salah, bukan hanya biaya pengambilan.
Invalidasi cache dimulai dari satu pertanyaan: event apa yang membuat cache kurang dapat dipercaya dibanding biaya refetch? Jika Anda memilih set kecil trigger dan konsisten, perilaku tetap dapat diprediksi dan UI terasa stabil.
Trigger yang paling penting di aplikasi nyata:
Contoh: pengguna mengedit foto profil, lalu kembali. Jika Anda hanya mengandalkan refresh berbasis waktu, layar sebelumnya mungkin menunjukkan gambar lama sampai fetch berikutnya. Sebagai gantinya, perlakukan edit sebagai trigger: perbarui objek profil di cache segera dan tandai sebagai fresh dengan timestamp baru.
Jaga aturan invalidasi kecil dan eksplisit. Jika Anda tidak bisa menunjuk event yang tepat yang menginvalidasi entri cache, Anda akan terlalu sering refresh (UI lambat, melompat) atau tidak cukup (layar kadaluarsa).
Mulailah dengan mencantumkan layar kunci Anda dan data yang dibutuhkan tiap layar. Jangan berpikir dalam endpoint. Pikirkan objek yang terlihat pengguna: profil, keranjang, daftar pesanan, item katalog, jumlah belum dibaca.
Selanjutnya, pilih satu sumber kebenaran per tipe data. Di Flutter, ini biasanya sebuah repository yang menyembunyikan dari mana data berasal (memori, disk, jaringan). Layar tidak seharusnya memutuskan kapan harus memanggil jaringan. Mereka meminta data ke repository dan bereaksi pada state yang dikembalikan.
Alur praktis:
Metadata adalah yang membuat aturan dapat ditegakkan. Jika ownerUserId berubah (logout/login), Anda bisa membuang atau mengabaikan row cache lama segera daripada menampilkan data pengguna sebelumnya untuk sekejap.
Untuk perilaku UI, tentukan sejak awal apa arti “stale”. Aturan umum: tampilkan data stale instan agar layar tidak kosong, mulai refresh di background, dan perbarui saat data baru datang. Jika refresh gagal, tetap tampilkan data stale dan tunjukkan error kecil yang jelas.
Lalu kunci aturan dengan beberapa tes membosankan:
Itulah perbedaan antara “kita punya caching” dan “aplikasi kita berperilaku sama setiap kali.”
Tidak ada yang merusak kepercayaan lebih cepat daripada melihat satu nilai di layar daftar, membuka detail, mengeditnya, lalu kembali dan melihat nilai lama lagi. Konsistensi antar navigasi datang dari membuat setiap layar membaca dari sumber yang sama.
Aturan yang solid: fetch sekali, simpan sekali, render berkali-kali. Layar tidak boleh memanggil endpoint yang sama secara independen dan menyimpan salinan privat. Tempatkan data cache di store bersama (lapisan state management Anda), dan biarkan baik list maupun detail mengamati data yang sama.
Simpan satu tempat yang menjadi pemilik nilai saat ini dan kesegaran. Layar bisa meminta refresh, tapi mereka tidak seharusnya masing-masing mengelola timer, retry, dan parsing sendiri.
Kebiasaan praktis yang mencegah “dua versi realitas”:
Bahkan dengan aturan yang baik, pengguna terkadang akan melihat data stale (offline, jaringan lambat, app dibackground). Buat hal itu jelas dengan sinyal kecil dan tenang: timestamp “Diperbarui barusan”, indikator halus “Merefresh…”, atau badge “Offline”.
Untuk edit, optimistic update sering terasa terbaik. Contoh: pengguna mengubah harga produk di layar detail. Perbarui store bersama segera sehingga layar daftar menampilkan harga baru saat mereka kembali. Jika penyimpanan gagal, rollback ke nilai sebelumnya dan tunjukkan error singkat.
Sebagian besar kegagalan caching itu membosankan: cache bekerja, tapi tak ada yang bisa menjelaskan kapan harus digunakan, kapan kadaluarsa, dan siapa pemiliknya.
Perangkap pertama adalah caching tanpa metadata. Jika Anda hanya menyimpan payload, Anda tidak bisa tahu apakah itu tua, versi app/schema mana yang membuatnya, atau pengguna mana yang menjadi pemilik. Simpan setidaknya savedAt, nomor versi sederhana, dan userId (atau tenant key). Kebiasaan itu mencegah banyak bug “kenapa layar ini salah?”.
Masalah umum lain adalah banyak cache untuk data yang sama tanpa pemilik. Layar daftar menyimpan list di memori, repository menulis ke disk, dan layar detail mengambil lagi dan menyimpan di tempat lain. Pilih satu sumber kebenaran (seringkali lapisan repository) dan buat setiap layar membaca melalui situ.
Perubahan akun sering menjadi jebakan. Jika seseorang logout atau ganti akun, kosongkan tabel dan kunci yang scoped ke user. Kalau tidak, Anda bisa menampilkan foto profil atau pesanan pengguna sebelumnya sekejap, yang terasa seperti pelanggaran privasi.
Perbaikan praktis yang menutup isu di atas:
Contoh: daftar produk Anda memuat instan dari cache, lalu refresh diam-diam. Jika refresh gagal, tetap tampilkan data cache tapi jelaskan bahwa mungkin sudah usang dan tawarkan Retry. Jangan blok UI pada refresh ketika data cache masih bisa dipakai.
Sebelum rilis, ubah caching dari “sepertinya baik” menjadi aturan yang bisa diuji. Pengguna harus melihat data yang masuk akal bahkan setelah navigasi bolak-balik, offline, atau masuk dengan akun berbeda.
Untuk tiap layar, putuskan berapa lama data bisa dianggap fresh. Mungkin menit untuk data yang cepat berubah (pesan, saldo) atau jam untuk data yang lambat berubah (pengaturan, kategori produk). Lalu konfirmasi apa yang terjadi saat tidak fresh: refresh di background, refresh saat dibuka, atau pull-to-refresh manual.
Untuk tiap tipe data, tentukan event mana yang harus menghapus atau melewati cache. Trigger umum meliputi logout, mengedit item, ganti akun, dan update app yang mengubah bentuk data.
Pastikan entri cache menyimpan sekumpulan metadata kecil di samping payload:
Jaga kepemilikan jelas: gunakan satu repository per tipe data (mis. ProductsRepository), bukan per widget. Widget harus meminta data, bukan menentukan aturan cache.
Juga putuskan dan uji perilaku offline. Konfirmasi apa yang ditampilkan dari cache, aksi mana yang dinonaktifkan, dan copy apa yang Anda tampilkan (“Menampilkan data tersimpan”, plus kontrol refresh yang terlihat). Refresh manual harus ada di setiap layar yang didukung cache dan mudah ditemukan.
Bayangkan aplikasi toko sederhana dengan tiga layar: katalog produk (daftar), detail produk, dan tab Favorit. Pengguna scroll katalog, membuka produk, dan mengetuk ikon hati untuk menandai favorit. Tujuannya adalah terasa cepat, bahkan di jaringan lambat, tanpa menampilkan ketidaksesuaian yang membingungkan.
Cache lokal apa yang membantu render instan: halaman katalog (ID, judul, harga, URL thumbnail, flag favorit), detail produk (deskripsi, spesifikasi, ketersediaan, lastUpdated), metadata gambar (URL, ukuran, cache key), dan favorit pengguna (set ID produk, opsional dengan timestamp).
Saat pengguna membuka katalog, tampilkan hasil cache segera, lalu revalidasi di background. Jika data fresh tiba, perbarui hanya yang berubah dan pertahankan posisi scroll.
Untuk toggle favorit, perlakukan sebagai aksi yang "harus konsisten". Perbarui set favorit lokal segera (optimistic update), lalu perbarui baris produk dan detail produk yang di-cache untuk ID itu. Jika panggilan jaringan gagal, rollback dan tampilkan pesan kecil.
Untuk menjaga konsistensi navigasi, arahkan badge di daftar dan ikon hati detail dari sumber kebenaran yang sama (cache lokal atau store), bukan dari state layar terpisah. Ikon hati di daftar terupdate saat Anda kembali dari detail, layar detail mencerminkan perubahan yang dibuat dari daftar, dan jumlah tab Favorit cocok di mana-mana tanpa menunggu refetch.
Tambahkan aturan refresh sederhana: cache katalog kedaluwarsa cepat (menit), detail produk sedikit lebih lama, dan favorit tidak pernah kadaluarsa tapi selalu direkonsiliasi setelah login/logout.
Caching berhenti menjadi misteri ketika tim Anda bisa menunjuk satu halaman aturan dan setuju apa yang harus terjadi. Tujuannya bukan kesempurnaan. Ini perilaku yang dapat diprediksi dan tetap sama lintas rilis.
Tulis tabel kecil per layar dan jaga agar singkat untuk direview saat perubahan: nama layar dan data utama, lokasi cache dan key, aturan kesegaran (TTL, berbasis event, atau manual), trigger invalidasi, dan apa yang dilihat pengguna saat refresh.
Tambahkan logging ringan saat menyetel. Catat cache hit, miss, dan alasan mengapa refresh terjadi (TTL kedaluwarsa, pengguna pull-to-refresh, app resume, mutasi selesai). Saat seseorang melaporkan “daftar ini terasa salah”, log tersebut membuat bug dapat diselesaikan.
Mulailah dengan TTL sederhana, lalu perbaiki berdasarkan apa yang diperhatikan pengguna. Feed berita mungkin mentolerir 5–10 menit staleness, sedangkan layar status pesanan mungkin perlu refresh saat resume dan setelah aksi checkout.
Jika Anda membangun aplikasi Flutter dengan cepat, membantu untuk menguraikan lapisan data dan aturan cache sebelum mengimplementasikan apa pun. Untuk tim yang menggunakan Koder.ai (koder.ai), planning mode adalah tempat praktis untuk menulis aturan per-layar terlebih dahulu, lalu membangun sesuai.
Saat Anda menyetel perilaku refresh, lindungi layar yang stabil saat bereksperimen. Snapshot dan rollback dapat menghemat waktu bila aturan baru tanpa sengaja memperkenalkan flicker, state kosong, atau hitungan yang tidak konsisten antar navigasi.
Mulailah dengan satu aturan jelas per layar: apa yang boleh ditampilkan segera (dari cache), kapan harus di-refresh, dan apa yang dilihat pengguna selama proses refresh. Jika Anda tidak bisa menjelaskan aturannya dalam satu kalimat, aplikasi pada akhirnya akan terasa tidak konsisten.
Anggap data cache memiliki status kesegaran. Jika fresh, tampilkan. Jika stale but usable, tampilkan sekarang dan refresh diam-diam. Jika must refresh, ambil data sebelum menampilkan (atau tampilkan state loading/offline). Ini menjaga perilaku UI konsisten daripada “kadang update, kadang tidak.”
Cache data yang sering dibaca dan sedikit menua tidak merugikan pengguna, seperti feed, katalog, data referensi, dan info profil dasar. Hati-hati dengan data yang berhubungan uang atau kritis waktu seperti saldo, ketersediaan stok, ETA pengiriman, atau status pesanan; Anda bisa cache untuk kecepatan, tapi paksa refresh tepat sebelum langkah keputusan atau konfirmasi.
Gunakan memory untuk reuse cepat selama sesi berjalan (profil saat ini, item yang baru dilihat). Gunakan penyimpanan key-value di disk untuk item kecil yang harus bertahan restart (preferensi). Gunakan database lokal saat data besar, terstruktur, perlu query, atau harus bekerja offline (pesan, pesanan, inventaris).
TTL sederhana adalah default yang baik: anggap data fresh selama waktu tertentu, lalu refresh. Untuk banyak layar, pengalaman lebih baik adalah “tampilkan cache sekarang, refresh di background, lalu update jika berubah,” karena menghindari layar kosong dan flicker.
Invalidasi pada event yang jelas mengurangi kepercayaan cache: edit user (create/update/delete), login/logout atau pergantian akun, resume app jika data lebih tua dari TTL Anda, dan refresh manual oleh pengguna. Jaga trigger ini kecil dan eksplisit agar tidak terlalu sering atau terlalu jarang melakukan refresh.
Biarkan kedua layar membaca dari sumber kebenaran yang sama, bukan salinan privat mereka sendiri. Ketika pengguna mengedit pada layar detail, perbarui objek cache bersama secara segera sehingga daftar menampilkan nilai baru saat kembali, lalu sinkronkan dengan server dan lakukan rollback hanya jika penyimpanan gagal.
Simpan metadata di samping payload, terutama timestamp dan identifier pengguna. Saat logout atau ganti akun, hapus atau pisahkan entri cache yang scoped ke pengguna serta batalkan request yang sedang berjalan yang terkait dengan pengguna lama agar Anda tidak menampilkan data pengguna sebelumnya sesaat.
Secara default biarkan data stale tetap terlihat dan tampilkan error kecil yang jelas serta tawarkan retry, daripada mengosongkan layar. Jika layar tidak aman menampilkan data lama, ubah menjadi aturan must-refresh dan tampilkan loading atau pesan offline daripada berpura-pura nilai lama dapat dipercaya.
Letakkan aturan cache di lapisan data (mis. repository) agar setiap layar mengikuti perilaku yang sama. Jika Anda bekerja cepat di Koder.ai, tulis aturan kesegaran dan invalidasi per-layar di planning mode dulu, lalu implementasikan sehingga UI hanya bereaksi pada state alih-alih membuat logika refresh sendiri.