9

I use Jetpack Compose and navigation compose and I want to pass id to this viewmodel:

class RecallViewModel(private val id:Long,application: Application):AndroidViewModel(application) {
  ............................
}

Composable function:

I don't know how to get the application in composable function:

@Composable
fun RecallScreen(
    id:Long,
    onEnd:() -> Unit
){
       val recallViewModel = viewModel(factory = RecallViewModelFactory(
id = id,application = "i don't know how to get application"))

}

and factory

class RecallViewModelFactory(private val id:Long,val application: Application):ViewModelProvider.AndroidViewModelFactory(application) {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return RecallViewModel(id,application) as T
    }
}

        composable(
            "${Routes.recall}/{id}",
            arguments = listOf(navArgument("id") { type = NavType.LongType })
        ) {
            RecallScreen(
                id = it.arguments!!.getLong("id"),
                onEnd = {navController.navigateUp()}
            )
        }

Raw Hasan
  • 1,096
  • 1
  • 9
  • 25
Omar Khaled
  • 439
  • 1
  • 4
  • 16
  • 1
    Are you passing your `id` as a `navArgument` to your screen as part of your route? If so, it is already available to your ViewModel via the `SavedStateHandle` that is supported out of the box. Is there a particular reason you're trying to build a manual factory for this? – ianhanniballake Sep 11 '21 at 18:02
  • how can i do this and is this good practice – Omar Khaled Sep 11 '21 at 18:08
  • Can you include the `composable` destination you use for your `RecallScreen`? – ianhanniballake Sep 11 '21 at 18:10

1 Answers1

41

To answer your question: you retrieve the Application from the LocalContext object:

val context = LocalContext.current
val application = context.applicationContext as Application

However, when using Navigation Compose, you don't need to manually pass any arguments to your ViewModel. Instead, you can utilize the built in support for SavedState in ViewModels and add a SavedStateHandle parameter to your ViewModel. The SavedStateHandle is a key/value map that is automatically populated with the arguments of your destination.

This means your ViewModel becomes:

class RecallViewModel(
    application: Application,
    savedStateHandle: SavedStateHandle
):AndroidViewModel(application) {

  // Get your argument from the SavedStateHandle
  private val id: Long = savedStateHandle.get("id")

  ............................
}

And you no longer need to manually parse your ID from the arguments or pass it to your ViewModel:

composable(
    "${Routes.recall}/{id}",
    arguments = listOf(navArgument("id") { type = NavType.LongType })
) {
    RecallScreen(
        onEnd = {navController.navigateUp()}
    )
}
@Composable
fun RecallScreen(
    onEnd:() -> Unit
) {
    val recallViewModel: RecallViewModel = viewModel()
}
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • Typo `savedStateHandle, SavedStateHandle`? – Raw Hasan Sep 12 '21 at 04:45
  • @RawHasan - good catch, yes, I corrected it to `savedStateHandle: SavedStateHandle` (the variable named `savedStateHandle`, of type `SavedStateHandle`). – ianhanniballake Sep 12 '21 at 04:59
  • @ianhanniballake Is it best practice to put the view model initializing into composable?(as you did) Or passing the instance as a parameter?(form activity) Or put the view model as a parameter but initialize/inject it in the parameter?(`@Composable fun RecallScreen(viewModel: RecallViewModel = viewModel()/hiltViewModel()`) – Dr.jacky Oct 17 '21 at 19:36
  • 1
    @Dr.jacky - passing it as a parameter makes it easier to use that component in previews and testing, so that certainly seems like a good idea. The style used in this answer was simply matching the style in the question as to provide the minimal set of changes needed to solve this particular problem. – ianhanniballake Oct 17 '21 at 19:41
  • @ianhanniballake I see. I've read somewhere that suggests don't let your composable depends on ViewModel, pass the final result to it but, as you said, Google does the same(https://developer.android.com/jetpack/compose/state#viewmodel-state);;; But in this way, doesn't it initialize the ViewModel and call the relevant method(for example calling network or db) multiple times on each recomposition(frame refresh as we see right now, even if the orientation is not changed, or the input is not changed, the composable could be called multiple times)? – Dr.jacky Oct 18 '21 at 08:19
  • 1
    is data persisted if we navigate forward and come back in savedStateHandle[]? – Cyph3rCod3r Aug 08 '22 at 07:24