Skip to content

Android Storage Datastore

DodaTech 2 min read

In this tutorial, you'll learn about Jetpack DataStore. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Problem

DataStore throws IllegalStateException: DataStore is already closed, or your Flow doesn't emit values after writing.

Wrong Approach ❌

// Creating DataStore as a property — may be garbage collected
class MyActivity : AppCompatActivity() {
    private val Context.dataStore by preferencesDataStore(name = "settings")
    // DataStore instance tied to Activity lifecycle — closed on rotation!
}

Output: DataStore is already closed on subsequent reads.

Right Approach ✅

// DataStore should be a top-level property or in Application
private val Context.settingsDataStore by preferencesDataStore(name = "settings")

class SettingsManager(private val context: Context) {

    // Read preferences as Flow
    val themeMode: Flow<ThemeMode> = context.settingsDataStore.data
        .map { prefs ->
            val modeName = prefs[THEME_MODE_KEY] ?: "system"
            ThemeMode.valueOf(modeName)
        }
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }

    // Write preferences (suspend)
    suspend fun setThemeMode(mode: ThemeMode) {
        context.settingsDataStore.edit { prefs ->
            prefs[THEME_MODE_KEY] = mode.name
        }
    }

    suspend fun setOnboardingComplete() {
        context.settingsDataStore.edit { prefs ->
            prefs[ONBOARDING_KEY] = true
        }
    }

    // Int preference
    suspend fun setLaunchCount(count: Int) {
        context.settingsDataStore.edit { prefs ->
            prefs[LAUNCH_COUNT_KEY] = count
        }
    }

    // Preference keys
    companion object {
        private val THEME_MODE_KEY = stringPreferencesKey("theme_mode")
        private val ONBOARDING_KEY = booleanPreferencesKey("onboarding_complete")
        private val LAUNCH_COUNT_KEY = intPreferencesKey("launch_count")
    }
}

// Compose usage
@Composable
fun ThemeAwareApp(settingsManager: SettingsManager) {
    val themeMode by settingsManager.themeMode.collectAsState(initial = ThemeMode.System)

    LaunchedEffect(themeMode) {
        settingsManager.setThemeMode(themeMode)
    }
    // ...
}

// Proto DataStore for typed objects
// Use Proto DataStore if you need a typed schema (recommended for complex data)

Output: Stable DataStore with proper lifecycle and reactive reads.

Prevention

  • Define DataStore at the top level or in Application — never in an Activity/Fragment.
  • Handle IOException in the Flow .catch() block.
  • Use edit { } for writes — it's thread-safe and atomic.
  • Preferences DataStore for simple key-value, Proto DataStore for typed schemas.

Common Mistakes with storage datastore

  1. Using return to exit a function early instead of wrapping a pure value in the monad
  2. Mixing let bindings with <- bindings in do notation, producing type errors
  3. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors

These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What is the difference between Preferences DataStore and Proto DataStore?

Preferences DataStore stores key-value pairs (like SharedPreferences). Proto DataStore stores typed objects using Protocol Buffers — type-safe and supports migrations.

### How do I migrate from SharedPreferences to DataStore?

Use SharedPreferencesMigration in the preferencesDataStore Builder. DataStore reads the old SharedPreferences file and migrates data on first access.

### Is DataStore synchronous?

No. DataStore is fully asynchronous (suspend functions + Flow). There's no synchronous get() equivalent. For non-Coroutine contexts, use runBlocking sparingly.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro