Jetpack Compose pass parameter to viewModel
Asked Answered
B

9

40

How can we pass parameter to viewModel in Jetpack Compose?

This is my composable

    @Composable
    fun UsersList() {
      val myViewModel: MyViewModel = viewModel("db2name") // pass param like this
    }

This is viewModel

    class MyViewModel(private val dbname) : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData<List<User>>().also {
                loadUsers()
            }
        }
    
        fun getUsers(): LiveData<List<User>> {
            return users
        }
    
        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
Babita answered 15/6, 2021 at 7:55 Comment(1)
More up-to-date approach using DI for the rest of ViewModel parameters: https://mcmap.net/q/395169/-jetpack-compose-pass-parameter-to-viewmodelHughie
R
55

you need to create a factory to pass dynamic parameter to ViewModel like this:

class MyViewModelFactory(private val dbname: String) :
    ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = MyViewModel(dbname) as T
}

then use your factory like this in composable functions:

@Composable
fun UsersList() {
    val myViewModel: MyViewModel =
        viewModel(factory = MyViewModelFactory("db2name")) // pass param like this
}

and now you have access to dbname parameter in your ViewModel:

class MyViewModel(private val dbname:String) : ViewModel() {
    // ...rest of the viewModel logics here
}
Ridinger answered 15/6, 2021 at 8:0 Comment(4)
why this not documented in android website?Babita
This is actually pretty tough to find. If you play with the DataStore Preferences codelabs, of which there are two, one of them (Working with Preferences DataStore, not Preferences DataStore) has an end solution that does make use of this implementation in tying a UserRespository class to a ViewModel but the codelab itself doesn't teach you anything about it and full of discrepencies (codelab not repo). I guess we're expected to know this from the Android View side as its strongly recommended in the View system to always use ViewModelProvider to manage ViewModel instances.Denominationalism
Thanks for this, I created multiple activities in my app assuming its not possible to call viewModel Factory classes in composable functions....Ingaborg
I used this and my app crashes randomly when going in the background and foreground, don't use this.Alyssa
O
21

If you use Navigation component, you get this for free in SavedStateHandle for view model.

Pass the argument to the composable that calls the view model and retrieve it with the same name on view model from saved state handle.

Like this:

On NavHost:

NavHost(
(...)
    composable(
            route = [route string like this $[route]/{$[argument name]}],
            arguments = listOf(
                navArgument([argument name]) { type = NavType.[type: Int/String/Boolean/etc.] }
            )
        ) {
            // access your viewModel in your composable
            val viewModel: MyViewModel = viewModel()
        }
    )
)

On view model:

class ViewModel(savedStateHandle: SavedStateHandle) {

    private val argument = checkNotNull(savedStateHandle.get<[type]>([argument name]))
}

Your argument will magically appear without having a view model factory.

Ostrogoth answered 7/8, 2022 at 16:55 Comment(1)
Best answer, thank you! Pro Android Dev article to explainSurber
H
11

With latest library androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2 is simple as this:

val myViewModel = viewModel {
  MyViewModel(param)
}
Holiness answered 30/11, 2023 at 10:26 Comment(0)
R
10

The other solutions work, but you have to create a factory for each ViewModel which seems overkill.

The more universal solution is like this:

inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) =
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(aClass: Class<T>):T = f() as T
    }

And use it like this:

@Composable
fun MainScreen() {
    val viewModel: MyViewModel = viewModel(factory = viewModelFactory {
        MyViewModel("Test Name")
    })
}

For ViewModel like this:

class MyViewModel(
  val name: String
):ViewModel() {}
Rooky answered 31/3, 2022 at 13:30 Comment(2)
An Android app without at least three Unchecked Cast is considered a dull app.Telepathy
In that @Composable function, I lack access to a 'viewModel()' function with a factory argument. Unsure how to use this proposed solution.Kironde
I
4

There is no need anymore to create the viewModelFactory by ourselves.

Now it is included inside androidx.lifecycle.viewmodel

/**
 * Creates an [InitializerViewModelFactory] with the initializers provided in the builder.
 */
public inline fun viewModelFactory(
    builder: InitializerViewModelFactoryBuilder.() -> Unit
): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()

And can be used as

 val viewModel: MyViewModel = viewModel(factory = viewModelFactory {
    initializer {
            MyViewModel(AuthRepoImpl(apiService), HomeRepoImpl(apiService))
    }
 })
Idioglossia answered 29/8, 2023 at 14:36 Comment(2)
the usage example isn't valid though. you have to call addInitializer() on context receiver in the lambdaHomeroom
@Homeroom I updated the example code to have the initializer block wrapping the constructor. This should work now.Marmion
E
3

Usually there is no common case where you need to do this. In android MVVM viewmodels get their data from repositories through dependency injection.

Here is the official documentation to the recommended android architecture: https://developer.android.com/jetpack/guide#recommended-app-arch

Edelweiss answered 15/6, 2021 at 14:20 Comment(1)
This is actually great, but I think that its also an overload for any small application. I believe a common case for doing what this question asks is for any small app. I think it'll only abstract beginners away from a wealth of understanding too in relieving them from understanding how to manage a small set of dependencies, etc. However, I do agree 100% that this is the way to go in any moderately-sized application, or when you've decided for yourself to implement this architecture in a one-all template you use for anything; good for small apps in such a case too.Denominationalism
D
3

Here's some Jetpack Compose/Kotlin-specific syntax for implementing the same:

ui/settings/SettingsViewModel.kt

class SettingsViewModel(
    private val settingsRepository: SettingsRepository
) : ViewModel() {
    /* Your implementation */
}

class SettingsViewModelFactory(
    private val settingsRepository: SettingsRepository
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create( modelClass: Class<T> ): T {
        if( modelClass.isAssignableFrom( SettingsViewModel::class.java ) ) {
            @Suppress( "UNCHECKED_CAST" )
            return SettingsViewModel( settingsRepository ) as T
        }
        throw IllegalArgumentException( "Unknown ViewModel Class" )
    }    

}

Then:

MainActivity.kt


/* dataStore by preferencesDataStore */

class MainActivity : ComponentActivity() {
    private lateinit var settingsRepository: SettingsRepository
    
    // Here we instantiate our ViewModel leveraging delegates and
    // a trailing lambda
    private val settingsViewModel by viewModels<SettingsViewModel> {
        SettingsViewModelFactory(
            settingsRepository
        )
    }

    /* onCreate -> setContent -> etc */
}
Denominationalism answered 16/1, 2022 at 3:51 Comment(0)
A
1

As it was mentioned by @Secret Keeper you need to create factory.

If your ViewModel has dependencies, viewModel() takes an optional ViewModelProvider.Factory as a parameter.

class MyViewModelFactory(
    private val dbname: String
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            return MyViewModel(dbname) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

To create your viewModel you will pass optional parameter. Inside your Composable you can do something like this.

val viewModel: MyViewModel = viewModel(
factory = MyViewModelFactory(
    dbname = "myDbName"
)
Abnaki answered 16/11, 2021 at 9:13 Comment(0)
H
1

Although most of solutions above work, their authors assume that the only parameters you will use in ViewModel are those provided manually.

So here is more up-to-date approach if you rather to use DI (Hilt):

  1. Add hilt-navigation-compose version >= 1.2.0
implementation "androidx.hilt:hilt-navigation-compose:1.2.0"
  1. Use AssistedFactory for your ViewModel
@HiltViewModel(assistedFactory = MyViewModel.AssistedFactory::class)
class MyViewModel @AssistedInject constructor(
    @Assisted customParameter: Int,
    private val someInjectedRepository: SomeRepository
) : ViewModel() {

...

    @dagger.assisted.AssistedFactory
    interface AssistedFactory {
        fun create(customParameter: Int): MyViewModel
    }
}
  1. Use special hiltViewModel implementation when requesting ViewModel from composable function
@Composable
fun SomeComposableScreen(
   customParameter: Int,
   navController: NavController,
   viewModel: MyViewModel = hiltViewModel<MyViewModel, MyViewModel.AssistedFactory> { factory ->
     factory.create(mediaIdSelected)
   }
) {
  ...
}

Now you have requested ViewModel with an argument from composable, while leaving other ViewModel parameters up to Hilt. Here is an article covering this aspect.

Hughie answered 24/4, 2024 at 12:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.