Android compose collectAsStateWithLifecycle stops collecting values after composable death

437 Views Asked by At

I have a composable widget that uses rememberSaveable to survive death after the app is going to the background or the screen goes to the backstack after the bottom navigation changes. This is the code of the widget:

    val banner = bannerViewModel.bannerStateFlow.collectAsStateWithLifecycle(null)
    val rememberedBanner = rememberSaveable { banner }

On the other side, the widget uses a viewmodel that connects to a use case and then to a repository to gather the information, this is the code in the viewmodel:

@HiltViewModel
internal class BannerViewModel @Inject constructor (
    private val savedStateHandle: SavedStateHandle,
    private val getBannerUseCase: GetBannerUseCase
): ViewModel() {

    @OptIn(ExperimentalCoroutinesApi::class)
    val bannerStateFlow = savedStateHandle.getStateFlow<Banner?>(
        key = "bannerInfoResourceId",
        initialValue = null
    ).flatMapLatest {
        getBannerUseCase()
    }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = null
    ).onStart {
        println("bannerFromFlow onStart - ViewModel")
    }.onCompletion {
        println("bannerFromFlow onCompletion - ViewModel")
    }
}

The repository connects to a Firebase Realtime Database datasource and stays connected using a flow, everything works great, you change a value in Firebase and instantly I see that change on the UI. But once I navigate other app's section and then I return to this widget, the UI is restored from the savedstate (you can see that the widget is not recomposed) but the new values changed in Firebase are no longer updated in the UI. I debugged the flow and seems to be working and receiving the new values, like if the collectAsStateWithLifecycle is no longer collecting the values from the flow.

Any clues?

1

There are 1 best solutions below

0
Jan Itor On

The rememberedBanner saves the State created by bannerViewModel.bannerStateFlow.collectAsStateWithLifecycle(null) on initial composition and never updates it, meanwhile the State stored in banner will be updated every time the screen is recreated.

What you probably want is to remember the value, not the State itself. To keep rememberedBanner updated, you also should specify the value that, when changed, will re initialize the rememberSaveable state.

val rememberedBanner = rememberSaveable(banner.value) { banner.value }

Although, that seems like pointless extra work, just using banner.value could be enough.