Merge pull request #5117 from vector-im/feature/adm/use-case-store-crash

Handling use case screen in sanity tests
This commit is contained in:
Benoit Marty 2022-02-03 00:53:20 +01:00 committed by GitHub
commit 3e212655eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 75 additions and 15 deletions

View File

@ -0,0 +1,57 @@
/*
* 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
* Based on https://androidx.tech/artifacts/datastore/datastore-preferences/1.0.0-source/androidx/datastore/preferences/PreferenceDataStoreDelegate.kt.html
*
* Makes use of a ReadOnlyProperty in order to provide a simplified api on top of a Context
* ReadOnlyProperty allows us to lazily access the backing property instead of requiring it upfront as a dependency
* <pre>
* val Context.dataStoreProvider by dataStoreProvider()
* </pre>
*/
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.applicationContext.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

@ -179,7 +179,7 @@ class DefaultVectorAnalytics @Inject constructor(
posthog?.identify(REUSE_EXISTING_ID, identity.getProperties().toPostHogProperties(), IGNORED_OPTIONS) posthog?.identify(REUSE_EXISTING_ID, identity.getProperties().toPostHogProperties(), IGNORED_OPTIONS)
} }
private fun Map<String, Any>?.toPostHogProperties(): Properties? { private fun Map<String, Any?>?.toPostHogProperties(): Properties? {
if (this == null) return null if (this == null) return null
return Properties().apply { return Properties().apply {

View File

@ -18,5 +18,5 @@ package im.vector.app.features.analytics.itf
interface VectorAnalyticsEvent { interface VectorAnalyticsEvent {
fun getName(): String fun getName(): String
fun getProperties(): Map<String, Any>? fun getProperties(): Map<String, Any?>?
} }

View File

@ -55,9 +55,9 @@ data class Identity(
override fun getName() = "Identity" override fun getName() = "Identity"
override fun getProperties(): Map<String, Any>? { override fun getProperties(): Map<String, Any?>? {
return mutableMapOf<String, Any>().apply { return mutableMapOf<String, Any?>().apply {
ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) } put("ftueUseCaseSelection", null)
}.takeIf { it.isNotEmpty() } }.takeIf { it.isNotEmpty() }
} }
} }

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() }
} }
} }