I am using Hilt for DI and I have this class.
class ChatCore #Inject constructor()
This class needs to be injected in fragment , without marking the fragment as #AdroidEntryPoint as this fragment can be attached to activity which isn't marked as #AndroidEntryPoint
How can i achieve this. I tried using EntryPoint but i end up with error.
class MyFragment : Fragment() {
lateinit var chatCore: ChatCore
#EntryPoint
#InstallIn(FragmentComponent::class)
interface ChatCoreProviderEntryPoint{
fun chatCore():ChatCore
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val hiltEntryPoint = EntryPointAccessors.fromFragment(this, ChatCoreProviderEntryPoint::class.java)
chatCore = hiltEntryPoint.chatCore()
}
Solved it by adding it into the application container.
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface ChatCoreProviderEntryPoint{
fun chatCore():ChatCore
}
val hiltEntryPoint = EntryPointAccessors.fromApplication(applicationContext,
ChatCoreProviderEntryPoint::class.java)
If you don't want to use AndroidEntryPoint for your Fragment you need to #Install your module (containing your dependency) within a different Component.
E.g. within the ApplicationComponent not the FragmentComponent.
Then you will also need to use the corresponding EntryPointAccessors.fromXyz(...) method. E.g. for a module installed in the ApplicationComponent you should be using EntryPointAccessors.fromApplication(...).
Related
MainActivity cannot be provided without an #Inject constructor or an
#Provides-annotated method. This type supports members injection but
cannot be implicitly provided.
I'm using dagger-android, I injected MainActivity through AndroidInjection.inject(this), but it's still unavailable in Module. I prepared sample project: https://github.com/deepsorrow/test_daggerIssu.git, files listed below:
FactoryVmModule:
#Module
class FactoryVmModule {
#Provides
#Named("TestVM")
fun provideTestVM(
activity: MainActivity, // <--- dagger can't inject this
viewModelFactory: ViewModelFactory
): TestVM =
ViewModelProvider(activity, viewModelFactory)[TestVM::class.java]
}
MainActivityModule:
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun injectMainActivity(): MainActivity
}
MainActivity (using DaggerAppCompatActivity):
class MainActivity : DaggerAppCompatActivity() {
#Named("TestVM")
#Inject
lateinit var testVM: TestVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
TestApplication:
class TestApplication : Application(), HasAndroidInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.create().inject(this)
}
override fun androidInjector() = dispatchingAndroidInjector
}
AppComponent:
#Component(modules = [AndroidInjectionModule::class, MainActivityModule::class, ViewModelModule::class, FactoryVmModule::class])
interface AppComponent {
fun inject(application: TestApplication)
}
dagger.android does do this automatically: See the explicit version of the binding that #ContributesAndroidInjector generates for you, where the generated AndroidInjector.Factory contains a #BindsInstance binding of the type you request here.
This isn't working for you because you are injecting MainActivity in a binding that is installed on your top-level component. This is a problem because AppComponent will exist before the Activity does, and will also be replaced as Android recreates the Activity: Passing an instance through #Component.Builder is not a way around this problem.
Instead, move your FactoryVmModule::class to within the subcomponent that #ContributesAndroidInjector generates, which you can do by including it in the modules attribute on #ContributesAndroidInjector. Dagger will create a different subcomponent instance per Activity instance, so your FactoryVmModule will always have a fresh binding to MainActivity.
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(
modules = [ViewModelModule::class, FactoryVmModule::class]
)
abstract fun injectMainActivity(): MainActivity
}
I moved your ViewModelModule class there as well; though it's possible you could leave it in your top-level Component if it doesn't depend on anything belonging to the Activity, you might want to keep them together. Bindings in subcomponents inherit from the application, so you can inject AppComponent-level bindings from within your Activity's subcomponent, but not the other way around. This means you won't be able to inject VM instances (here, TestVM) outside your Activity, but if they depend on the Activity, you wouldn't want to anyway: Those instances might go stale and keep the garbage collector from reclaiming your finished Activity instances.
I defined my own LayoutInflater.Factory2 class in a separate module. I want to inject it into each activity in my App, but the point is that I have to set this factory before activity's super.onCreate() method.
When I using Hilt it makes an injection right after super.onCreate(). So I have an UninitializedPropertyAccessException.
Is there any opportunity to have an injection before super.onCreate with Hilt?
Below is my example of module's di.
#Module
#InstallIn(SingletonComponent::class)
object DynamicThemeModule {
#FlowPreview
#Singleton
#Provides
fun provideDynamicThemeConfigurator(
repository: AttrRepository
): DynamicTheme<AttrInfo> {
return DynamicThemeConfigurator(repository)
}
}
You can inject the class before onCreate by using Entry Points like this.
#AndroidEntryPoint
class MainActivity: AppCompatActivity() {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DynamicThemeFactory {
fun getDynamicTheme() : DynamicTheme<AttrInfo>
}
override fun onCreate(savedInstanceState: Bundle?) {
val factory = EntryPointAccessors.fromApplication(this, DynamicThemeFactory::class.java)
val dynamicTheme = factory.getDynamicTheme()
super.onCreate(savedInstanceState)
}
}
If you need something like this a lot Id recommend creating an instance of it in the companion object of your Application class when your application starts (onCreate). That is before any of your views are created. So you don´t need to jump threw those hoops all the time, but can just access the instance that already exists. This code above won´t be available in attachBaseContext, when you need it there you have to create it in your application class I think.
I have created a tab layout(WorkoutAtHome and Workoutinstudio) inside the HomeFragment and to create tab layout obviously i have used fragment. Now i want to inject ViewModelProviderFactory object inside the tablayout fragments. so for that what I have done is added the home fragment with the #ContributesAndroidInjector and both tablayout fragments are inside homeFragment module. Below is the code.
ActivityBuilderModule
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(
modules = [
FragmentBuilderModule::class
]
)
abstract fun contributeMainActivity(): MainActivity
}
FragmentBuilderModule
#Module
interface FragmentBuilderModule {
#ContributesAndroidInjector
fun contributeSplashFragment(): SplashFragment
#HomeScope
#ContributesAndroidInjector(
modules = [
TabBuilderModule::class,
HomeViewModelModule::class,
HomeModule::class]
)
fun contributeHomeFragment(): HomeFragment
#LocationScope
#ContributesAndroidInjector(
modules = [
LocationModule::class
]
)
fun contributeLocationFragment(): LocationFragment
}
TabBuilderModule
#Module
interface TabBuilderModule {
#ContributesAndroidInjector
fun contributeWorkoutInStudio(): WorkoutInStudioFragment
#ContributesAndroidInjector
fun contributeWorkoutAtHomeFragment(): WorkoutAtHomeFragment
}
Now the issue is whenever i want to inject anything the tablayout fragment then i get error saying that No injector factory bound for Class<com.example.fitternitytest.view.fragment.home.tabs.WorkoutInStudioFragment>
WorkoutAtHomeFragment
class WorkoutAtHomeFragment :DaggerFragment() , HasSupportFragmentInjector {
private var count = 0
#Inject
lateinit var factory: ViewModelProviderFactory
private val viewModel: HomeViewModel by activityViewModels { factory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
Same code is for other fragment also.
First HasFragmentInjector is implemented by DaggerFragment - why are you doing it again?
class WorkoutAtHomeFragment :DaggerFragment() , HasSupportFragmentInjector {
Second - you are missing injector factory for WorkoutInStudioFragment, seen by the error, but you have shown the code for WorkoutAtHomeFragment
Third - this is not the way to use Dagger anymore. You should be using Hilt which will mean that you no more need things like #ContributesAndroidInjector
You only say:
#AndroidEntryPoint
class WorkoutAtHomeFragment : Fragment() {
...
}
Hilt Tutorial: https://developer.android.com/training/dependency-injection/hilt-android
There are more tutorials and a codelab in the end of the page.
I am facing this issue in multi module android project with HILT.
kotlin.UninitializedPropertyAccessException: lateinit property repository has not been initialized in MyViewModel
My modules are
App Module
Viewmodel module
UseCase Module
DataSource Module
'App Module'
#AndroidEntryPoint
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.test()
}}
'ViewModel Module'
class MainViewModel #ViewModelInject constructor(private val repository: MyUsecase): ViewModel() {
fun test(){
repository.test()
}}
'UseCase Module'
class MyUsecase #Inject constructor() {
#Inject
lateinit var feature: Feature
fun doThing() {
feature.doThing()
}
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
}
'DataSource Module'
interface Feature {
fun doThing()
}
class RealFeature : Feature {
override fun doThing() {
Log.v("Feature", "Doing the thing!")
}
}
Dependencies are
MyFragment ---> MyViewModel ---> MyUseCase ---> DataSource
what i did wrong with this code pls correct it.
above your activity class you must add annotation #AndroidEntryPoint
as below:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
In addition to moving all your stuff to constructor injection, your RealFeature isn't being injected, because you instantiate it manually rather than letting Dagger construct it for you. Note how your FeatureModule directly calls the constructor for RealFeature and returns it for the #Provides method. Dagger will use this object as is, since it thinks you've done all the setup for it. Field injection only works if you let Dagger construct it.
Your FeatureModule should look like this:
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
Or with the #Binds annotation:
#Module
#InstallIn(ApplicationComponent::class)
interface FeatureModule {
#Binds
fun feature(realFeature: RealFeature): Feature
}
This also highlights why you should move to constructor injection; with constructor injection, this mistake wouldn't have been possible.
The problem in the code is that #ViewModelInject doesn't work as #Inject in other classes. You cannot perform field injection in a ViewModel.
You should do:
class MainViewModel #ViewModelInject constructor(
private val myUseCase: MyUsecase
): ViewModel() {
fun test(){
myUseCase.test()
}
}
Consider following the same pattern for the MyUsecase class. Dependencies should be passed in in the constructor instead of being #Injected in the class body. This kind of defeats the purpose of dependency injection.
First, i think you are missing #Inject on your RealFeature class, so the Hilt knows how the inject the dependency. Second, if you want to inject into a class that is not a part of Hilt supported Entry points, you need to define your own entry point for that class.
In addition to the module that you wrote with #Provides method, you need to tell Hilt how the dependency can be accessed.
In your case you should try something like this:
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface FeatureInterface {
fun getFeatureClass(): Feature
}
Then, when you want to use it, write something like this:
val featureInterface =
EntryPoints.get(appContext, FeatureInterface::class.java)
val realFeature = featureInterface.getFeatureClass()
You can find more info here:
https://dagger.dev/hilt/entry-points
https://developer.android.com/training/dependency-injection/hilt-android#not-supported
class MainViewModel #ViewModelInject constructor(private val repository: HomePageRepository,
#Assisted private val savedStateHandle: SavedStateHandle)
: ViewModel(){}
and intead of initializing the viewmodel like this :
private lateinit var viewModel: MainViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
Use this directly :
private val mainViewModel:MainViewModel by activityViewModels()
EXplanation :
assisted saved state handle : will make sure that if activity / fragment is annotated with #Android entry point combined with view model inject , it will automatically inject all required constructor dependencies available from corresonding component activity / application so that we won't have to pass these parameters while initializing viewmodel in fragment / activity
Make sure you added class path and plugin
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35'
in Project.gradle
apply plugin: 'dagger.hilt.android.plugin'
in app.gradle
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