diff --git a/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt
new file mode 100644
index 0000000000..5b7988b76f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt
@@ -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
+ *
+ * val Context.dataStoreProvider by dataStoreProvider()
+ *
+ */
+fun dataStoreProvider(): ReadOnlyProperty DataStore> {
+ return MappedPreferenceDataStoreSingletonDelegate()
+}
+
+private class MappedPreferenceDataStoreSingletonDelegate : ReadOnlyProperty DataStore> {
+
+ private val dataStoreCache = ConcurrentHashMap>()
+ private val provider: (Context) -> (String) -> DataStore = { context ->
+ { key ->
+ dataStoreCache.getOrPut(key) {
+ PreferenceDataStoreFactory.create {
+ context.applicationContext.preferencesDataStoreFile(key)
+ }
+ }
+ }
+ }
+
+ override fun getValue(thisRef: Context, property: KProperty<*>) = provider.invoke(thisRef)
+}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
index 1063d30a41..b8b367b740 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
@@ -23,7 +23,10 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange
import androidx.core.content.ContextCompat
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
import dagger.hilt.EntryPoints
+import im.vector.app.core.datastore.dataStoreProvider
import im.vector.app.core.di.SingletonEntryPoint
import kotlin.math.roundToInt
@@ -50,3 +53,5 @@ fun Context.getTintedDrawable(@DrawableRes drawableRes: Int,
private fun Float.toAndroidAlpha(): Int {
return (this * 255).roundToInt()
}
+
+val Context.dataStoreProvider: (String) -> DataStore by dataStoreProvider()
diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
index d32cef604b..62d360f5f7 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
@@ -179,7 +179,7 @@ class DefaultVectorAnalytics @Inject constructor(
posthog?.identify(REUSE_EXISTING_ID, identity.getProperties().toPostHogProperties(), IGNORED_OPTIONS)
}
- private fun Map?.toPostHogProperties(): Properties? {
+ private fun Map?.toPostHogProperties(): Properties? {
if (this == null) return null
return Properties().apply {
diff --git a/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt b/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
index c6acb3b87a..2797734343 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/itf/VectorAnalyticsEvent.kt
@@ -18,5 +18,5 @@ package im.vector.app.features.analytics.itf
interface VectorAnalyticsEvent {
fun getName(): String
- fun getProperties(): Map?
+ fun getProperties(): Map?
}
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
index 1cc433aa7e..99f1fadfc4 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Identity.kt
@@ -55,9 +55,9 @@ data class Identity(
override fun getName() = "Identity"
- override fun getProperties(): Map? {
- return mutableMapOf().apply {
- ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
+ override fun getProperties(): Map? {
+ return mutableMapOf().apply {
+ put("ftueUseCaseSelection", null)
}.takeIf { it.isNotEmpty() }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
index ce85eeeb98..a2f3196979 100644
--- a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
+++ b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt
@@ -17,44 +17,42 @@
package im.vector.app.features.session
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.stringPreferencesKey
-import androidx.datastore.preferences.preferencesDataStore
+import im.vector.app.core.extensions.dataStoreProvider
import im.vector.app.features.onboarding.FtueUseCase
import kotlinx.coroutines.flow.first
import org.matrix.android.sdk.internal.util.md5
/**
- * Local storage for:
+ * User session scoped storage for:
* - messaging use case (Enum/String)
*/
class VectorSessionStore constructor(
- private val context: Context,
+ context: Context,
myUserId: String
) {
- private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_session_store_${myUserId.md5()}")
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) }
}
suspend fun setUseCase(useCase: FtueUseCase) {
- context.dataStore.edit { settings ->
+ dataStore.edit { settings ->
settings[useCaseKey] = useCase.persistableValue
}
}
suspend fun resetUseCase() {
- context.dataStore.edit { settings ->
+ dataStore.edit { settings ->
settings.remove(useCaseKey)
}
}
suspend fun clear() {
- context.dataStore.edit { settings -> settings.clear() }
+ dataStore.edit { settings -> settings.clear() }
}
}