How to inject a ViewModel with Koin in Kotlin? - android

How do we inject ViewModel with dependency using Koin?
So For Example I have a ViewModel thats like this:
class SomeViewModel(val someDependency: SomeDependency, val anotherDependency: AnotherDependency): ViewModel()
Now the official docs here, states that to provide a ViewModel we could do something like:
val myModule : Module = applicationContext {
// ViewModel instance of MyViewModel
// get() will resolve Repository instance
viewModel { SomeViewModel(get(), get()) }
// Single instance of SomeDependency
single<SomeDependency> { SomeDependency() }
// Single instance of AnotherDependency
single<AnotherDependency> { AnotherDependency() }
}
Then to inject it, we can do something like:
class MyActivity : AppCompatActivity(){
// Lazy inject SomeViewModel
val model : SomeViewModel by viewModel()
override fun onCreate() {
super.onCreate()
// or also direct retrieve instance
val model : SomeViewModel= getViewModel()
}
}
The confusing part for me is that, normally you will need a ViewModelFactory to provide the ViewModel with Dependencies. Where is the ViewModelFactory here? is it no longer needed?

Hello viewmodel() it's a Domain Specific Language (DSL) keywords that help creating a ViewModel instance.
At this [link][1] of official documentation you can find more info
The viewModel keyword helps declaring a factory instance of ViewModel.
This instance will be handled by internal ViewModelFactory and
reattach ViewModel instance if needed.
this example of koin version 2.0 [1]: https://insert-koin.io/docs/2.0/documentation/koin-android/index.html#_viewmodel_dsl
// Given some classes
class Controller(val service : BusinessService)
class BusinessService()
// just declare it
val myModule = module {
single { Controller(get()) }
single { BusinessService() }
}

Related

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

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)?
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))
}
}

How to inject a singleton using hilt inside a composable

I'm trying to inject a singleton class that was defined in a hiltmodule inside a composable.
I know how to inject viewmodels but what about singleton classes ?
#Inject
lateinit var mysingleton: MySingletonClass
This code works fine in an activity but carrying it around from the activity to the composable that uses it is a long way ...
Any better solution ?
You cannot inject dependencies into a function, which is what a #Composable is. #Composable functions don't have dependencies, but can get values returned by Hilt functions, like hiltViewModel().
If you need access to a ViewModel-scoped (or Application-scoped) singleton inside a #Composable, you can have that singleton injected into the ViewModel, and then access the ViewModel from the #Composable.
You can inject that singleton into the ViewModel by annotating the provider function for that object in the ViewModel hilt module as #ViewScoped.
You could install the provider into the SingletonComponent::class and annotate it as #Singleton, if you want a singleton for the whole app, instead of a singleton per ViewModel instance. More info here.
Hilt module file
#Module
#InstallIn(ViewModelComponent::class)
object ViewModelModule {
#ViewScoped
#Provides
fun provideMySingleton(): MySingletonClass = MySingletonClass()
}
Your ViewModel class:
#HiltViewModel
class MyViewModel
#Inject constructor(
val mySingleton: MySingletonClass
): ViewModel() {
...
}
Your #Composable function:
#Composable fun DisplayPrettyScreen() {
...
val viewModel: MyViewModel = hiltViewModel()
val singleton = viewModel.mySingleton //no need to assign it to a local variable, just for explanation purposes
}
I also thought that is not possible but then found way... tried it and seems it works.
define you entry point interface:
private lateinit var dataStoreEntryPoint: DataStoreEntryPoint
#Composable
fun requireDataStoreEntryPoint(): DataStoreEntryPoint {
if (!::dataStoreEntryPoint.isInitialized) {
dataStoreEntryPoint =
EntryPoints.get(
LocalContext.current.applicationContext,
DataStoreEntryPoint::class.java,
)
}
return dataStoreEntryPoint
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DataStoreEntryPoint {
val dataStoreRepo: DataStoreRepo
}
DataStoreRepo is singleton defined in Hilt
#Singleton
#Provides
fun provideDataStoreRepository(dataStore: DataStore<Preferences>): DataStoreRepo =
DataStoreRepo(dataStore)
and then use in composable:
#Composable
fun ComposableFuncionName(dataStoreRepo: DataStoreRepo = requireDataStoreEntryPoint().dataStoreRepo){
...
}

Way to share an dependency between class

I have three class needing to share a dependency. The latter is initialisated by one of them.The SettingsViewModel contains the data to initialize the dependency and it need to be deleted at the end of the activity. NetworkViewModel and TimeViewModel use it as an interface since the dependancy is an interface with the logic to handle Bluetooth.
SettingsViewModel -->(initialize) SingletonDependency.
NetworkViewModel --> (use).
TimeViewModel --> (use).
How can I make Hilt (or manual) injection to use the same interface? If I understand well I can't use singleton here since I need to iniatilize the dependency when the activity start.
If we consider that your class name is SomeClass you can provide a live data of this class like this:
#Module
#InstallIn(SingletonComponent::class)
object SingeltonModule {
#Provides
#Singleton
fun provideSomeClassLiveData(): MutableLiveData<SomeClass> {
return MutableLiveData<SomeClass>()
}
}
in your SettingsViewModel do this:
#HiltViewModel
class SettingsViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.value = SomeClass()
}
}
and in other view models you can inject this to contractors and observe it:
#HiltViewModel
class NetworkViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.observeForEver{
//do what you want with value
}
}
}

Cannot create an instance of class com.example.event.ui.main.EventsViewModel

I try to create instance of ViewModel, but get an error.
java.lang.RuntimeException: Cannot create an instance of class com.example.event.ui.main.EventsViewModel
my code here to get instance:
viewModel = ViewModelProvider(this).get(EventsViewModel::class.java)
view model class
class EventsViewModel #Inject constructor(private val eventApi: EventApi): BaseViewModel() {
val getEventsData = MutableLiveData<Response<List<Event>>>()
fun getEventsData() {
uiScope.launch {
getEventsData.value = eventApi.getEvents()
}
}
}
Here, your ViewModelProvider is not aware of how to create EventsViewModel. If your ViewModel constructor was an empty constructor without any dependencies, it would work. However, if there is dependency or a parameter in your ViewModel constructor you need to provided a custom ViewModelProviderFactory.
If you want to check how/why custom view model factory is created you can check this codelab:
https://developer.android.com/codelabs/kotlin-android-training-view-model#7
If you want to employ dagger for injecting dependencies into your ViewModel, you can find an example in my sample repo:
https://github.com/hakanbagci/truemvvm

how to get a ViewModel instance inside another ViewModel withKoin

I am new to koin and kotlin and I have just started using koin in my project and It is working quite good. I have two viewmodel classes, SubscritpionViewModel and LoginViewModel. Is there a way I can get instance of LoginViewModel inside SubscriptionViewModel. I don't know if it is right or not but it will be handy for me if I can access the other viewmodel.
val viewModule = module {
viewModel { SubscriptionViewModel(get(), get()) }
viewModel { LoginViewModel(get()) }
}
SubscriptionViewModel
class SubscriptionViewModel(val api: ServiceApi, var user: LoginViewModel) : BaseViewModel() {
...
}
I have also created a separate module for this, but I don't know what is the right way to initialize it.
val userModule = module {
single( definition = {
get<LoginViewModel>() })
}
I think it's a bad design. I think what you should do is to create a common object between LoginViewModel and SubscriptionViewModel and inject it via constructor to both LoginViewModel and SubscriptionViewModel. Maybe Repository pattern would be good? Please describe the functionality you want to implement so we can get the idea of why you need one ViewModel inside another. With repository you can do something like this:
class UserRepository(private val serviceApi: ServiceApi) {
}
class SubscriptionViewModel(val userRepository: UserRepository) : BaseViewModel() {
...
}
class LoginViewModel(val userRepository: UserRepository) : BaseViewModel() {
...
}
and in Koin module:
module {
single { UserRepository(get()) }
viewModel { SubscriptionViewModel(get()) }
viewModel { LoginViewModel(get()) }
}

Categories

Resources