How to pass arguments to ViewModel from Fragment and inject dependencies at the same time?

Viewed 879

I need to open a Compose component with its own ViewModel and pass arguments to it, but at the same time I inject dependencies to this ViewModel. How can I achieve this? Can I combine ViewModel factory and Dependency Injection (Hilt)?

2 Answers

Yes. you can..

Have your component be like this:

@Composable
fun MyScreen(
    viewModel: MyViewModel = hiltViewModel()
) {
    ... 
}

and in your viewModel:

@HiltViewModel
class MyViewModel @Inject constructor(
    private val repository: MyRepository,
    ... //If you have any other dependencies,  add them here
): ViewModel() {

   ...
}

When you pass arguments to the ViewModel, make sure that Hilt knows where to get that dependency. If you follow the MVVM architecture, then the ViewModel should handle all the data and the composable all the ui related components. So usually, you only need the ViewModel injection into the composable and all the other data injected dependencies into the ViewModel.

The composable should only care about the data that it gets from the ViewModel. Where the ViewModel gets that data and the operations it does on that data, it does not care.

Lemme know if this is what you meant..

Check out the official website for more: Hilt-Android

Yes, you can. This is called "Assisted Inject" and it has it's own solutions in Hilt, Dagger(since version 2.31) and other libraries like AutoFactory or square/AssistedInject.

In this article, you can find an example of providing AssistedInject in ViewModel for Composable with Hilt Entry points.

Here is some code from article in case if article would be deleted:

In the main Activity, we’ll need to declare EntryPoint interface which will provide Factory for creating ViewModel:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @EntryPoint
    @InstallIn(ActivityComponent::class)
    interface ViewModelFactoryProvider {
        fun noteDetailViewModelFactory(): NoteDetailViewModel.Factory
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NotyTheme {
                NotyNavigation()
            }
        }
    }
}

We get Factory from Activity and instantiating our ViewModel with that Factory and assisted some field:

@Composable
fun noteDetailViewModel(noteId: String): NoteDetailViewModel {
    val factory = EntryPointAccessors.fromActivity(
        LocalContext.current as Activity,
        MainActivity.ViewModelFactoryProvider::class.java
    ).noteDetailViewModelFactory()

    return viewModel(factory = NoteDetailViewModel.provideFactory(factory, noteId))
}

Now just go to your navigation components and use this method to provide ViewModel to your Composable screen as following:

NavHost(navController, startDestination = Screen.Notes.route, route = NOTY_NAV_HOST_ROUTE) {
    composable(
        Screen.NotesDetail.route,
        arguments = listOf(navArgument(Screen.NotesDetail.ARG_NOTE_ID) { type = NavType.StringType })
    ) {
        val noteId = it.arguments?.getString(Screen.NotesDetail.ARG_NOTE_ID)!!
        NoteDetailsScreen(navController, noteDetailViewModel(noteId))
    }
}
Related