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 to put @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
}
}
@Florian is totally right, I had forgotten that.
Remove dataStoreManager
provider from your DI module. Just:
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)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With