DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Android DataStore Guide — Migration from SharedPreferences

Android DataStore Guide — Migration from SharedPreferences

DataStore is the modern replacement for SharedPreferences, offering better performance and type safety with coroutines.

Setup with Preferences DataStore

// Gradle dependency
dependencies {
    implementation "androidx.datastore:datastore-preferences:1.0.0"
}

// Create extension function
val Context.settingsDataStore by preferencesDataStore(name = "settings")

// Type-safe key definitions
val IS_DARK_MODE = booleanPreferencesKey("is_dark_mode")
val USER_NAME = stringPreferencesKey("user_name")
val LAST_SYNC_TIME = longPreferencesKey("last_sync_time")
val THEME_COLOR = intPreferencesKey("theme_color")
val TAG_LIST = stringSetPreferencesKey("tags")
Enter fullscreen mode Exit fullscreen mode

Repository Pattern with Flow

class SettingsRepository(private val context: Context) {

    fun getUserNameFlow(): Flow<String> =
        context.settingsDataStore.data.map { preferences ->
            preferences[USER_NAME] ?: ""
        }

    fun getIsDarkModeFlow(): Flow<Boolean> =
        context.settingsDataStore.data.map { preferences ->
            preferences[IS_DARK_MODE] ?: false
        }

    suspend fun setUserName(name: String) {
        context.settingsDataStore.edit { preferences ->
            preferences[USER_NAME] = name
        }
    }

    suspend fun setIsDarkMode(enabled: Boolean) {
        context.settingsDataStore.edit { preferences ->
            preferences[IS_DARK_MODE] = enabled
        }
    }

    suspend fun updateLastSyncTime() {
        context.settingsDataStore.edit { preferences ->
            preferences[LAST_SYNC_TIME] = System.currentTimeMillis()
        }
    }

    suspend fun clearAllSettings() {
        context.settingsDataStore.edit { it.clear() }
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewModel Integration with stateIn

class SettingsViewModel(
    private val repository: SettingsRepository
) : ViewModel() {

    val userName: StateFlow<String> = repository
        .getUserNameFlow()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = ""
        )

    val isDarkMode: StateFlow<Boolean> = repository
        .getIsDarkModeFlow()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = false
        )

    fun updateUserName(name: String) {
        viewModelScope.launch {
            repository.setUserName(name)
        }
    }

    fun toggleDarkMode() {
        viewModelScope.launch {
            val current = isDarkMode.value
            repository.setIsDarkMode(!current)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Compose Settings UI

@Composable
fun SettingsScreen(viewModel: SettingsViewModel) {
    val userName by viewModel.userName.collectAsState()
    val isDarkMode by viewModel.isDarkMode.collectAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text("Settings", style = MaterialTheme.typography.headlineSmall)

        Spacer(modifier = Modifier.height(16.dp))

        // Text input
        TextField(
            value = userName,
            onValueChange = viewModel::updateUserName,
            label = { Text("User Name") },
            modifier = Modifier.fillMaxWidth()
        )

        Spacer(modifier = Modifier.height(16.dp))

        // Toggle switch
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text("Dark Mode", modifier = Modifier.weight(1f))
            Switch(
                checked = isDarkMode,
                onCheckedChange = { viewModel.toggleDarkMode() }
            )
        }

        // Action button
        Button(
            onClick = { viewModel.clearSettings() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Clear All Settings")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

SharedPreferences Migration

suspend fun migrateFromSharedPreferences(
    context: Context,
    repository: SettingsRepository
) {
    val sharedPref = context.getSharedPreferences("old_prefs", Context.MODE_PRIVATE)

    // Migrate each value
    if (sharedPref.contains("user_name")) {
        val name = sharedPref.getString("user_name", "")
        repository.setUserName(name!!)
    }

    if (sharedPref.contains("is_dark_mode")) {
        val darkMode = sharedPref.getBoolean("is_dark_mode", false)
        repository.setIsDarkMode(darkMode)
    }

    // Clear old SharedPreferences (optional)
    sharedPref.edit().clear().apply()
}

// Call during app startup
LaunchedEffect(Unit) {
    migrateFromSharedPreferences(context, repository)
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • preferencesDataStore delegate simplifies setup
  • Use type-safe keys (booleanPreferencesKey, etc.)
  • Leverage Flow for reactive updates
  • Implement repository pattern for encapsulation
  • Use stateIn for Compose StateFlow integration
  • Migrate gradually from SharedPreferences

8 Android app templates: Gumroad

Top comments (0)