'Handling commands from the viewmodel to the UI

The peculiarity of this application is that every time a user does something (except common things like typing) the application must check with an authority that they are indeed allowed to perform that action.

For example, let us say that the user wishes to see their profile (which is on the top bar)

the Composable screen looks something like this:

@Composable
fun HomeScreen(
    navController: NavController,
    vm: HomeViewModel = hiltViewModel()
) {
    val state = vm.state.value
    val scaffoldState = rememberScaffoldState()
    HomeScreen(state, scaffoldState, vm::process)
}

@Composable
fun HomeScreen(state: HomeState, scaffoldState: ScaffoldState, event: (HomeEvent) -> Unit) {
    Scaffold(
        scaffoldState = scaffoldState,
        modifier = Modifier.fillMaxSize(),
        topBar = {
            TopAppBar(
                title = {
                    Text("Hello world")
                },
                actions = {
                    IconButton(onClick = {
                        event.invoke(HomeEvent.ShowProfile)
                    }) {
                        Icon(
                            painter = painterResource(id = R.drawable.ic_person),
                            contentDescription = stringResource(id = R.string.profile)
                        )
                    }
                }
            )
        }
    ) {
    }
}

the view model receives it like so:

@HiltViewModel
class HomeViewModel @Inject constructor(app: Application, private val checkAllowed: CheckAllowed): AndroidViewmodel(app) {
    val state = mutableStateOf(HomeState.Idle)

    fun process(event:HomeEvent) {
        when(event) {
            HomeEvent.ShowProfile -> {
               state.value = HomeState.Loading
               viewModelScope.launch {
                   try {
                       val allowed = checkAllowed(Permission.SeeProfile) //use case that checks if the action is allowed
                       if (allowed) {

                       } else {

                       }
                   } finally {
                        state.value = HomeState.Idle
                   }
               }
            }

        }
    }
}

I now have to send a command to the ui, to either show a snackbar with the error or navigate to the profile page.

I have read a number of articles saying that compose should have a state, and the correct way to do this is make a new state value, containing the response, and when the HomeScreen receives it , it will act appropriately and send a message back that it is ok

I assume something like this : in the viewmodel

 val command = mutableStateOf<HomeCommand>(HomeCommand.Idle)

 fun commandExecuted() {
     command.value = HomeCommand.Idle
 }

and inside the HomeScreen

val command = vm.command.value
try {
    when (command) {
        is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
        is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)

    }
}finally {
   vm.commandExecuted()
}

but the way I did it is using flows like so:

inside the viewmodel:

private val _commands = MutableSharedFlow<HomeCommand>(0, 10, BufferOverflow.DROP_LATEST)
val commands: Flow<HomeCommand> = _commands

and inside the HomeScreen:

LaunchedEffect(key1 = vm) {
    [email protected] { command ->
        when (command) {
            is HomeCommand.ShowProfile -> navController.navigate("profile_screen")
            is HomeCommand.ShowSnackbar -> scaffoldState.snackbarHostState.showSnackbar(command.message, "Dismiss", SnackbarDuration.Indefinite)

    }
}

This seems to work, but I am afraid there may be a memory leak or something I'm missing that could cause problems

Is my approach correct? Should I change it to state as in the first example? can I make it better somehow?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source