In memory implementation of preferences datastore

92 views Asked by At

I want to create an in memory implementation of DataStore<Preferences>.

val dummyDataStore = object : DataStore<Preferences> {
    val _data = MutableStateFlow(Preferences())
    
    override val data: Flow<Preferences>
        get() = _data

    override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences {
        return transform(_data.value)
    }
}

The problem is that all the constructors for Preferences are internal Cannot access '<init>': it is internal in 'Preferences'.

class PreferencesMock: Preferences() {
    override fun asMap(): Map<Key<*>, Any> {
        TODO("Not yet implemented")
    }

    override fun <T> contains(key: Key<T>): Boolean {
        TODO("Not yet implemented")
    }

    override fun <T> get(key: Key<T>): T? {
        TODO("Not yet implemented")
    }
}

The main motivation is to conveniently use datastores with Jetpack Compose without breaking previews in interactive mode.

val LocalDataStore = staticCompositionLocalOf { dummyDataStore }

@Composable
fun DataStoreProvider(
    dataStore: DataStore<Preferences> = LocalContext.current.settingsDataStore,
    content: @Composable () -> Unit
) {
    CompositionLocalProvider(LocalDataStore.provides(dataStore)) {
        content()
    }
}
interface Preference<V> {
    val key: Preferences.Key<V>

    fun flow(dataStore: DataStore<Preferences>): Flow<V?> =
        dataStore.data.map { value(it) }.distinctUntilChanged()

    suspend fun edit(dataStore: DataStore<Preferences>, newValue: (currentValue: V?) -> V) {
        dataStore.edit {
            it[key] = newValue(value(it))
        }
    }

    ...
}
@Composable
fun <V> Preference<V>.stateOrDefault() =
    flowOrDefault(LocalDataStore.current, LocalContext.current) // default value might need context (darkTheme: (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES)
        .collectAsStateWithLifecycle(initialValue = defaultValue())
1

There are 1 answers

2
Marsroverr On

Accessing DataStores directly in Composable functions is not recommended as composables can update quite frequently, up to every frame of an animation which on high refresh rate phones may be as often as 120 times per second.

I would recommend re-architecting your composables to only take the specific data they need to display, and handle DataStore access in the ViewModel. This way you reduce the amount of times the DataStore has to be accessed, in addition to making it easier to write previews since all you have to do is create an @Preview-annotated function that calls your composable with hardcoded placeholders.

For more info on Compose best practices you can read Thinking in Compose and Follow best practices from the official documentation.