Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to inject ViewModel in the screen? #28

Closed
5AbhishekSaxena opened this issue Jan 13, 2022 · 7 comments
Closed

How to inject ViewModel in the screen? #28

5AbhishekSaxena opened this issue Jan 13, 2022 · 7 comments
Labels
question Further information is requested wontfix This will not be worked on

Comments

@5AbhishekSaxena
Copy link

5AbhishekSaxena commented Jan 13, 2022

Hi

I was trying to use the library with the ViewModel with the screen. I usually inject the ViewModel into the screen via arguments as shown below.

SampleScreen

@Composable
@Destination(start = true)
fun SampleScreen(
    viewModel: SampleViewModel,
    navigator: DestinationsNavigator,
) {
         // components
}

When I try the same, I am getting a runtime error.

2022-01-14 03:37:55.843 6495-6495/com.example.project E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.project, PID: 6495
java.lang.RuntimeException: SampleViewModel was requested, but it is not present
    at com.example.project.destinations.SampleScreenDestination.Content(SampleScreenDestination.kt:34)
    at com.ramcosta.composedestinations.DefaultNavHostEngine.CallComposable(DefaultNavHostEngine.kt:144)
    at com.ramcosta.composedestinations.DefaultNavHostEngine.access$CallComposable(DefaultNavHostEngine.kt:30)
    at com.ramcosta.composedestinations.DefaultNavHostEngine$addComposable$1.invoke(DefaultNavHostEngine.kt:104)
    at com.ramcosta.composedestinations.DefaultNavHostEngine$addComposable$1.invoke(DefaultNavHostEngine.kt:103)

Could you please tell me if I missing some steps while setting up the library?

Navigation setup

@Composable
fun NavigationComponent(
    modifier: Modifier = Modifier
) {

    DestinationsNavHost(
        navGraph = NavGraphs.root,
        modifier = modifier
    )
}

How would you suggest injecting the VMs?
How would you use the library with the Hilt library?

@5AbhishekSaxena 5AbhishekSaxena changed the title How to inject ViewModel in the screen while using this library? How to inject ViewModel in the screen? Jan 13, 2022
@raamcosta
Copy link
Owner

raamcosta commented Jan 13, 2022

Hi @5AbhishekSaxena ! 👋

So you have some options, I'll try to go over them.

  1. You can just use a default value for the ViewModel:
@Composable
@Destination(start = true)
fun SampleScreen(
    viewModel: SampleViewModel = hiltViewModel(),
    navigator: DestinationsNavigator,
) {
         // components
}
  1. You can manually call that screen:
@Composable
fun NavigationComponent(
    modifier: Modifier = Modifier
) {

    DestinationsNavHost(
        navGraph = NavGraphs.root,
        modifier = modifier
    ) {
        composable(SampleScreenDestination) {
            SampleScreen(
                viewModel = hiltViewModel(),
                navigator = destinationsNavigator
            )
        }
    }
}

Btw number 2 can be used to pass anything that you may want to pass from the NavHost down (like lets say the ScaffoldState.
AAAND you still don't need to call all your screens manually, you can opt to just manually call the screens you need to pass something explicitly. More info here.
BUT I usually go with 1 for the ViewModel, coz it can be built anywhere, it doesn't need to be constructed at the NavHost level.

Glad you opened this because it reminds me.. I should add a compile time error if you define some parameter that the lib cannot provide 🤔
I'll add that to my todo list.

Let me know if the solutions seem good to you!

@raamcosta
Copy link
Owner

Ahh the compile-time error won't work because people can just be manually calling them. I can improve the error message though :)

@5AbhishekSaxena
Copy link
Author

5AbhishekSaxena commented Jan 13, 2022

Thanks for the quick reply,

I went with method #2 to avoid the default value thingy.

If somebody isn't using hilt, they can refer to this

@Composable
@Suppress("UNCHECKED_CAST")
private fun getSampleScreenViewModel(): SamplelScreenViewModel {
    return viewModel(factory = object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return SampleScreenViewModel() as T
        }
    })
}

and used it like this

@Composable
fun SampleNavigationComponent(
    modifier: Modifier = Modifier
) {

    DestinationsNavHost(
        navGraph = NavGraphs.root,
        modifier = modifier
    ) {
        composable(SampleScreenDestination) {
            SampleScreen(
                viewModel = getSampleScreenViewModel(),
                navigator = destinationsNavigator
            )
        }
    }
}

Also, I found the documentation a bit confusing or missing details, would you mind if I suggest the changes?

@5AbhishekSaxena
Copy link
Author

Ahh the compile-time error won't work because people can just be manually calling them. I can improve the error message though :)

An IDE warning should work just fine.

@raamcosta
Copy link
Owner

Not at all! I'd be thrilled if you could! 🙂
I honestly haven't had the time to refine the documentation to what it should be 😔

Btw if people are not using Hilt, your solution might not be enough if you plan to access the navigation arguments from the SavedStateHandle. For that you need something like this:

    class Factory(
        navBackStackEntry: NavBackStackEntry
    ) : AbstractSavedStateViewModelFactory(
        owner = navBackStackEntry,
        defaultArgs = navBackStackEntry.arguments
    ) {

        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(
            key: String,
            modelClass: Class<T>,
            handle: SavedStateHandle
        ): T {
            return ProfileViewModel(handle) as T
        }
    }
    
    // And to get the VM:
    //...
    val vm = viewModel<ProfileViewModel>(
        factory = ProfileViewModel.Factory(navBackStackEntry)
    )

@5AbhishekSaxena
Copy link
Author

Cool, I'll create a PR with the suggestions for the documentation.

Btw if people are not using Hilt, your solution might not be enough if you plan to access the navigation arguments from the SavedStateHandle.

A really good solution, thanks. I'll update the same in my project.

@raamcosta
Copy link
Owner

I'll close the issue then, feel free to reopen it or add more comments if you want to :)

@raamcosta raamcosta added question Further information is requested wontfix This will not be worked on labels Jan 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants