Home > Blockchain >  Pass value of ViewModel to a new Composable screen instance
Pass value of ViewModel to a new Composable screen instance

Time:08-07

I have a composable function declared like this:

fun ScreenA(
    nav: NavController,
    type: SomeTypeObject,
) {
    val vm = getViewModel<SomeTypeObjectViewModel>()
    val state = rememberScaffoldState()
    val scope = rememberCoroutineScope()

    LaunchedEffect(LocalContext.current) {
        when(type) {
             SomeTypeObject.TYPE1 ->{
                vm.updateState("1")
            }
             SomeTypeObject.TYPE2 -> {
               //do something else
        }
    }
}

SomeTypeObjectViewModel contains state variable of my ScreenA like this:

var remeberVal = mutableStateOf<SomeTypeObject?>(null)

Now at some point in another composable function i use my navigationGraph to open another instance of ScreenA, so SomeTypeObjectViewModel gets recreated and remeberVal restes istelf but i want keep and reuse it when new instance of ScreenA is made.

Passing remeberVal as argument using the navigationGraph is not an option since you can only pass Strings, ints or parcelable objects which is not my case, considering that remeberVal has MutableState<SomeTypeObject?> type.

At this point my question is: Is there a way to pass remeberVal to the new instance of ScreenA or to avoid SomeTypeObjectViewModel being reinstantiated after when i re-route to ScreenA using my navigaion graph?

Thank you!

Edit:

my getViewModel() is a Koin function to injevt the ViewModel, the internal code is:

org.koin.androidx.compose ViewModelComposeExtKt.class @Composable
public inline fun <reified T : ViewModel> getViewModel(
    qualifier: Qualifier?,
    owner: ViewModelStoreOwner,
    scope: Scope,
    noinline parameters: ParametersDefinition? /* = (() → ParametersHolder)? */
): T

The navigation graph is made in something like this way:

fun MyNGraph(nav: NavHostController) {

 composable(
            route = Routes.CaseType1.route   "/{someParameters}/",
            arguments = listOf(
                navArgument("someParameters") {},
            ),
        ) { backStackEntry ->
            val someParameters = backStackEntry.arguments?.getString("someParameters")

                someParameters?.let { someParameters ->
                        ScreenA(
                            type = SomeTypeObject.TYPE1, // Notice here, where i change type but use the same screen
                        )
                    }
            }
        }

        composable(
            route = Routes.CaseType2.route   "/{someParameters}/",
            arguments = listOf(
                navArgument("someParameters") {},
            ),
        ) { backStackEntry ->
            val someParameters = backStackEntry.arguments?.getString("someParameters")

                someParameters?.let { someParameters ->
                        ScreenA(
                            type = SomeTypeObject.TYPE2, // Notice here
                        )
                    }
            }
        }
}

CodePudding user response:

You are using Koin for DI, so you can just add a dependency with a broader scope than your SomeTypeObjectViewModel that will hold the state you want to share between different screens/composables or between different VM instances. In that way your VMs have access to a shared state (a shared state holder is usually called a Repository).

class MySharedState {
    // this could also be a MutableState instead of MutableStateFlow
    // but then you are spreading the androidx.compose.runtime dependency
    // to a shared state that should not need to know about Compose
    val typeFlow = MutableStateFlow<SomeTypeObject?>(null)
}

class SomeTypeObjectViewModel(
    val sharedState: MySharedState
): ViewModel() {
    fun updateType(type: SomeTypeObject) {
        sharedState.typeFlow.value = type
    }

    fun updateState(value: String) {
        // your existing logic...

        // call updateType(...) when you want to update the type
    }

    // rest of your ViewModel code
}

Where you are configuring your Koin modules add (if you are using Koin 3.2 )

module {
    // a shared state scoped to the whole app lifecycle
    singleOf(::MySharedState) // <-- add this
    
    viewModelOf(::SomeTypeObjectViewModel) // <-- you probably already have this
}

If you are using Koin < 3.2

module {
    // a shared state scoped to the whole app lifecycle
    single { MySharedState() } // <-- add this
    
    viewModel { SomeTypeObjectViewModel(get()) } // <-- you probably already have this but add one more get()
}

If you also want to access the state in your composables, you can use Flow.collectAsState()

fun ScreenA(
    nav: NavController,
    type: SomeTypeObject,
) {
    val vm = getViewModel<SomeTypeObjectViewModel>()
    val currentType by vm.sharedState.typeFlow.collectAsState()

    // ...
}

CodePudding user response:

by scoping your ViewModel to navigation routes or the navigation graph you can retrieve the same instance of your ViewModel

visit https://developer.android.com/jetpack/compose/libraries#hilt-navigation

@Composable
fun MyApp() {
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

  • Related