androidkotlinandroid-jetpack-datastore

About Collecting Values from DataStore


I'm currently using DataStore to store and collect values to create a UI.

There are flows for collecting Volume and Media Path.

I understand that when I store a value, it should be collectable in the corresponding Flow, and it currently behaves that way.

However, when I store Volume, the Media Path Flow also emits, which shouldn't happen.

    suspend fun storeVolume(volume: Int) {
        dataStore.edit {
            it[VOLUME] = volume
        }
    }

    fun getVolume(): Flow<Int> = dataStore.data.map {
        it[VOLUME] ?: DEFAULT_VOLUME_SIZE
    }

    suspend fun storeMediaPath(path: String) {
        dataStore.edit {
            it[MEDIA] = path
        }
    }

    fun getMediaFile(): Flow<File?> = dataStore.data.map {
        it[MEDIA]?.let { path -> File(path) }
    }
init {
        viewModelScope.launch {
            dataStoreHelper.getVolume().collectLatest { volume ->
                _mainUiState.update { it.copy(volume = volume) }
            }
        }
        viewModelScope.launch {
            dataStoreHelper.getMediaFile().collectLatest { file ->
                _mainUiState.update { it.copy(alarmMedia = file) }
            }
        }
    }

I couldn't find any documentation related to this.

It seems that when I store a value, it gets emitted to all flows collecting that value.

Therefore, it seems to be performing unnecessary tasks, and I would like to optimize this.


Solution

  • The flow from the data store (dataStore.data) will emit any time there is any change in the backing map of data. You need to filter the data in downstream flows so they only emit when the data they are interested in is emitted. This can be done using distinctUntilChanged. Note, this still allows the initial emission that has the current value stored at the time collection begins.

    fun getVolume(): Flow<Int> = dataStore.data.map {
        it[VOLUME] ?: DEFAULT_VOLUME_SIZE
    }.distinctUntilChanged()
    
    fun getMediaFile(): Flow<File?> = dataStore.data.map {
        it[MEDIA]?.let { path -> File(path) }
    }.distinctUntilChanged()
    

    But using get____() for function that have no argument and aren't suspending is an antipattern in Kotlin. These should be properties:

    val volume: Flow<Int> = dataStore.data.map {
        it[VOLUME] ?: DEFAULT_VOLUME_SIZE
    }.distinctUntilChanged()
    
    val mediaFile: Flow<File?> = dataStore.data.map {
        it[MEDIA]?.let { path -> File(path) }
    }.distinctUntilChanged()