Open rovkinmax opened 2 months ago
You can use Kotlin callback functions.
YourViewModel / ScreenModel {
suspend fun loginUser(context: Context, response:(succes: LoginResponseModel?,error: String?) -> Unit){
withContext(Dispatchers.IO){
repository.loginUser() { response, error ->
response?.let {
response(it,null)
}
error?.let {
response(null,it)
}
}
}
}
}
call above function in your compose like
scope.launch{
viewModel.login(context){response, error ->
response?.let{
//Succed navigate Home Screen
}
error?.let{
//failed navigate bottomsheet to show fail message
}
}
}
I've created this solution after small investigation
This is not completed yet, but can show the main idea
Idea is simple: build proxy VoyagerRouter
for voyager navigator and ViewModel, DI provide instance of VoyagerRouter to ViewModel and also to compose component
class VoyagerRouter {
private val sharedFlow = MutableSharedFlow<VoyagerEvent>(extraBufferCapacity = 1)
fun events(): SharedFlow<VoyagerEvent> {
return sharedFlow.asSharedFlow()
}
fun navigateTo(screen: Screen) {
sharedFlow.tryEmit(VoyagerEvent.NavigateTo(screen))
}
fun newRootScreen(screen: Screen) {
sharedFlow.tryEmit(VoyagerEvent.NewRootScreen(screen))
}
fun back() {
sharedFlow.tryEmit(VoyagerEvent.Back)
}
}
sealed class VoyagerEvent {
data class NavigateTo(val screen: Screen) : VoyagerEvent()
data class NewRootScreen(val screen: Screen) : VoyagerEvent()
data object Back : VoyagerEvent()
}
@Composable
fun FlowComponent(
screen: Screen,
scopeName: String = "",
content: @Composable () -> Unit = {},
) = DIScope(scopeName = scopeName) { // DIScope is my own DI wrapper for compose
Navigator(screen) {
val navigator = LocalNavigator.currentOrThrow
// here my way to get router from DI, will depend on yours DI
val router = LocalDIScope.current!!.getInstance<VoyagerRouter>()
LaunchedEffect(scopeName) {
router.events()
.collect { event ->
when (event) {
is VoyagerEvent.NavigateTo -> navigator.push(event.screen)
is VoyagerEvent.NewRootScreen -> navigator.replaceAll(event.screen)
is VoyagerEvent.Back -> navigator.pop()
}
}
}
val currentScreen = navigator.lastItem
navigator.saveableState("currentScreen") {
currentScreen.Content()
}
}
// here will be content for your screen
content()
}
Hello! Do you have an understanding of how to implement navigation from a ScreenModel? 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 ScreenModel 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?