I created a usecase that receives data and emits t...
# compose
p
I created a usecase that receives data and emits the data on a flow. I'm collecting the usecase in a corutine in my viewmodel:
Copy code
private fun updateUiState() {
    viewModelScope.launch {
        sensorsDataUseCase.invoke().collect { sensorsData ->
            _uiState.update { currentState -> currentState.copy(sensorsData = sensorsData) }
        }
    }
}
The problem here is that when the screen goes background, the flow is still emitting data. How can I stop collecting the data when the viewmodel screen is in foreground? I have a uistate
onScreen: Boolean
variable which is se to true on screen onResume and to false on screen onPause, so it seems to be a perfect fit for this case. But I don't know how to use it to stop collecting the data, or how to cancel it by a cleaner way
I tryed adding a
private var sensorsDataJob: Job? = null
variable in the viewmodel which stores the corutine job that launches the flow recollection, and I cancel it when onResume gets notified to the viewmodel, it seems to work, but I'm not sure this is the correct way to do things
this is how I notify onresume and onpause to the viewmodel from my screen composable:
Copy code
LifecycleResumeEffect(Unit) {
    vm.onResume()
    onPauseOrDispose {
        vm.onPause()
    }
}
s
p
I hope not, that issue was not solved, and it was related to a while true
on the other hand I'm using the onresume and onpause that I added for trying to find a solution for that issue you linked
but it's not the same, here I'm trying to collect from a flow, not to iterate a while true
here, I have some items that are generated by a listener, so the only way I found to pass those items to the viewmodel was with a flow in which I emit values every time the listener is called with a new item
s
The answer is still the same. Create a flow which is cold and turned to StateFlow using .stateIn() with a WhileSubscribed strategy. And use collectAsStateWithLifecycle so there is no collector when the lifecycle isn't resumed
☝🏼 1
p
I really tried it, even I tried with your proposal, but I wasn't able to achieve it, so I did with those onresume/onpause methods
I mean, I appreciate your efforts, but I'm not able to apply your proposal
is it wrong the strategy I followed here?
s
Well, it'd then be best to show exactly what you tried to do when using stateIn and exactly how it failed to work, so we can fix the problem there I would say. I wouldn't suggest you store job objects and do onPause and onResume from your composable, if that's what you're asking.
p
I understand, but on the other question I pasted the code with my try and no one answered, so I managed to do it without your proposed solution
here I will face exactly the same problem, but I'll try this weekend
on the other hand, I really don't know how to make your solution work with my code
if I'm not wrong, collectAsStateWithLifecycle should be called from a screen, not from a viewmodel
and the screen doesn't access to the flow or the usecase
which are in the viewmodel
the flow, is in the usecase, not in the viewmodel
I'm not sure how your strategy should be applied
I understand you see it as an easy solution, but I don't get the point. Happened the same with the other link you posted
s
If you don't post your code here I am afraid I won't be able to help any more than just saying what I said before.
p
sorry for the delay, I have this method:
Copy code
private fun updateUiState() {
        viewModelScope.launch {
            // on start, update everything
            _uiState.update { currentState ->
                currentState.copy(
                    summaryPageContent = specificationsUseCase.getSummaryInfo(),
                    systemPageContent = specificationsUseCase.getSystemInfo(),
                    loading = false
                )
            }

            // observe changes in current page of uistate pager state, to enable or disable sensor data
            // collection if the current page is sensor page
            launch {
                snapshotFlow { _uiState.value.pagerState.currentPage }
                    .distinctUntilChanged()
                    .collect { currentPage ->
                        if (Page.entries[currentPage] == Page.SENSORS) {
                            startSensorsDataCollection()
                        } else {
                            stopSensorsDataCollection()
                        }
                    }
            }

            // later, after each 500 ms, conditional update
            while (isActive && _uiState.value.onScreen) {
                delay(500)
                _uiState.update { currentState ->
                    val currentPage = Page.entries[currentState.pagerState.currentPage]

                    currentState.copy(
                        summaryPageContent = if (shouldUpdatePage(currentPage, Page.SUMMARY)) specificationsUseCase.getSummaryInfo() else currentState.summaryPageContent,
                        systemPageContent = if (shouldUpdatePage(currentPage, Page.SYSTEM)) specificationsUseCase.getSystemInfo() else currentState.systemPageContent,currentState.sensorsPageContent,
                        loading = false
                    )
                }
            }
        }
    }
it's even longer, but I reduced to simplify
I start updates and stop updates when composable detects onresume and onpause with this:
Copy code
fun onResume() {
    _uiState.update { currentState -> currentState.copy(onScreen = true) }
    updateUiState()
}

fun onPause() {
    _uiState.update { currentState -> currentState.copy(onScreen = false) }
    stopSensorsDataCollection()
}
private fun stopSensorsDataCollection() {
    sensorsDataJob?.cancel()
    sensorsDataJob = null
}
and this is how I detect onresume and onpause in the composable:
Copy code
LifecycleResumeEffect(Unit) {
    vm.onResume()
    onPauseOrDispose {
        vm.onPause()
    }
}
how can this be passed to your proposal?
they key lines are the call to updateUiState() and startSensorsDataCollection() and stopSensorsDataCollection()
I forgot to show this function, this is how I start to collect sensors data
Copy code
private fun startSensorsDataCollection() {
    if (sensorsDataJob?.isActive == true) return
    sensorsDataJob = viewModelScope.launch {
        sensorsDataUseCase.invoke().collect { sensorsData ->
            _uiState.update { currentState ->
                currentState.copy(sensorsData = sensorsData.toList())
            }
        }
    }
}
I use this variable to be able to stop and start the job for collecting data, only when the screen is in foreground and the page is the page for sensors (it's a viewpager with various pages)
Copy code
// job used for store sensors data flow corutine and being able to cancel recollection when screen in background
private var sensorsDataJob: Job? = null
if this can be simplified with your proposal, I'll be very happy to migrate it, but I simply don't know how to migrate this to your proposal
if you need something more @Stylianos Gakis please tell me
Hi, I achieved a migration to another approach that doesn't require onResume and onPause to be propagated to the viewmodel, please @Stylianos Gakis can you tell me if this is a better approach? also, please, can you show me how can this be migrated to your cold flow proposal? This is a summary of what this code does: the ViewModel manages its UI state with a MutableStateFlow, periodically updating system information every 500ms and only running these updates while the UI is active (detected via subscriptionCount). It also observes the current page (automatically starting sensor data collection when the sensor page is active and stopping it when not or when the app goes to the background) ensuring that both updates and sensor data gathering run only when needed.
Copy code
private val _uiState = MutableStateFlow(SpecificationsScreenUiState(loading = true))
val uiState: StateFlow<SpecificationsScreenUiState> = _uiState

// Job for periodic UI updates
private var periodicUpdateJob: Job? = null
// Job for sensor data collection
private var sensorsDataJob: Job? = null

init {
    // Observe changes in the pager state to manage sensor data collection
    viewModelScope.launch {
        snapshotFlow { _uiState.value.pagerState.currentPage }
            .distinctUntilChanged()
            .collect { currentPage ->
                if (Page.entries[currentPage] == Page.SENSORS) {
                    startSensorsDataCollection()
                } else {
                    stopSensorsDataCollection()
                }
            }
    }
    // Automatically start or stop periodic updates based on active uiState subscribers.
    viewModelScope.launch {
        _uiState.subscriptionCount.collect { count ->
            if (count > 0) {
                startPeriodicUpdateJob()
                // Explicitly check if current page is sensors and restart sensor collection
                if (Page.entries[_uiState.value.pagerState.currentPage] == Page.SENSORS) {
                    startSensorsDataCollection()
                }
            } else {
                stopPeriodicUpdateJob()
                stopSensorsDataCollection() // Stop sensor collection when not visible
            }
        }
    }
}

private fun startSensorsDataCollection() {
    if (sensorsDataJob?.isActive == true) return
    sensorsDataJob = viewModelScope.launch {
        sensorsDataUseCase.invoke().collect { sensorsData ->
            _uiState.update { currentState ->
                currentState.copy(sensorsData = sensorsData.toList())
            }
        }
    }
}

private fun stopSensorsDataCollection() {
    sensorsDataJob?.cancel()
    sensorsDataJob = null
}

// This job performs a full update initially and then periodic updates every 500 ms.
private fun startPeriodicUpdateJob() {
    if (periodicUpdateJob?.isActive == true) return
    periodicUpdateJob = viewModelScope.launch {
        // Perform an initial complete update.
        updateAllPages()
        // Then, periodically update only the visible pages.
        while (isActive) {
            delay(500)
            updateOnlyVisiblePages()
        }
    }
}

private fun stopPeriodicUpdateJob() {
    periodicUpdateJob?.cancel()
    periodicUpdateJob = null
}