It seems like what you want is to ask Dagger to create an instance of an object. Let me explain how can you do that with EntryPoint
s.
My Notes on your Dagger Module
In your class TargetNumberManager
:
- I would use plain Kotlin classes for Dagger modules and avoid using interfaces or data classes. Because you won't be creating an instance of your module: Dagger will create it.
- Avoid using variables in
provides
functions (such as getNextTargetNumber
) because you are providing a dependency that is marked as @Singleton
: this means Dagger will only call your getNextTargetNumber()
function 1 time. It's better to separate business logic (such as counting into managers, use cases etc.) and dependency injection code (such as provides functions into Dagger modules).
You can separate that counting logic and Dagger module like this:
@Singleton
data class TargetNumberManager @Inject constructor(
val maxNumber: Int = 3,
val prefix: String = "ZZ"
) : ITargetNumberManager {
private var currentNumber = 0
override fun getNextTargetNumber(): String {
val targetNumberBuilder: StringBuilder = java.lang.StringBuilder()
targetNumberBuilder.append(prefix)
val lengthOfNumber = currentNumber.toString().length
for (i in lengthOfNumber..maxNumber step 1) {
targetNumberBuilder.append(0)
}
targetNumberBuilder.append(currentNumber++)
return targetNumberBuilder.toString()
}
}
Note that:
- I removed
@InstallIn(SingletonComponent::class)
and @Module
annotations. This means this class is not a module anymore. Because TargetNumberManager
looks like an implementation of a business logic and it's not related with dependency injection.
- I added
@Inject
annotation to the constructor
. This means Dagger will instantiate this class. In order for Dagger to create an instance of TargetNumberManager
, it needs to find the dependencies of TargetNumberManager
which are maxNumber
and prefix
variables. You need to provide them too.
For now we can extract them to constants:
@Singleton
data class TargetNumberManager @Inject constructor() : ITargetNumberManager {
private var currentNumber = 0
override fun getNextTargetNumber(): String {
val targetNumberBuilder: StringBuilder = java.lang.StringBuilder()
targetNumberBuilder.append(PREFIX)
val lengthOfNumber = currentNumber.toString().length
for (i in lengthOfNumber..MAX_NUMBER step 1) {
targetNumberBuilder.append(0)
}
targetNumberBuilder.append(currentNumber++)
return targetNumberBuilder.toString()
}
companion object {
private const val MAX_NUMBER: Int = 3
private const val PREFIX: String = "ZZ"
}
}
With these changes in TargetNumberManager
Dagger can create it when you want to inject it somewhere. It's marked as Singleton so Dagger will provide the same instance whenever you inject it.
Dagger needs to know which implementation you want to use, when you try to inject an instance of ITargetNumberManager
. Because you have an interface and an implementation, but there may be other implementations of the same interface, so we need to tell Dagger which one use. We do that by using @Binds
annotation. @Binds
methods must be either abstract or inside an interface. Here I used abstract class for Dagger module:
@InstallIn(SingletonComponent::class)
@Module
abstract class MyModule {
@Binds
abstract fun bindTargetNumberManager(impl: TargetNumberManager): ITargetNumberManager
}
Now Dagger knows which implementation to inject when you ask ITargetNumberManager
. Note that I didn't put @Singleton
here because the implementation class TargetNumberManager
is already marked as @Singleton
. So it will be Singleton whenever you inject the type TargetNumberManager
AND ITargetNumberManager
.
How to get an instance from Dagger with EntryPoints
- In plain Dagger you create component interfaces. Hilt includes everything from plain Dagger, so there are also components in Hilt. In Hilt there are
EntryPoint
interfaces. Hilt discovers these interfaces and makes the component interface extend these EntryPoint
interfaces. Using an EntryPoint
is basically as if you added functions to component interfaces. See: https://dagger.dev/hilt/entry-points
- Your component interface and your
EntryPoint
can include a function that returns an instance of your object. By adding a function that has a return type of your dependency, you are simply asking Dagger to create an instance for you.
A typical EntryPoint looks like below:
@EntryPoint
@InstallIn(SingletonComponent::class)
interface MyEntryPoint {
fun getTargetNumberManager(): ITargetNumberManager
}
When you need ITargetNumberManager
: get an instance of this MyEntryPoint
then get an instance of ITargetNumberManager
:
val myEntryPoint: MyEntryPoint = EntryPoints.get(applicationContext, MyEntryPoint::class.java)
val targetNumberManager: ITargetNumberManager = myEntryPoint.getTargetNumberManager()
That's it! If you want to do that in your Target
class:
data class Target(
@PrimaryKey
val targetNumber: String,
val targetType: TargetType?,
val numOfElement: Int?,
val location: Coordinate?
) : java.io.Serializable {
companion object {
fun create(applicationContext: Context): Target {
val myEntryPoint: MyEntryPoint = EntryPoints.get(applicationContext, MyEntryPoint::class.java)
val targetNumberManager: ITargetNumberManager = myEntryPoint.getTargetNumberManager()
val nextNumber = targetNumberManager.getNextTargetNumber()
return Target(nextNumber, null, null, null)
}
}
}
Hilt needs to know the Application Context because it keeps the SingletonComponent inside the Application class. So it will go there to get the component. After it gets the component, it will cast to your EntryPoint interface. Then you can call the functions you added into your EntryPoint.
My Other Notes:
AndroidEntryPoint
is ONLY for Android entry points, such as: Activity, Fragment, View, Service, BroadcastReceiver. NOT for companion objects or other type of classes.
See: https://dagger.dev/hilt/android-entry-point.html
@Inject
cannot be used in variables inside functions. You can add @Inject
to constructors instead.
I hope this helps. I tried to explain each point. Let me know if you have further questions!