ComposeGears / Tiamat

Simple Compose multiplatform navigation library
https://composegears.github.io/Tiamat/
Apache License 2.0
124 stars 0 forks source link

[Question] How to navigate from ViewModel? #11

Closed rovkinmax closed 1 month ago

rovkinmax commented 2 months ago

Hello! Do you have an understanding of how to implement navigation from a ViewModel? I've studied the documentation and examples, but they all describe quite simple cases where all the navigation is done within compose functions. In slightly more complex applications, at least in my case, a click on a UI element can hide a lot of logic (form validation, permissions checking, network requests, etc.), and to navigate further, the ViewModel needs to expose something to let the compose function know that it's time to change the screen. May be you have some workaround for that?

vkatz commented 2 months ago

@rovkinmax HI, There is at least 2 options in your case 1) You can create State / flow object inside viewmodel (eg: muteableState<Destination?>(null)) and operate it. Then observe it in the composable fun + LauncheEffect that navigate on value changed. 2) You may pass NavController inside viewmodel (in the LaunchEffect) and call nav functions inside model (nav functions are NOT compose and may be called at any place, just be carefull about threads)

vkatz commented 2 months ago

@rovkinmax here is simple example of case#2

class ScreenModel : TiamatViewModel() {
    private var nc: NavController? = null

    fun bind(nc: NavController?) {
        this.nc = nc
    }

    fun navigate(){
        nc?.navigate(MainScreen)
    }
}

val Screen by navDestination<Unit> {
    val nc = navController()
    val model = rememberViewModel { ScreenModel() }
    DisposableEffect(nc) {
        model.bind(nc)
        onDispose {
            model.bind(null)
        }
    }
    // ...
    Button(onClick = model::navigate){
        Text("button")
    }
}
rovkinmax commented 2 months ago

@vkatz Is it save to keep NavController inside VM?

vkatz commented 2 months ago

@rovkinmax it will not kept inside VM in the example above

 DisposableEffect(navController) {
        model.bind(navController)
        onDispose {
            model.bind(null)  // <- we cleare it here, so you will not be able to navigate if this screen is not "on screen"
        }
    }

Answering exactly your question: no, it is not safe, navController instance may be recreated during navigation (state will be restored, yet instance obj may be different, that's why it is better to attach/detach via DisposableEffect)