providing a way to lazily read dynamic datastore instances

- fixes crash where multiple session store instances attempt to read from the same datastore backing file
This commit is contained in:
Adam Brown 2022-02-01 13:02:41 +00:00
parent 986d9f92e9
commit 741f9fabbb
3 changed files with 63 additions and 10 deletions

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.datastore
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import java.util.concurrent.ConcurrentHashMap
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* Provides a singleton datastore cache
* allows for lazily fetching a datastore instance by key to avoid creating multiple stores for the same file
*/
fun dataStoreProvider(): ReadOnlyProperty<Context, (String) -> DataStore<Preferences>> {
return MappedPreferenceDataStoreSingletonDelegate()
}
private class MappedPreferenceDataStoreSingletonDelegate : ReadOnlyProperty<Context, (String) -> DataStore<Preferences>> {
private val dataStoreCache = ConcurrentHashMap<String, DataStore<Preferences>>()
private val provider: (Context) -> (String) -> DataStore<Preferences> = { context ->
{ key ->
dataStoreCache.getOrPut(key) {
PreferenceDataStoreFactory.create {
context.preferencesDataStoreFile(key)
}
}
}
}
override fun getValue(thisRef: Context, property: KProperty<*>) = provider.invoke(thisRef)
}

View file

@ -23,7 +23,10 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange import androidx.annotation.FloatRange
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import dagger.hilt.EntryPoints import dagger.hilt.EntryPoints
import im.vector.app.core.datastore.dataStoreProvider
import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.di.SingletonEntryPoint
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -50,3 +53,5 @@ fun Context.getTintedDrawable(@DrawableRes drawableRes: Int,
private fun Float.toAndroidAlpha(): Int { private fun Float.toAndroidAlpha(): Int {
return (this * 255).roundToInt() return (this * 255).roundToInt()
} }
val Context.dataStoreProvider: (String) -> DataStore<Preferences> by dataStoreProvider()

View file

@ -17,44 +17,42 @@
package im.vector.app.features.session package im.vector.app.features.session
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import im.vector.app.core.extensions.dataStoreProvider
import im.vector.app.features.onboarding.FtueUseCase import im.vector.app.features.onboarding.FtueUseCase
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.md5
/** /**
* Local storage for: * User session scoped storage for:
* - messaging use case (Enum/String) * - messaging use case (Enum/String)
*/ */
class VectorSessionStore constructor( class VectorSessionStore constructor(
private val context: Context, context: Context,
myUserId: String myUserId: String
) { ) {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_session_store_${myUserId.md5()}")
private val useCaseKey = stringPreferencesKey("use_case") private val useCaseKey = stringPreferencesKey("use_case")
private val dataStore by lazy { context.dataStoreProvider("vector_session_store_${myUserId.md5()}") }
suspend fun readUseCase() = context.dataStore.data.first().let { preferences -> suspend fun readUseCase() = dataStore.data.first().let { preferences ->
preferences[useCaseKey]?.let { FtueUseCase.from(it) } preferences[useCaseKey]?.let { FtueUseCase.from(it) }
} }
suspend fun setUseCase(useCase: FtueUseCase) { suspend fun setUseCase(useCase: FtueUseCase) {
context.dataStore.edit { settings -> dataStore.edit { settings ->
settings[useCaseKey] = useCase.persistableValue settings[useCaseKey] = useCase.persistableValue
} }
} }
suspend fun resetUseCase() { suspend fun resetUseCase() {
context.dataStore.edit { settings -> dataStore.edit { settings ->
settings.remove(useCaseKey) settings.remove(useCaseKey)
} }
} }
suspend fun clear() { suspend fun clear() {
context.dataStore.edit { settings -> settings.clear() } dataStore.edit { settings -> settings.clear() }
} }
} }