2

I have the following SharedFlow in my viewmodel -

class HeroesViewModel(private val heroesRepositoryImpl: HeroesRepositoryImpl) : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()

    private val _uiAction = MutableSharedFlow<UiAction>()
    val uiAction = _uiAction.asSharedFlow()

 sealed class UiAction {
        data class NavigateToHeroesDetails(val heroModel: HeroModel) : UiAction()
    }

And I implement the observing of it in my Composable screen -

fun DashboardScreen(
    navigator: DestinationsNavigator,
    viewModel: HeroesViewModel = get()
) {
    val uiState by viewModel.uiState.collectAsState()
    val uiAction by viewModel.uiAction.collectAsState(initial = null)

    when (uiAction) {
        is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
            navigator.navigate(HeroDetailsScreenDestination(uiAction.heroModel)) // Here is where I get the compiler error
        }
        null -> Unit
    }

When trying to use the information from the action, the compiler gives me the following error -

Smart cast to 'HeroesViewModel.UiAction.NavigateToHeroesDetails' is impossible, because 'uiAction' is a property that has open or custom getter

What would be the correct way verify that the variable is indeed of the type I want?

Alon Shlider
  • 1,187
  • 1
  • 16
  • 46

2 Answers2

2

So the answer that helped me to achieve my goal was to initiate a coroutine using the LaunchedEffect block, and inside it collecting the latest UI action -

LaunchedEffect(key1 = "") {
        viewModel.uiAction.collect { uiAction ->
            when (uiAction) {
                is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
                    navigator.navigate(HeroDetailsScreenDestination(uiAction.heroModel))
                }
            }
        }
    }
Alon Shlider
  • 1,187
  • 1
  • 16
  • 46
1

If you want to keep the by delegate then a simple way is to assign to a val inside the when expression

    val uiAction by viewModel.uiAction.collectAsState(initial = null)
    
    @Suppress("UnnecessaryVariable")
    when (val action = uiAction) {
        is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
            navigator.navigate(HeroDetailsScreenDestination(action.heroModel))
        }
        null -> Unit
    }

There are other ways as well

  • with(uiAction) { when (this) { ... } }
  • uiAction.run { when (this) { ... } }
  • uiAction.let { when (it) { ... } }

If you don't need the by delegate, then you can do

    val uiAction = viewModel.uiAction.collectAsState(initial = null)

    when (val action = uiAction.value) {
        is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
            navigator.navigate(HeroDetailsScreenDestination(action.heroModel))
        }
        null -> Unit
    }

And if you just need read-only access in the current scope, then you can simplify it to

    val uiAction = viewModel.uiAction.collectAsState(initial = null).value

    when (uiAction) {
        is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
            navigator.navigate(HeroDetailsScreenDestination(uiAction.heroModel))
        }
        null -> Unit
    }
Ma3x
  • 5,761
  • 2
  • 17
  • 22