Making cold Flows lifecycle-aware inside ViewModels when they cannot be conflated

41 Views Asked by At

My application usually repeats a specific pattern on its screens:

  • may get some data from some API to build the initial state
  • handle some user actions
  • navigate accordingly

For many screens, using the stateIn operator to transform my cold flows into hot flows works like a charm. It's also pretty easy to test my VMs when I follow this pattern:

val state: StateFlow<UiState> = combine(
        initializationUseCase(),
        someValueChangedFromUi,
    ) { initializationResult, selectedValue ->

        when (initializationResult) {
            Result.Loading -> initialUiState.copy(isLoading = true)

            is Result.Error -> initialUiState.copy(
                isLoading = false,
                error = initializationResult.error,
            )

            is Result.Success -> initialUiState.copy(
                isLoading = false,
                someList = initializationResult.data,
                selectedValue = selectedValue,
            )
        }
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(),
        initialValue = initialUiState,
    )

I struggle when I have a cold flow that cannot be turned into a StateFlow - because this flow may produce repeated data, and I need to process this repeated data. Also, if the user sends the app to the background and opens it again, I shouldn't receive the last value.

I tried to collect this flow on my init block:

private val result = MutableStateFlow("")

    init {
        viewModelScope.launch {
            repository.myDataStream.collect {
                result.update { "the result" }
            }
        }
    }

    val state = combine(
        repository.isSyncing(),
        result,
    ) { isSyncing, result ->

        when {
            isSyncing -> {
                UiState(isLoading = true)
            }

            else -> {
                UiState(myResult = result)
            }
        }
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(),
        initialValue = UiState(),
    )

But this created two new issues:

  • I'll collect myDataStream even if the user leaves my app.
  • I'll store the result in a MutableStateFlow; since it's conflated, repeated results will not trigger combine. I considered setting it to a null value after processing a myDataStream value, but it seems a bit of a hack and error-prone.

I headed this amazing article: https://bladecoder.medium.com/smarter-shared-kotlin-flows-d6b75fc66754, but I'm still struggling to figure out how to solve this.

0

There are 0 best solutions below