Belajar Mobile Developer - Local Storage & Database
Episode 11 of 28

Belajar Mobile Developer - Local Storage & Database

Menyimpan data lokal dengan tepat: perbandingan SharedPreferences/ DataStore, SQLite/Room, Realm, dan options lain, konsep offline-first dengan migration & query reactive — sambil membangun penyimpanan offline-capable untuk Fitku

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah episode 10, aplikasi Fitku bisa berbicara dengan server. Tapi ada masalah: di kereta bawah tanah atau daerah tanpa sinyal, server tidak terjangkau. Di episode 11 ini kita belajar local storage & database — menyimpan data di device sehingga aplikasi tetap berguna tanpa internet. Ini landasan untuk offline-first yang akan kita dalami di episode 23.

Mengapa ini penting? Pengguna menghabiskan banyak waktu di jaringan yang buruk. Aplikasi yang bisa bekerja offline dan menyinkronkan saat online kembali mendapat loyalitas jauh lebih tinggi. Selain itu, baca dari disk selalu lebih cepat daripada jaringan — UX langsung meningkat.

Memilih Alat Penyimpanan

KebutuhanAlat yang Tepat
Setting kecil (token, pref)DataStore / SharedPreferences
Data terstruktur ringanSQLite / Room / SwiftData
Data real-time & reactiveRealm / Room + Flow
File (gambar, video)Filesystem (cache + document dir)

Preferences: DataStore

Untuk pasangan key-value kecil, Android modern memakai DataStore (pengganti SharedPreferences):

KotlinDataStore Preferences
val Context.dataStore by preferencesDataStore(name = "fitku_prefs")
 
val USER_ID = stringPreferencesKey("user_id")
val GOAL_STEPS = intPreferencesKey("goal_steps")
 
// Baca (Flow → reactive)
val userId: Flow<String?> = context.dataStore.data
    .map { prefs -> prefs[USER_ID] }
 
// Tulis
suspend fun setGoal(steps: Int) {
    context.dataStore.edit { prefs -> prefs[GOAL_STEPS] = steps }
}

DataStore berbasis Flow → UI otomatis update saat data berubah, tanpa mekanisme manual.

SQLite / Room

Untuk data terstruktur (workout history), Room di Android adalah lapisan di atas SQLite dengan compile-time query check:

KotlinEntity & DAO Room
@Entity(tableName = "workouts")
data class WorkoutEntity(
    @PrimaryKey val id: String,
    val name: String,
    val startedAt: Long,
    val durationMinutes: Int,
    val synced: Boolean = false,
)
 
@Dao
interface WorkoutDao {
    @Query("SELECT * FROM workouts ORDER BY startedAt DESC")
    fun observeAll(): Flow<List<WorkoutEntity>>
 
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(items: List<WorkoutEntity>)
}

observeAll() mengembalikan Flow — setiap perubahan database otomatis di-push ke UI. Inilah pola reactive persistence.

Flutter: sqflite / drift / Isar

Flutter punya banyak pilihan; untuk 2026, drift (lapisan SQLite type-safe) dan Isar (database NoSQL cepat) populer. Contoh memakai sqflite untuk kebutuhan sederhana:

sqflite di Flutter
import 'package:sqflite/sqflite.dart';
 
Future<void> insertWorkout(Workout w) async {
  final db = await openDatabase('fitku.db');
  await db.insert('workouts', w.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace);
}

Untuk RN, pilihan umum: AsyncStorage (sederhana) atau SQLite via expo-sqlite untuk data terstruktur.

Realm

Realm (kini bagian dari MongoDB) adalah database mobile object-oriented: objek langsung persist tanpa ORM, dan query-nya live/reactive. Kelebihan: sangat cepat untuk data ber-relasi kompleks; kekurangan: ukuran binary lebih besar dan vendor lock-in.

Realm vs Room vs DataStore
DataStore  → setting kecil, key-value, ringan
Room       → data terstruktur, SQL, reactive (Flow), mature
Realm      → objek langsung, reactive, cepat, tapi berat
SwiftData  → pilihan iOS modern (episode ini fokus Android/Flutter/RN)

Migration Database

Aplikasi berevolusi → skema database pun berubah. Room menangani ini lewat Migration:

KotlinMigration Room
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE workouts ADD COLUMN intensity TEXT DEFAULT 'medium'")
    }
}
 
val database = Room.databaseBuilder(context, AppDatabase::class.java, "fitku.db")
    .addMigrations(MIGRATION_1_2)
    .build()

Warning

Migration adalah kewajiban, bukan pilihan. Jika kalian mengubah skema tanpa migration, pengguna dengan versi lama akan crash saat update. Biasakan: setiap perubahan skema = satu migration baru, dan uji dengan exportSchema = true untuk deteksi otomatis.

Strategi Offline-First

Prinsip offline-first: tulis lokal dulu, sinkronkan kemudian. Data lokal adalah sumber kebenaran sementara; server menjadi final setelah sync (detail di episode 23).

KotlinSimpan dulu, sync kemudian
class WorkoutRepositoryImpl(
    private val api: FitkuApi,
    private val dao: WorkoutDao,
) : WorkoutRepository {
    override suspend fun saveWorkout(workout: Workout) {
        dao.insert(workout.copy(synced = false))   // 1. simpan lokal seketika
        syncPending()                               // 2. coba sync di background
    }
 
    private suspend fun syncPending() {
        dao.pending().forEach { local ->
            try {
                api.uploadWorkout(local.toDto())
                dao.markSynced(local.id)
            } catch (_: RetryableException) {
                return  // gagal → tinggal di antrean, dicoba lagi nanti
            }
        }
    }
}

Pola "outbox": tabel data lokal diberi flag synced. Saat online, item yang belum synced dikirim; saat offline, antrean menunggu. Ini fondasi aplikasi yang bisa dipakai di mana pun.

Tip

Untuk praktik cepat, expo-sqlite di Expo dan sqflite di Flutter sama-sama mudah dimulai. Mulailah dari model sederhana (Workout), lalu perluas. Kuncinya konsisten: simpan lokal dulu, reactive ke UI, sinkronkan dengan tenang.

Common Pitfalls Local Storage

  • Menulis di main thread: operasi database yang berat mem-block UI. Selalu pakai async/coroutine/isolate.
  • Menyalin cache tanpa batas: disk penuh → app bermasalah. Kelola cache dengan LRU/batas ukuran.
  • Query berulang tanpa cache: DAO reactive (Flow) mengirim banyak event; gunakan distinctUntilChanged.
  • Tidak menangani upgrade: versi database lama crash tanpa migration.
  • Menyimpan secret di database/DataStore: token & key harus di secure storage (episode 13), bukan penyimpanan biasa.

Penutup

Pada episode 11 ini, kalian telah menyimpan data dengan benar:

  • Pilih alat sesuai kebutuhan: DataStore (pref), Room/drift/SQLite (terstruktur), Realm (objek cepat).
  • Migration wajib untuk setiap perubahan skema.
  • Pola offline-first: tulis lokal dulu, sinkronkan kemudian via outbox.
  • Persistensi reactive (Flow/streams) membuat UI selalu sinkron dengan data.

Di episode 12 selanjutnya kita membangun jalur pengguna: Navigation & Deep Linking — stack, tab, drawer, dan deep linking untuk membuka layar spesifik dari notifikasi atau link. Sampai jumpa!

Belajar Mobile Developer - Local Storage & Database | Belajar Mobile Developer