https://kotlinlang.org logo
#flow
Title
# flow
g

Giorgi

03/06/2023, 6:18 PM
Hi. Got a question about
SharingStarted.WhileSubscribed()
, Im on JS target, with compose multiplatform. lets say I have a code like this:
Copy code
class HistoryScreenViewModel : DIAware {
    override val di: DI = kodein
    private val dispatcher by instance<CoroutineDispatcher>(tag = MAIN)
    private val viewModelScope = CoroutineScope(dispatcher)
    private val historyFilesRepository by instance<HistoryFilesRepository>()
    private val settingsRepo by instance<SettingsRepo>()

    val markdownText = historyFilesRepository.selectedFiles
        .combine(settingsRepo.minimumAmountOfVideoClicks, ::YoutubeHistoryParams)
        .map(::parseHistoryToMarkdown)
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
        .onSubscription { println("-- subscribed") }
        .onCompletion { println("-- onCompletion") }
}
There is a
markdownText
StateFlow being created with
WhileSubscribed
argument. My expectation is that when
markdownText
is subscribed, then it will execute all functions above, including
parseHistoryToMarkdown
, then when I switch to another screen it will complete (it does print
"-- onCompletion"
in the console) and keep the cached value. Later, when I go back to same screen, it will emit cached value or if
settingsRepo.minimumAmountOfVideoClicks
changed, then it will recalculate it. But it happens differently. When I switch back to the screen and collect is started then it will execute
parseHistoryToMarkdown
function once again. Should not it emit same value if
settingsRepo.minimumAmountOfVideoClicks
did not change? The documentation says that it keeps replay cache forever by default
k

Kevin Worth

03/06/2023, 8:03 PM
The only way you could keep the value would be to keep it running in a scope that doesn’t get cancelled. Given you are using
viewModelScope
, it appears that changing screens cancels that scope and restarts it when you return to that screen. Have you tried another scope? But also, how crucial is it to run only once? If it’s not very very costly, then you’re probably fine having it stop and start the way it is currently.
g

Giorgi

03/08/2023, 12:07 PM
The problem is that
parseHistoryToMarkdown
function takes 4-5 seconds (it parses 5mb JSON text, sorts them and generates markdown text), so thats why I want it to happen only once. I got closer to the problem now. it was not about the scope, but the state flow. So in my original code
historyFilesRepository.selectedFiles
is a StateFlow, so every time
markdownText
was subscribed it would emit the value anyway. okay. Here is my small failing test
Copy code
@OptIn(ExperimentalCoroutinesApi::class)
class Learning {

    @Test
    fun someRandomLearningTest() = runTest {
        val viewModelScope = CoroutineScope(Dispatchers.Default)
        var numberOfMapExecution = 0

        val data = MutableStateFlow(1)
            .map {
                println("Doing some heavy calculations")
                numberOfMapExecution += 1
                it + 5
            }
            .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
            .onSubscription { println("-- subscribed") }
            .onCompletion { println("-- onCompletion") }

        println(data.take(2).first())
        assertEquals(6, data.first())
        assertEquals(6, data.first())
        assertEquals(1, numberOfMapExecution)
    }
}
what would you recommend here? I dont even know if it wil lbe possible to achieve this while having MutableStateFlows. All I want is
.map
to not run too many times
is there a way to tell
stateIn
function to not resubscribe to above flows?
5 Views