简体   繁体   中英

Provide preferences datastore with Hilt

I was trying to provide a common DataStore<Preferences> so that the same preference file could be used in multiple places but I got the helpful error message:

Cannot find symbol: DaggerMyApplication_HiltComponents_SingletonC.builder()

@Module
@InstallIn(ApplicationComponent::class)
object DataStoreModule {
    
    @Provides
    fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> = context.createDataStore("settings")
}

I can however do the following and use it within an @Inject constructor.

@Singleton
class DataStoreProvider @Inject constructor(@ApplicationContext context: Context) {

    val dataStore: DataStore<Preferences> = context.createDataStore("settings")
}

I assume that the extension createDataStore is doing something that Hilt does not like but I'd appreciate an explanation of what is going on even if the problem is not solvable.

This worked for me:

    @Provides
    @Singleton
    fun dataStore(@ApplicationContext appContext: Context): DataStore<Preferences> =
        appContext.createDataStore("settings")

The idea is putting @Singleton behind the provider method.


Update on Feb 9, 2021:
Preferably, create a manager and provide that:

class DataStoreManager(appContext: Context) {

    private val settingsDataStore = appContext.createDataStore("settings")

    suspend fun setThemeMode(mode: Int) {
        settingsDataStore.edit { settings ->
            settings[Settings.NIGHT_MODE] = mode
        }
    }

    val themeMode: Flow<Int> = settingsDataStore.data.map { preferences ->
        preferences[Settings.NIGHT_MODE] ?: AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
    }

}

AppModule :

@InstallIn(SingletonComponent::class)
@Module
class AppModule {
    @Provides
    @Singleton
    fun dataStoreManager(@ApplicationContext appContext: Context): DataStoreManager =
        DataStoreManager(appContext)

Update on March 20, 2021:
Version 1.0.0-alpha07

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

class DataStoreManager(appContext: Context) {

    private val settingsDataStore = appContext.dataStore

    suspend fun setThemeMode(mode: Int) {
        settingsDataStore.edit { settings ->
            settings[Settings.NIGHT_MODE] = mode
        }
    }

    val themeMode: Flow<Int> = settingsDataStore.data.map { preferences ->
        preferences[Settings.NIGHT_MODE] ?: AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
    }
}

Update on May 1, 2021: @Florian is totally right, I had forgotten that.

Remove dataStoreManager provider. Then,

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

@Singleton //You can ignore this annotation as return `datastore` from `preferencesDataStore` is singletone
class DataStoreManager @Inject constructor(@ApplicationContext appContext: Context) {

    private val settingsDataStore = appContext.dataStore

    suspend fun setThemeMode(mode: Int) {
        settingsDataStore.edit { settings ->
            settings[Settings.NIGHT_MODE] = mode
        }
    }

    val themeMode: Flow<Int> = settingsDataStore.data.map { preferences ->
        preferences[Settings.NIGHT_MODE] ?: AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
    }

}

As Dr.jacky mention, create a manager is now recommended way, but you can still use PreferenceDataStoreFactory and create Preferences DataStore singleton:

@Provides
@Singleton
fun providePreferencesDataStore(@ApplicationContext appContext: Context): DataStore<Preferences> =
    PreferenceDataStoreFactory.create(
        produceFile = {
            appContext.preferencesDataStoreFile(PREFERENCES_STORE_NAME)
        }
    )

I used DataStore<Preferences> with Hilt as following.

PersistenceModule.kt

@Module
@InstallIn(SingletonComponent::class)
object PersistenceModule {

    @Provides
    @Singleton
    fun provideDataStoreManager(@ApplicationContext context: Context): DataStoreManager {
        return DataStoreManager(context)
    }
}

DataStoreManager.kt

class DataStoreManager @Inject constructor(@ApplicationContext private val context: Context) {

    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(STORE_NAME)

    private suspend fun <T> DataStore<Preferences>.getFromLocalStorage(
        PreferencesKey: Preferences.Key<T>, func: T.() -> Unit) {
        data.catch {
            if (it is IOException) {
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[PreferencesKey]
        }.collect {
            it?.let { func.invoke(it as T) }
        }
    }

    suspend fun <T> storeValue(key: Preferences.Key<T>, value: T) {
        context.dataStore.edit {
            it[key] = value
        }
    }

    suspend fun <T> readValue(key: Preferences.Key<T>, responseFunc: T.() -> Unit) {
        context.dataStore.getFromLocalStorage(key) {
            responseFunc.invoke(this)
        }
    }
}

ViewModel.kt

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val dataStore: DataStoreManager
) : LiveCoroutinesViewModel() {

    fun readNextReviewTime() {
        viewModelScope.launch {
            dataStore.readValue(nextReviewTime) {
                // Here you can do something with value.
            }
        }
    }
}

Update

@HiltViewModel
class TranslateViewModel @Inject constructor(
    definitionRepository: DefinitionRepository,
    translateRepository: TranslateRepository,
    val dataStoreManager: DataStoreManager
) : LiveCoroutinesViewModel() {

    init {
        readValueInViewModelScope(sourceLanguage, "ta") { // use value here }
        readValueInViewModelScope(targetLanguage, "si") { // use value here }
    }

    private fun <T> readValueInViewModelScope(key: Preferences.Key<T>, defaultValue: T, onCompleted: T.() -> Unit) {
        viewModelScope.launch {
            dataStoreManager.readValue(key) {
                if (this == null) {
                    storeValueInViewModelScope(key, defaultValue)
                } else {
                    onCompleted.invoke(this)
                }
            }
        }
    }

    fun <T> storeValueInViewModelScope(key: Preferences.Key<T>, value: T) {
        viewModelScope.launch {
            dataStoreManager.storeValue(key, value)
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM