I have used koin in the past and injecting viewModel with koin is a single liner. I need to know how to do that without it!
should We use a big switch/case in ViewModelFactory for different viewmodels?
class ViewModelFactory: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when(modelClass) {
is FirstViewModel -> FirstViewModel()
is SecondViewModel -> SecondViewModel()
...
}
}
}
but then I have to inject all the dependencies of all viewmodels to the factory. and that's really messy code. even without that the switch/case by itself is messy! I don't think you should do that specially in big projects. so what are the alternative ways of doing this?
how can dagger help with this?
there are actually quite good alternatives to that.
first: the first one which people tend to forget about is having a ViewModelfactory per ViewModel it's a lot better than having a massive factory. that is what we call the Single Responsibility Principle
class FirstViewModelFactory(val firstDependency: SomeClass, val secondDependency: SomeOtherClass): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
FirsViewModel(firstDependency, secondDependency) as T
}
}
and in the activity:
val viewModel: FirstViewModel = ViewModelProvider(this, FirstViewModelFactory(first, second))[FirstViewModel::class.java]
Second: if you want to have only one Factory for all your ViewModels you can define a generic factory with that takes a lambda:
class ViewModelFactory<VM: ViewModel>(val provider: () -> VM): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return provider() as T
}
}
and use it like this in the activity or fragment:
val ViewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
FirstViewModel(first, second)
})[FirstViewModel::class.java]
this actually makes your life a lot easier. but it can still be improved
Third: you can tell dagger how to provide you FirstViewModel and its dependencies.
if you don't know how to use dagger, you try to learn it then read this part.
you need to tell dagger that you want to get an instance of FirstViewModle. the place for that is in the AppComponent.
#Component(modules = [AppModule::class])
interface AppComponent {
val applicationContext: Context
val firstViewModel: FirstViewModel
...
#Component.Factory
interface Factory {
fun create(#BindsInstance applicationContext: Context): AppComponent
}
}
and in your appModule, you need to tell dagger how to provide dependencies of FirstViewModel with #Provides annotation. then you need to instantiate your dagger component in application class, there are lots of ways to do this, I just use the factory interface:
class MyApplication: Application() {
val component: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}
don't forget to add MyApplication in the manifest.
then in your activity, you can inject the viewModel without ever being worry about the dependencies:
val appComponent = (application as MyApplication).appComponent
val viewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
appComponent.firstViewModel
})[FirstViewModel::class.java]
you can make it more beautiful and readable using lazy and extension functions and you can eventually have something like this:
private val viewModel: FirstViewModel by injectVmWith { appInjector.firstViewModel }
Fourth: you can always use dagger's multibinding. with this, you inject ViewModelFactory to activity or fragment and you retrieve the viewModel from that. with this, you are telling dagger to put all your viewModels in a map and inject it to the ViewModelFactory. you help dagger to find viewModels by annotating them. and you also tell dagger what their key is using another annotation. you do all that in another module and for every viewmodel you need a function in your viewModel module. then instead of switch/case in your massive factory, you tell dagger to get the required viewmodel from the map based on its type. it's a service locator (anti?)pattern. it's another topic by itself and this answer is already too long. refer to this or this
Summary : I think if you are using dagger the third one is definitely the winner. multibinding is also good but every time you add a viewmodel you need to remember to add a function in the viewmodelModule too(just like Koin). and if you are not using dagger, the second way in my opinion is the best but you should decide what is best for you.
Koin is also great!
Related
I am implementing some of the architectural designs from Google I/O's app to my own app, but I have come across something in their app that has created some confusion for me.
They have a domain layer with repository usecases, which I use myself in my apps usually. However, I do have to provide these usecases with dagger in my app. But on Google's I/O app, I cannot find any module that provides these usecases. And when used with viewmodels annotated #HiltViewModel (In my own app), it seems like it works? Somehow these get injected into my viewmodels. I do have to provide all the usecase's dependencies(repositories etc) with Hilt, but I don't have to provide any usecase via Hilt.
Here is example how it looks in my code.
Usecase:
abstract class UseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
suspend operator fun invoke(parameters: P): Resource<R> {
return try {
withContext(coroutineDispatcher) {
execute(parameters).let {
Resource.Success(it)
}
}
} catch (e: Exception) {
Timber.d(e)
Resource.Error(e.toString())
}
}
#Throws(RuntimeException::class)
protected abstract suspend fun execute(parameters: P): R
}
Concrete implementation of usecase:
class GetListUseCase #Inject constructor(
private val coroutineDispatcher: CoroutineDispatcher,
private val remoteRepository: RemoteRepository
): UseCase<ListRequest,ItemsList>(coroutineDispatcher) {
override suspend fun execute(parameters: ListRequest): ItemsList{
return remoteRepository.getList(parameters)
}
}
Viewmodel:
#HiltViewModel
class DetailViewModel #Inject constructor(
private val GetListUseCase: getListUseCase
): ViewModel() {
suspend fun getList(): Resource<ItemsList> {
getPokemonListUseCase.invoke(ListRequest(3))
}
}
Example of provided repo:
#Singleton
#Provides
fun provideRemoteRepository(
api: Api
): RemoteRepository = RemoteRepositoryImpl(api)
Remote repo:
#ActivityScoped
class RemoteRepositoryImpl #Inject constructor(
private val api: Api
): RemoteRepository {
override suspend fun getList(request: ListRequest): PokemonList {
return api.getPokemonList(request.limit, request.offset)
}
}
My Question is:
How come this works? Why don't I have to provide usecase' via Hilt? Or is my design wrong and I should provide usecases via Hilt even if this works?
EDIT:
Link to Google I/O domain layer and ui layer if it helps.
Your design is great! And it's not wrong, but yes, you can add some additional context for your use cases if you put them to a separate module and scope them per your needs. Modules exist mostly for cases when you do have your third-party dependencies (the simplest example is OkHTTPClient) or when you have interface -> impl of the interface, or you want to visibly limit/extend lifecycle/visibility of your components.
Currently you're telling Hilt how to provide instances of GetListUseCase by annotating its constructor with #Inject AND Hilt already knows what is "CoroutineDispatcher" (because it's been provided by #Provides in the coroutine module, right?) and what is RemoteRepository (because you're injecting the interface, but beneath the hood you're providing the real implementation of it in the repo module by #Provides).
So it's like saying - give me an instance of the use case class with two constructor dependencies, and both of them are known to Hilt, so it's not confusing.
And if you want to have a scoped binding/component (like explained here) or to mark your use case as a singleton, then you have to create a UseCaseModule and scope your use case components there.
The Problem
When attempting to add a ViewModel bind into the multibinding for an inherited ViewModelFactory (created with no scope) within a lower scope (#FragmentScope), I keep running into this error:
java.lang.IllegalArgumentException: unknown model class com.example.app.MyFragmentVM
What I've read or tried
(note: the below is not by any means an exhaustive list, but are two good examples of resources and the kinds of advice I've perused)
[1] dagger2 and android: load module which injects viewmodel on a map (and other variants / similar Q and As)
[2] https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
I'm relatively new to working with Dagger so I had to do a lot of Googling to try and understand what has been going on, but I've reached a point where, to my understanding, something should be working(?)...
From sources similar to [1], I removed the #Singleton scope on ViewModelFactory, but I still get the aforementioned crash saying there is no model class found in the mapping.
From sources similar to [2] I tried to reinforce my understanding of how dependencies worked and how items are exposed to dependant components. I know and understand how ViewModelProvider.Factory is available to my MyFragmentComponent and it's related Modules.
However I do not understand why the #Binds #IntoMap isn't working for the MyFragmentVM.
The Code
Let me first go through the setup of the stuff that already exists in the application -- almost none of it was scoped for specific cases
// AppComponent
#Component(modules=[AppModule::class, ViewModelModule::class])
interface AppComponent {
fun viewModelFactory(): ViewModelProvider.Factory
fun inject(activity: MainActivity)
// ... and other injections
}
// AppModule
#Module
class AppModule {
#Provides
#Singleton
fun providesSomething(): Something
// a bunch of other providers for the various injection sites, all #Singleton scoped
}
// ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindsViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityVM::class)
abstract fun bindsMainActivityVM(vm: MainActivityVM): ViewModel
}
// VMFactory
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
And the following is how I am trying to add and utilize my #FragmentScope:
// MyFragmentComponent
#FragmentScope
#Component(
dependencies = [AppComponent::class],
modules = [MyFragmentModule::class, MyFragmentVMModule::class]
)
interface MyFragmentComponent {
fun inject(fragment: MyFragment)
}
// MyFragmentModule
#Module
class MyFragmentModule {
#Provides
#FragmentScope
fun providesVMDependency(): VMDependency {
// ...
}
}
// MyFragmentVMModule
#Module
abstract class MyFragmentVMModule {
#Binds
#IntoMap
#ViewModelKey(MyFragmentVM::class)
abstract fun bindsMyFragmentVM(vm: MyFragmentVM): ViewModel
}
// MyFragment
class MyFragment : Fragment() {
#set:Inject
internal lateinit var vmFactory: ViewModelProvider.Factory
private lateinit var viewModel: MyFragmentVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerMyFragmentComponent.builder()
.appComponent(MyApplication.instance.component)
.build()
.inject(this)
viewModel = ViewModelProvider(this, vmFactory).get(MyFragmentVM::class.java)
}
}
What's interesting here to note is that MyFragmentModule itself does NOT end up providing any unique injections for MyFragment (those all come from AppComponent as it is right now). It DOES however, provide unique injections for the ViewModel that MyFragment uses.
The root of this problem is the difference between subcomponents and component dependencies.
Subcomponents
When working with subcomponents, the parent component knows everything about its subcomponents. As such, when a subcomponent requests a multibinding, the parent component can combine its contributions with those of the subcomponent. This even works transitively: if the subcomponent requests an unscoped ViewModelProvider.Factory, the injected map will include bindings from the subcomponent. (The same is true of a #Reusable binding, but not a #Singleton.)
If you change your components with dependencies into subcomponents, everything will just work. However, this might not fit your desired architecture. In particular, this is impossible if MyFragmentComponent is in an Instant App module.
Component dependencies
When working with component dependencies, the main component merely exposes objects through provision methods, and it does not know about any components that might depend on it. This time, when asked for a ViewModelProvider.Factory, the main component does not have access to any #ViewModelKey bindings except its own, and so the Factory it returns will not include the MyFragmentVM binding.
If MyFragmentComponent does not require any ViewModel bindings from AppComponent, you can extract bindsViewModelFactory into its own module and include it in both components. That way, both components can create their own Factory independently.
If you do need some ViewModel bindings from AppComponent, hopefully you can add those binding modules to MyFragmentComponent as well. If not, you would have to expose the map in AppComponent, and then somehow combine those entries with your new bindings. Dagger does not provide a good way to do this.
We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent
Well, as I tried to summarise in the title, here is the details.
We have a relatively large application, that uses Dagger, in really not ideal ways, so we decided to start writing tests, and for that, I needed to expose dependencies for Mockito, hence, I faced an issue to start provide view models using a singleton factory, still applicable and there is tons of tutorials around that explains this.
We have across our app, a lot of features, that is implemented using a single activity, and a navigation component, that single activity sometimes have a created view model that we use to share data between the container activity and the fragments populated using the navigation editor.
What I couldn't figure out is the following, how can I use dagger to inject a shared view model, to return the same instance each time I invoke #Inject for a specific view model, I know it could be done through scopes maybe, but I couldn't figure it out, and I have an explanation that I need to be verified. (I will provide my code below)
I started by implementing my Singleton ViewModelFactory as follows:
#Singleton
class ViewModelFactory #Inject constructor(private val creators: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Then I created my ViewModelModule that provides the ViewModelFactory and the ViewModel as follows:
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindsViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#EbMainScope
#ViewModelKey(EBMainContainerViewModel::class)
abstract fun bindEbMainViewModel(ebMainContainerViewModel: EBMainContainerViewModel): ViewModel
}
And before you ask, here is the scope implementation:
#Scope
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
annotation class EbMainScope
Last step, here is my activity/fragment injectors module:
#Module
abstract class ScreensBuildersModule {
#ContributesAndroidInjector
#EbMainScope
abstract fun contributeEbMainActivity(): EBMainActivity
#ContributesAndroidInjector
#EbMainScope
abstract fun contributeEbDashboardMainFragment(): EBDashboardMainFragment
}
Of course I wired everything in the AppComponent, and the app ran smoothly, with the catch, that there was two instances of EbMainContainerViewModel, despite me defining the scope.
My explanation was, I actually had two different providers rather than one, but I still cannot understand why, since I marked it as #Singleton.
Does someone has an explanation to this ? If more input is needed let me know guys.
I had the same problem, but fix it this way:
I use for example this code:
https://github.com/android/architecture-samples/tree/dagger-android
In my fragments (in which I wanted to use Shared ViewModel) I use
this (it helped me):
private val viewModel by viewModels<SearchViewModel>({ activity as MainActivity }) { viewModelFactory }
instead of this(as in sample):
private val viewModel by viewModels<SearchViewModel> { viewModelFactory }
Because the first argument is ownerProducer, so we create a ViewModel in the activity scope.
Okay then, here is practical guide I have managed to do, a solution I guess viable, and since #pratz9999 asked for a solution, here it is:
In order to instantiate a ViewModel, you would need a ViewModelProvider, which under the hood creates a ViewModelFactory, if you would depend on the above implementation, for each entry in the module (i.e #IntoMap call) a new provider will be instantiated (which is fines) but here goes the catch, it will create a new ViewModelFactory each time, take a look at the following:
/**
* Creates a {#link ViewModelProvider}, which retains ViewModels while a scope of given
* {#code fragment} is alive. More detailed explanation is in {#link ViewModel}.
* <p>
* It uses the given {#link Factory} to instantiate new ViewModels.
*
* #param fragment a fragment, in whose scope ViewModels should be retained
* #param factory a {#code Factory} to instantiate new ViewModels
* #return a ViewModelProvider instance
*/
#NonNull
#MainThread
public static ViewModelProvider of(#NonNull Fragment fragment, #Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
My fault as I guessed after some research, that I did not inject the proper ViewModelFactory, so I ended up doing the following:
In my base fragment class, I injected a ViewModelFactory as follows:
/**
* Factory for injecting view models
*/
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
Then in a utility class, I had a method that returns a shared ViewModel as follows (Note the activity?.run this makes the view model instance binded to the holding activity, and thus having the shared scope concept):
fun <T: ViewModel> BaseNavFragmentWithDagger.getSharedViewModelWithParams(clazz: Class<T>): T =
activity?.run { ViewModelProviders.of(this, viewModelFactory).get(clazz) }
?: throw RuntimeException("You called the view model too early")
And finally for private ViewModels I went with this:
fun <T: ViewModel> BaseNavFragmentWithDagger.getPrivateViewModelWithParams(clazz: Class<T>): T =
ViewModelProviders.of(this, viewModelFactory).get(clazz)
I am using Koin library in Kotlin for DI
Koin providing by viewmodel() for get instance of ViewModel by sharedViewModel() to get same instance in fragments.
How can I get same instance of the ViewModel in activities ? I didn't find any way to achieve this.
you must use single{} instead of viewModel{} in module declaration.
single { SharedViewModel() }
And, you can use viewModel() in your views.
View1
private val viewModel: SharedViewModel by viewModel()
View2
private val viewModel: SharedViewModel by viewModel()
But you must load modules when view start by
loadKoinModules(module1)
The important point is that you must unload module in when destroy view.
unloadKoinModules(mainModule)
So, when unload modules your singleton ViewModel will be destroyed.
#EDIT
Now, you can use sharedViewModel declaration.
After some research or discussion on architecture level and also report and issue github Koin,i found solution for this
In this scenario,We should save that state/data into Repository which we need to share between multiple activities not in the viewModel and two or more different ViewModels can access same state/data that are saved in single instance of repository
you need to read more about ViewModel to understand it better.
https://developer.android.com/topic/libraries/architecture/viewmodel
ViewModel is connected to your Activity
so you can share your Activities ViewModel only between his Fragments ,
that is what mean sharedViewModel in koin
sharedViewModel is the same if you use ViewModel Factory with same context .
sharing any data between Activities can be done via Intent , there is no another way in Android,
or you can keep some static / global data and share it between Activities
I would suggest making the app a ViewModelStoreOwner and injecting the viewModels using as owner the app.
The code required would look like this
class App : Application(), ViewModelStoreOwner {
private val mViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore {
return mViewModelStore
}
}
You can define some extensions to easily inject the viewModels
val Context.app: App
get() = applicationContext as App
inline fun <reified T : ViewModel> Context.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(app, null) }, T::class, parameters)
}
}
inline fun <reified T : ViewModel> Fragment.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(requireContext().app, null) }, T::class, parameters)
}
}
You can then inject your viewModel like this
class MainActivity : AppCompatActivity() {
private val mAppViewModel: AppViewModel by appViewModel()
}
The advantage of this solution is that you don't need to recreate the view model and if you decide to save the state between app restarts, you can easily make the app an SavedStateRegistryOwner as well and using the SavedStateHandle save/restore your state from inside the viewModel, being now bound to the process lifecycle.
I know this is very very late but you can try this:
if you are extending a baseviewmodel, you need to declare the baseViewmodel as a single then in your respective activity inject the BaseViewModel.
Practical example:
val dataModule = module {
single { BaseViewModel(get(), get()) }
}
in your ViewModel
class LoginViewModel(private val param: Repository,
param1: Pref,
param2: Engine) : BaseViewModel(param1, param2)
Then in your activity class
val baseViewModel: BaseViewModel by inject()
Hope this help someone.