How do I use HiltViewModelFactory in non-Android classes? - android

How can one go about injecting HiltViewModelFactory into non-Android components like Conductor's Controller.. etc. Currently I have this code but it doesn't work, saying HiltViewModelFactory cannot be provided.
#EntryPoint
#InstallIn(ActivityComponent::class)
interface MyClassInterface {
fun viewModelFactory(): HiltViewModelFactory
}
private val viewModelStore = ViewModelStore()
private val viewModelFactory by lazy {
EntryPoints.get(requireContext(), MyClassInterface::class.java).viewModelFactory()
}

You could probably do this:
#Module
#InstallIn(ActivityComponent::class)
class HiltViewModelModule {
#Provides fun viewModelFactory(activity: Activity): HiltViewModelProviderFactory =
(activity as AppCompatActivity).getDefaultViewModelProviderFactory() as HiltViewModelFactory
}
This should work correctly in any Activity that is annotated with #AndroidEntryPoint.

Related

Hilt not finding service

I'm trying to understand HILT and I'm creating a sample project, I'm used to creating modules and components, but now with a hilt, there's the InstallIn() and perhaps I have to play with them to do the same as before. For instance, I'm used to creating a module per feature as :
LoginModule (contains: ViewModel, activity/fragment, data source, use case, etc...)
ProductModule (contains: ViewModel, activity/fragment, data source, use case, etc...)
From now I've created a NetworkModule with hilt :
#Module
#InstallIn(ApplicationComponent::class)
object NetworkModule {
#Provides
fun provideRetrofit(httpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(httpClient)
.build()
#Provides
fun provideOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
#ErrorInterceptorOkHttpClient errorInterceptor: Interceptor,
): OkHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(errorInterceptor)
.build()
#ErrorInterceptorOkHttpClient
#Provides
fun provideErrorInterceptor(): Interceptor = ErrorInterceptor()
#Provides
fun provideHttpLogginInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = when {
BuildConfig.DEBUG -> HttpLoggingInterceptor.Level.BODY
else -> HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
#Provides
fun provideMyService(retrofit: Retrofit): MyService =
retrofit.create(MyService::class.java)
}
And then I have the feature or let's say part of the app that is the home where I want to show some things from now a List, and I've created this :
#Module
#InstallIn(FragmentComponent::class)
object MainActivityModule {
#Provides
fun proviceDataSource(
myService: MyService,
myMapper: MyMapper,
): MyDatasource = MyDatasourceImpl(myService, myMapper)
#Provides
fun provicesMyMapper(): MyMapper = MapperImpl()
}
And in my Activity, I've added the
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
And in Fragment, I don't have anything maybe this is the thing that I'm doing wrong?
And when to use the ApplicationComponent SingletonComponent , etc?
Since I was using dagger (not dagger-android) I had a ComponentFactory where I had all of this Components as (LoginComponent, GoogleMapComponent, ReferalCodeComponent) all of those were a feature (talking about multi-module project) and there I was creating the factory of these components, how's the way of doing this now with Hilt?
When using multiple modules, if I follow my idea, I will define multiple modules in a library and provide them to the interface module for use.
Your question is how to use the factory method you defined in the interface module? I don't understand your question very well on this point.
If you use the MVVM architecture, you can inject these components into the ViewModel . The Flow provided by these components (such as LoginComponent) can be accessed in the ViewModel. Of course, the parameters injected through the ViewModel can also easily access the corresponding methods of the components you provide.
Below is the ViewModel in my Demo:
#HiltViewModel
class NickNameViewModel #Inject constructor(
private val repo: DataRepo,
application: Application,
): AndroidViewModel(application) {
fun updateUserNickName(
userId: String,
userKey: String,
userNickName: String,
resultCallBack: (Boolean, String) -> Unit = {_, _ -> },
) {
viewModelScope.launch {
repo.updateUserInfo(
userId = userId,
userKey = userKey,
userNickName = userNickName,
).collect {
//.....
}
}
}
}
}
In the above ViewModel, the DetaRepo component is injected into the ViewModel through construction injection.
The method provided in DataRepo can be easily accessed in the method updateUserNickName.
Below is the code of ViewModel in Fragment:
#AndroidEntryPoint
class NickNameModifyFragment: Fragment(R.layout.nick_name_modify_fragment) {
companion object {
const val nickNickNameResult = "nickNickNameResult"
}
private var _binding: NickNameModifyFragmentBinding? = null
private val viewModel by viewModels<NickNameViewModel>()
}
It should be noted that if Hilt is in Fragment's ViewModel, you still need to add the #AndroidEntryPoint annotation to the attach Activity.
Hope to bring you good luck.
If your using SingletonComponent then with the #Provides annotation you should also required to use #singleton annotation
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton #Provides
fun provideServiceGenerator(
context: Context
) = ServiceGenerator(context)
}
similarly For FragmentComponent you need annotate with
#Provides #FragmentScoped
as I read you mentioned UseCase and other stuff u must check out hilt viewModel because it will auto generate a lot boilerplate code for you and handles dependency according to activity or fragment lifecycle
#Module
#InstallIn(ViewModelComponent::class)
object ViewModelModule {
//repo
#Provides
#ViewModelScoped
fun providesTicketsRepo(
checkOutApi: CheckOutApi
)= TicketsRepo(checkOutApi)
}
#HiltViewModel
class TicketsAndCouponViewModel
#Inject constructor(
val ticketsRepo: TicketsRepo
) : ViewModel() { /****/}
inside fragment and activity
val viewModel by viewModels<TicketsAndCouponViewModel>()

Using Hilt, how to inject into a class that does not have a context?

I have a class named NetworkManager. Since it is not one of the Android Components, I am using custom entry point, NetworkManagerEntryPoint with one fun that returns NetworkClient object which is what I want to inject.
Now, to inject an instance of this class using Hilt, I believe I need to use one of the Helper methods in EntryPointAccessors. But all of them requires a reference to android components. So, do I really have to pass an android component like Context to my class to inject an object using Hilt?
class NetworkManager() {
#InstallIn(SingletonComponent::class)
#EntryPoint
interface NetworkManagerEntryPoint {
fun getNetworkClient(): NetworkClient
}
var defaultNetworkClient: NetworkClient = EntryPointAccessors.fromApplication(
context, // Do I have to pass a context to this class to use Hilt?
NetworkManagerEntryPoint::class.java
).getNetworkClient()
fun <R : Any> executeRequest(
request:Request<R>,
networkClient: NetworkClient = defaultNetworkClient
): Response<R> {
// Do some operation
}
}
Hi there maybe you can try this way i have done , i follow the mvvm pattern
My RetrofitApi
interface RetrofitApi {
#GET("endpoint")
suspend fun getApi():Response<RetrofitApiResponse>
}
My NetworkModule
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule{
#Singleton
#Provides
fun provideApi(): RetrofitApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RetrofitApi::class.java)
#Singleton
#Provides
fun provideRepository(retrofitApi:RetrofitApi) : MainRepository =
DefualtMainRepository(retrofitApi)
}
and this module gets injected in my repository
class DefualtMainRepository #Inject constructor(
val retrofitApi: RetrofitApi
):MainRepository {
override suspend fun getQuotes(): Resource<RetrofitApiResponse> {
val response = retrofitApi.getApi()
val result = response.body()
if (response.successful){
}
}
}
If you are interested i have full project in my github and even wrote a medium article explaining it, Hopefully my answer is helpful to you
https://zaidzakir.medium.com/a-simple-android-app-using-mvvm-dagger-hilt-e9f45381f1bc
Okay. #Zaid Zakir's answer showed me that I can inject objects via constructor parameters, if not field injection. So, the solution for me ended up looking like this.
#Singleton
class NetworkManager #Inject constructor(
var defaultNetworkClient: NetworkClient
) {
fun <R : Any> executeRequest(
request:Request<R>,
networkClient: NetworkClient = defaultNetworkClient
): Response<R> {
// Do some operation
}
}
In another class named NetworkClientModule, I have this,
#Module
#InstallIn(SingletonComponent::class)
abstract class NetworkClientModule {
#Binds #Singleton
abstract fun bindDefaultNetworkClient(impl: DefaultNetworkClient): NetworkClient
}

I can't inject Dao to a Repository Class, with HILT, in Android

i follow this tutorial Room Dependency Injection - MVVM To-Do List App with Flow and Architecture Components #4
Now I want to convert, a hole app that I have with Room, to this Clean Architecture.
In the tutorial Florian uses DI, to inject TaskDao into TaskViewModel, but I have a Repositories clases.
So I get to a point where the app is build without errors.
and this is my Repository:
class AnioRepository constructor(
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
//val anioDao: AnioDao
fun downloadAnios(): AniosResponse? {
GlobalScope.launch(Dispatchers.IO) {
val result = agrotrackerApi.getAnios()
if (result.isSuccessful) {
for (anio in result.body()!!){
Log.d(TAG, anio.toString())
}
}
}
return null
}
fun getAnios() {
//anioDao.getListAnios()
}
}
and this is my RepositoryModule:
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun providesAnioRepository( agrotrackerApi: AgrotrackerApi) : AnioRepository {
return AnioRepository(agrotrackerApi)
}
}
So I'm trying to add Dao class to the Repository Class, like this:
class AnioRepository constructor(
private val anioDao: AnioDao,
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
...
and then, change RepositoryModule, to match constructor...
...
fun providesAnioRepository( anioDao: AnioDao, agrotrackerApi: AgrotrackerApi) : AnioRepository
= AnioRepository(anioDao, agrotrackerApi)
...
but when I press Ctrl-F9, i get this error:
public abstract class ATMDatabase extends androidx.room.RoomDatabase {
^C:\pryectos\AndroidStudioProjects\ATMobileXKt\app\build\generated\source\kapt\debug\com\cse\atm\ATMApplication_HiltComponents.java:155:
error: [Dagger/MissingBinding] #com.cse.atm.di.ApplicationScope
kotlinx.coroutines.CoroutineScope cannot be provided without an
#Provides-annotated method. public abstract static class SingletonC
implements ATMApplication_GeneratedInjector,
^
#com.cse.atm.di.ApplicationScope kotlinx.coroutines.CoroutineScope is injected at
com.cse.atm.database.ATMDatabase.Callback(�, applicationScope)
com.cse.atm.database.ATMDatabase.Callback is injected at
com.cse.atm.di.AppModule.providesDatabase(�, callback)
com.cse.atm.database.ATMDatabase is injected at
com.cse.atm.di.AppModule.providesAnioDao(db)
com.cse.atm.database.AnioDao is injected at
com.cse.atm.di.RepositoryModule.providesAnioRepository(anioDao, �)
javax.inject.Provider<com.cse.atm.data.AnioRepository> is injected at
What means this error? What I'm missing ?
#ApplicationScope annotation is in another AppModule.kt, I dont' where is the problem.
Any help wil be appreciated!!
Best Regards
Your ATMDatabase.Callback is requesting a CoroutineScope with custom qualifier #ApplicationScope, but you are not providing such a CoroutineScope in any of your modules.
To provide a coroutine scope, the tutorial you linked adds this code around the 28-minute mark:
#Provides
#Singleton
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
Since you are using the #ApplicationScope qualifier when requesting a coroutine scope, you will also need to add the qualifier to this #Provides method:
#Provides
#Singleton
#ApplicationScope
fun provideApplicationScope() = CoroutineScope(SupervisorJob())

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

Not able to inject a class using SubComponent in Dagger 2

I'm trying to add Dagger 2 in one project. I have created 3 components
AppComponent (the primary one)
RetrofitComponent (Dependent component works fine)
ControllerComponent (Subcomponent, not injecting properly)
this is my AppComponent
#ApplicationScope
#Component(modules = [ApplicationModule::class, PrefModule::class])
interface AppComponent {
fun inject(instance: AppInstance)
fun getAppPref(): AppPreference
fun newControllerComponent(controllerModule: ControllerModule): ControllerComponent
}
this is my ControllerComponent
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
fun injectActivity(activity: BaseActivity)
fun injectDialog(dialog: BaseDialog)
}
ControllerModule
#Module
class ControllerModule(activity: FragmentActivity) {
var mActivity: FragmentActivity
init {
mActivity = activity
}
#Provides
fun getContext(): Context {
return mActivity
}
#Provides
fun getActivity(): Activity {
return mActivity
}
#Provides
fun getFragmentManager(): FragmentManager {
return mActivity.supportFragmentManager
}
#Provides
fun getDialogManager(fragmentManager: FragmentManager): DialogsManager
{
return DialogsManager(fragmentManager)
}
#Provides
fun getDialogsFactory(): DialogsFactory {
return DialogsFactory()
}
}
Injecting the activity is working fine, but when I try to inject the Dialog its never injecting
controller.injectDialog(singleDialog)
My entire code is here Github link. Need some help. What am I not doing or doing wrong that I can't inject from subcomponent.
To use field injection on a given class, you need to specify the concrete type rather than its superclass.
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
//fun injectActivity(activity: BaseActivity)
//fun injectDialog(dialog: BaseDialog)
fun inject(activity: SomeSpecificActivity)
fun inject(activity: SomeOtherActivity)
fun inject(dialog: SomeSpecificDialog)
fun inject(dialog: SomeOtherDialog)
}
The Injection is working, but you are injecting nothing inside SingleClickDialog class, remember than the method inject(class : Class) is for let it to know dagger that class will be injected, but you need to put the elements you need inject inside that class with the annotation #Inject, and those elements you need to add its provider in ControllerModule as well

Categories

Resources