Hilt Inject into ViewModel without constructor params - android

With the new dependency injection library Hilt, how to inject some classes into ViewModel without constructor params and ViewModelFactory?
Is it possible?
Like in Fragment, we use only #AndroidEntryPoint and #Inject.

how to inject some classes into ViewModel without constructor params and ViewModelFactory? Is it possible?
Hilt supports constructor injection of ViewModel via the #HiltViewModel (previously #ViewModelInject) annotation.
This allows for any #AndroidEntryPoint-annotated class to redefine their defaultViewModelProviderFactory to be the HiltViewModelFactory, which allows the creation of #HiltViewModel-annotated ViewModels correctly instantiated via Dagger/Hilt.
NEW HILT VERSION:
#HiltViewModel
class RegistrationViewModel #Inject constructor(
private val someDependency: SomeDependency,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
OLD HILT VERSION:
class RegistrationViewModel #ViewModelInject constructor(
private val someDependency: SomeDependency,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
And then
#AndroidEntryPoint
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by viewModels<RegistrationViewModel>() // <-- uses defaultViewModelProviderFactory

Yes, it is possible to inject dependency into a ViewModel class without constructor params. First we need to create a new interface annotated with #EntryPoint to access it.
An entry point is an interface with an accessor method for each
binding type we want (including its qualifier). Also, the interface
must be annotated with #InstallIn to specify the component in which to
install the entry point.
The best practice is adding the new entry point interface inside the class that uses it.
public class HomeViewModel extends ViewModel {
LiveData<List<MyEntity>> myListLiveData;
#ViewModelInject
public HomeViewModel(#ApplicationContext Context context) {
myListLiveData = getMyDao(context).getAllPosts();
}
public LiveData<List<MyEntity>> getAllEntities() {
return myListLiveData;
}
#InstallIn(ApplicationComponent.class)
#EntryPoint
interface MyDaoEntryPoint {
MyDao myDao();
}
private MyDao getMyDao(Context appConext) {
MyDaoEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
appConext,
MyDaoEntryPoint.class
);
return hiltEntryPoint.myDao();
}
}
In the code above we created a method named getMyDao and used EntryPointAccessors to retrieve MyDao from Application container.
Notice that the interface is annotated with the #EntryPoint and it's
installed in the ApplicationComponent since we want the dependency
from an instance of the Application container.
#Module
#InstallIn(ApplicationComponent.class)
public class DatabaseModule {
#Provides
public static MyDao provideMyDao(MyDatabase db) {
return db.MyDao();
}
}
Though the code above has been tested and worked properly but it is not the recommended way to inject dependency into ViewModel by android officials; and unless we know what we're doing, the best way is to inject dependency into ViewModel through constructor injection.

Related

Android How to dependency inject variables into non activity?

I have an android application and I would like to perform dependency injection on a class which is not activity or fragment therefore the applicationContext is not present.
#HiltAndroidApp
class App: Application {
#Inject
lateinit var analytics: Analytics
override fun onCreate() {
super.onCreate()
// other details
}
}
My AppModule
#Module
#InstallIn(ApplicationComponent::class)
abstract class AppModule() {
companion object {
#Provide
#Singleton
fun provideSomeClass(): SomeClass = SomeClass()
}
}
If I try to inject SomeClass in a activity it works fine but not on a non activity class it fails with an error Object is not initialized.
class Consumer {
#lateinit var SomeClass someClass;
}
Can someone point what I am doing wrong?
Inject a field of a non-Activity class
To do this you have to create an Interface that will be an #EntryPoint,
and pass to that interface the ApplicationContext.
Code sample:
// No annotations here
class Consumer(ctx: Context) { // pass here the Android context
// Create an Interface (required by #InstallIn)
#EntryPoint
#InstallIn(SingletonComponent::class) // this could be implementation specific
interface Injector {
fun getSomeClass(): SomeClass // getter that will be injected
// you can also define a proper Kotlin Getter here
}
// create the injector object
val injector = EntryPoints.get(ctx, Injector::class.java)
// retrieve the injected object
val someObject = injector.getSomeClass()
suspend fun andFinallyUseIt() {
someObject.someMethod()
}
}
More:
Make sure you don't winde your scope
Read more: Dagger #EntryPoint
Use inject in constructor
class Consumer #Inject constructor(private val someclass:SomeClass){
//some code
}

How can I use Hilt to inject Retrofit to Repository, which is injected to ViewModel?

I have just learnt manual dependency injection, but I am trying out Hilt to handle these dependency injections.
I want to inject a ViewModel into a Fragment. The fragment is contained within an Activity. Right now, I have added the annotations to Application, Activity, and Fragment.
#HiltAndroidApp
class MovieCatalogueApplication : Application()
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
}
#AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding: FragHomeBinding
private val viewmodel: HomeViewModel by viewModels()
...
As can be seen, my HomeFragment depends on HomeViewModel. I have added a ViewModel injection as described here like so.
class HomeViewModel #ViewModelInject constructor(
private val movieRepository: MovieRepository,
private val showRepository: ShowRepository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
However, the ViewModel requires two repositories. Right now, my MovieRepository is like so.
class MovieRepository (private val movieApi: MovieService) {
...
}
In the above code, MovieService will be created by Retrofit using the Retrofit.create(class) method. The interface used to create MovieService is like so.
interface MovieService {
...
}
To get my Retrofit instance, I am using the following code.
object RetrofitService {
...
private var _retrofit: Retrofit? = null
val retrofit: Retrofit
get() {
return when (_retrofit) {
null -> {
_retrofit = Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
_retrofit!!
}
else -> _retrofit!!
}
}
}
I am not too sure how I can inject the Retrofit into the Repository to be used by my ViewModel later on. Could someone give me some pointers or step-by-step instructions on how to do this?
Apparently, it is not as hard as it seems.
You have to first define the binding information to Hilt. Binding information tells Hilt how to provide the instances of the dependency specified. Because MovieService is created using a Retrofit (which is a 3rd-party class not created by yourself) using the builder pattern, you can't use the constructor injection and you have to instead use Hilt modules and the annotation #Provides to tell Hilt about this binding information.
As described in the doc, the annotated function in the Hilt module you have created will supply the following information to Hilt so that Hilt can provide the instances of the dependency.
• The function return type tells Hilt what type the function provides instances of.
• The function parameters tell Hilt the dependencies of the corresponding type.
• The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
In the end, you only need to modify the MovieRepository class, add a module for each repository, and annotate the function that tells Hilt how to provide the service instance created with Retrofit with #Provides.
Code.
class MovieRepository #Inject constructor(
private val movieApi: MovieService
) {
...
}
interface MovieService {
...
}
#Module
#InstallIn(ActivityRetainedComponent::class)
object MovieModule {
#Provides
fun provideMovieService(): MovieService
= RetrofitService.retrofit.create(MovieService::class.java)
}
As you can see, the ActivityRetainedComponent is referred in the #InstallIn annotation because the Repository is to be injected to a ViewModel. Each Android component is associated to different Hilt components.

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

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

Inject Adapter class to Fragment using Dagger2

I have followed Android Architecture Blueprints Dagger2 for dependency injection: URL
Now I want to inject Adapter to my Fragment class:
#ActivityScoped
class MainFragment #Inject
constructor(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
Main Module class:
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
MainFragmentModule:
#Module
class MainFragmentModule {
#Provides
fun provideArtistAdapter() = ArtistAdapter()
}
And this is my adapter class:
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)
When I build the project I get following Kotlin compiler error:
error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sample.android.lastfm.LastFmApp> {
^
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainModule.bindArtistClickCallback$app_debug(mainFragment)
com.sample.android.lastfm.ArtistClickCallback is injected at
com.sample.android.lastfm.ui.main.ArtistAdapter.artistClickCallback
com.sample.android.lastfm.ui.main.ArtistAdapter is injected at
com.sample.android.lastfm.ui.main.MainFragment.adapter
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainActivity.mainFragment
com.sample.android.lastfm.ui.main.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sample.android.lastfm.di.AppComponent → com.sample.android.lastfm.di.ActivityBindingModule_MainActivity$app_debug.MainActivitySubcomponent]
Can you suggest me how to solve this problem?
Codes can be found at URL
Your fragment should probably not have #ActivityScoped as a scope. Further do not use constructor injection with fragments (or any other framework type)! The Android framework will create those objects in some cases, and you will end up with the wrong reference in your classes. Add the fragment to the corresponding component via its builder.
Also you're using a provides annotated method as well as constructor injection (#Inject constructor()). Pick one. Since you also use field injection within the ArtistAdapter the next "error" you would encounter would be a null callback because you don't inject the adapter anywhere. You just create the object.
Constructor injection should usually be favored, which will also inject fields. Remove the following completely, keep the annotation on the construcor:
#Provides
fun provideArtistAdapter() = ArtistAdapter()
Moving on, your error originates in MainActivitySubcomponent (last line) and seems to be because your MainFragment is bound as an ArtistClickCallback, but requires a ArtistAdapter which requires a ArtistClickCallback...hence your dependency cycle.
This issue should resolve itself once you fix the problems mentioned (#Inject constructor on the fragment in this case) above, since it originates through the MainFragment being constructed by Dagger within the MainActivitySubcomponent, which is the wrong place anyways since your fragment should have a lower scope than the Activity.
Further you need to move your binding (#Binds fun bindArtistClickCallback) into the MainFragmentModule, since there is no fragment to bind in the Activity component (where you add the binding currently)
When you fix all those issues, you will bind your fragment to the correct FragmentSubcomponent, where you will bind it as a Callback, with which you can then create the Adapter and it should work.
I recommend you have a more thorough look on Dagger and make sure to understand all the issues / fixes pointed out.
This is how it should look
#FragmentScoped
class MainFragment(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
}
#Module
class MainFragmentModule {
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)

Is optional using #Inject for all constructors in Dagger2?

Why some people uses #Inject for constructor of classes and some other did not use this annotation for constructor.
Is it optional to use this?
It's not needed if you provide instance yourself:
//without #Inject
class SomeInstance contructor(...): SomeInstanceInterface{}
#Module
class Module{
#Provides()
fun provide():SomeInstanceInterface {
return SomeInstance(...)
}
}
But if you want that Dagger create instance for you, then you need to mark constructor with #Inject and ask Dagger to create instances:
#Module
class Module{
#Provides()
fun provide(inst: SomeInstance):SomeInstanceInterface = inst
}
or
#Component
interface Component{
fun someInstance():SomeInstanceInterface
}

Categories

Resources