Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Android DataStore with multi users or files

I want to store some preferences using DataStore. But the problem is that my application can have multiple users and therefor needs to store these preferences in separate files. I got a working example using only one user but I'm struggling to support multiple users.

Here is an example of my code:

class DataStorageRepository(private val context: Context, private val userRepository: UserRepository) {

    private object PreferencesKeys {
        val SETTING_ONE = intPreferencesKey("setting_one")
    }

    // retrieve datastore for currently logged in user. 
    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = userRepository.currentRegistration().name)

    val userPreferencesFlow: Flow<UserPreferences> = context.dataStore.data.map { preferences ->
        val settingOne = preferences[PreferencesKeys.SETTING_ONE] ?: 0

        UserPreferences(settingOne)
    }

    suspend fun storeSettingOne(settingOne: Int) {
        context.dataStore.edit { preferences ->
            preferences[PreferencesKeys.SETTING_ONE] = settingOne
        }
    }

    data class UserPreferences(val lastUsedToAccountTab: Int)
}

I'm using Koin and I tried unloading the DataStorageRepository on logout and recreating it on login but the DataStore seems to stay alive until the app is killed and I get the following crash:

java.lang.IllegalStateException: There are multiple DataStores active for the same file: [...] You should either maintain your DataStore as a singleton or confirm that there is no two DataStore's active on the same file (by confirming that the scope is cancelled).

I also tried to use a CoroutineScope and kill that when I log out, but after recreating the scope on login the DataStore doesn't seem to get recreated.

Does DataStore support a way to close the connection or to handle multiple files?

like image 458
Wirling Avatar asked Mar 17 '21 08:03

Wirling


3 Answers

Put this line inside companion object { }

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settingPrefs")

My Code

class SettingPrefs(private val context: Context) {

    companion object {
        private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settingPrefs")
        private val soundKey = booleanPreferencesKey("sound")
        private val vibrateKey = booleanPreferencesKey("vibrate")
    }

    val getSound: Flow<Boolean>
        get() = context.dataStore.data.map {
            it[soundKey] ?: true
        }

    suspend fun setSound(value: Boolean) {
        context.dataStore.edit { it[soundKey] = value }
    }

    val getVibration: Flow<Boolean>
        get() = context.dataStore.data.map {
            it[vibrateKey] ?: true
        }

    suspend fun setVibration(value: Boolean) {
        context.dataStore.edit { it[vibrateKey] = value }
    }
}
like image 108
Mohd Naushad Avatar answered Oct 13 '22 05:10

Mohd Naushad


You can use different key for different user or manual keep DataStore singleton.


For exception:

java.lang.IllegalStateException: There are multiple DataStores active for the same file: [...] You should either maintain your DataStore as a singleton or confirm that there is no two DataStore's active on the same file (by confirming that the scope is cancelled).

androidx.datastore:datastore-*:1.0.0-alpha07 is released.

Put this at the top level of your kotlin file so there is only one instance of it.

private val Context.dataStore by preferencesDataStore("settings")

class Xxx{

}

https://developer.android.com/jetpack/androidx/releases/datastore#1.0.0-alpha07.

The Context.createDataStore extension function has been removed and replaced with globalDataStore property delegate. Call globalDataStore once at the top level in your kotlin file. For example:

val Context.myDataStore by dataStore(...) 

Put this at the top level of your kotlin file so there is only one instance of it. (I57215, b/173726702)

like image 6
user4097210 Avatar answered Oct 13 '22 06:10

user4097210


At the moment I was posting this question I found a solution to this problem. In order to solve my problem I needed to combine my previous two solutions. So on logout I unload the DataStorageRepository and on login I reload it again. I also needed to create a CoroutineScope that I cancel on logout.

My Module

val loggedInModule = module {
    single { DataStorageRepository(get(), get()) }
}

I created a scope and passed it to the DataStore

var loggedInScope: CoroutineScope = CoroutineScope(Dispatchers.Default)

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = userRepository.currentRegistration().name, scope = loggedInScope)

On Login

loggedInScope = CoroutineScope(Dispatchers.Default)
loadKoinModules(loggedInModule)

On Logout

loggedInScope.cancel()
unloadKoinModules(loggedInModule)
like image 2
Wirling Avatar answered Oct 13 '22 05:10

Wirling