I have three class needing to share a dependency. The latter is initialisated by one of them.The SettingsViewModel contains the data to initialize the dependency and it need to be deleted at the end of the activity. NetworkViewModel and TimeViewModel use it as an interface since the dependancy is an interface with the logic to handle Bluetooth.
SettingsViewModel -->(initialize) SingletonDependency.
NetworkViewModel --> (use).
TimeViewModel --> (use).
How can I make Hilt (or manual) injection to use the same interface? If I understand well I can't use singleton here since I need to iniatilize the dependency when the activity start.
If we consider that your class name is SomeClass you can provide a live data of this class like this:
#Module
#InstallIn(SingletonComponent::class)
object SingeltonModule {
#Provides
#Singleton
fun provideSomeClassLiveData(): MutableLiveData<SomeClass> {
return MutableLiveData<SomeClass>()
}
}
in your SettingsViewModel do this:
#HiltViewModel
class SettingsViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.value = SomeClass()
}
}
and in other view models you can inject this to contractors and observe it:
#HiltViewModel
class NetworkViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.observeForEver{
//do what you want with value
}
}
}
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
}
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
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
}
I try to use the new Dagger Android injection thing that works so far.
Now I want to extend it to my needs.
In my MainActivityModule I added a TestModule:
#Module
abstract class MainActivityModule {
#ActivityScope
#ContributesAndroidInjector(modules = arrayOf(TestModule::class))
internal abstract fun contributeMainActivityInjector(): MainActivity
}
The TestModule is really simple:
#Module
internal abstract class TestModule {
#Provides
internal fun provideTest(): String {
return "foo bar"
}
}
But I get this error: TestModule must be set
I looked into the generated source code but can't find a hint what I have to do. I searched for this at Google too but found only simple examples :-(
What have I forgotten? You can find the complete app at GitHub.
Edit
As Jeff Bowman sayed the provideTest() needs to be static. When I create a Java class like this:
#Module
public class TestModule {
#Provides
static String provide() {
return "foo bar";
}
}
it works.
So the final question: How to make this in Kotlin? This doesn't work:
#Module
internal abstract class TestModule {
companion object {
#Provides
#JvmStatic
internal fun provideTest(): String {
return "foo bar"
}
}
}
So I need another way to create a static method.
yeh I found a solution :-)
The Kotlin way to get a static method is to put the method in a companion object but now Dagger throws an error that the #Provides can only be used in a #Module. To fix this I annotated the companion object too
#Module
internal abstract class TestModule {
#Module
companion object {
#Provides
#JvmStatic
internal fun provideTest(): String {
return "foo bar"
}
}
}
An alternative solution, that's a bit cleaner in case you need to mock / replace dependencies in tests, would be to avoid making the class abstract, and keep the provides not static, like this:
#Module
internal class TestModule {
#Provides
internal fun provideTest(): String {
return "foo bar"
}
}