Android Hilt - Question about binding per activity - android

I've a question about implementing bindings per activity. Let me give you background. In application I've for ex. two activities, both of them inject same simple provider with two different values. When I create two separate modules for both activities with #ActivityRetainedComponent scope - compiler gives me error that there are duplicated bindings. The solution for that problem is to create special scope for multiple activities but then I'll need to manually point which scope should be used. I rather to make that pointing on DI level. Below some sample code:
class SampleProvider(
var text : String
)
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityOneScope
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityTwoScope
#InstallIn(ActivityRetainedComponent::class)
#Module
object ActivityOneModule {
#Provides
#ActivityOneScope
#ActivityRetainedComponent
fun provideSampleProvider() : SampleProvider = SampleProvider("ActivityOne")
}
#InstallIn(ActivityRetainedComponent::class)
#Module
object ActivityTwoModule {
#Provides
#ActivityTwoScope
#ActivityRetainedComponent
fun provideSampleProvider() : SampleProvider = SampleProvider("ActivityTwo")
}
I want to omit specifying scope during injection inside activity like this:
#ActivityOneScope
#Inject lateinit var testProvider: TestProvider
Is it possible in Hilt? In Dagger 2 it was possible using for example #ContributesAndroidInject and pointing specific module.
Thanks in advance

Related

Android: Hilt is no using Module, which may be the reason the app is crashing without showing any information

This is the situation:
I'm using Compose, Hilt, Navigation and ViewModel. I'm trying to get an instance of my ViewModel within a Composable Screen via Hilt:
#Composable
fun HomeScreen(
modifier: Modifier = Modifier,
homeViewModel: HomeViewModel = viewModel()
) {
...
}
#HiltViewModel
class HomeViewModel #Inject constructor(
private val updateCaptureUseCase: UpdateCaptureUseCase
) : ViewModel() {
...
}
class UpdateCaptureUseCase #Inject constructor(private val captureRepository: CaptureRepository) {
...
}
I get an instance of CaptureRepository by defining it inside a Module:
#Module
#InstallIn(ViewModelComponent::class)
abstract class CaptureModule {
#Binds
abstract fun bindCaptureLocalDataSource(
captureLocalDataSourceImpl: CaptureLocalDataSourceImpl
): CaptureLocalDataSource
#Binds
abstract fun bindCaptureRepository(
captureRepositoryImpl: CaptureRepositoryImpl
): CaptureRepository
}
The problem is that CaptureModule appears in Android Studio as if it had no usages.
I can build and run the app with no problems, but when it is supposed to show HomeScreen it crashes. What stresses me out and makes it hard to figure out a solution is that there are no errors in the Run tab nor the Logcat.
If I remove updateCaptureUseCase from the constructor of HomeViewModel, then the app works correctly and is able to reach HomeScreen without errors. Since updateCaptureUseCase depends on CaptureRepository and it is being defined in CaptureModule, but this Module shows no usages, I suspect the error comes from Hilt and ViewModel
I think when ViewModel gets initialized hilt checks the dependency graph/tree, and since it has a parameter that also needs a dependency which is the CaptureRepository , hilt also looks for it, but because your'e using #Bind, afaik, those dependencies should also define #Inject annotation.
I was able to reproduce your issue and manage to fix it by, specifying inject to your repository impl
class CaptureRepositoryImpl #Inject constructor(): CaptureRepository
another work around is having your DI module a companion object and define how hilt will provide the dependency without the need to specify #Inject in your repository impl.
#Module
#InstallIn(ViewModelComponent::class)
abstract class CaptureModule {
...
companion object {
#Provides
fun provideHomePresenter(): CaptureRepository {
return CaptureRepositoryImpl()
}
}
}
After many hours I found out a solution: I had to use #AndroidEntryPoint annotation in my Activity.
The problem is that since I'm fairly new with Compose, Hilt and Navigation I had no idea what structure I should use: I wanted to use a single Activity and instead of using Fragments for navigation I desired to use Composables.
Android Docs for Navigation provide examples about the structure I wanted; I had set up everything the same, but the only thing that was missing was that annotation. I though it was not needed since I didn't require to inject dependencies directly into the Activity, but in the end this was the root of the bug, a difficult one because the app crashed without showing a single error

whats the difference between #Provide and #Inject in dagger2?

Whats the difference between #Inject and #Provide ?
although both are used for providing dependencies then whats the difference ?
This is covered very well in documentation, #Inject and #Provides are two different ways of introducing dependencies in the dependency graph. they are suited for different use cases
#Inject
Easy to use, simply add #Inject on constructor or property and you are done
It can be used to inject types as well as type properties
In a subjective way it may seem clearer than #Provides to some people
#Provides
If you don't have access to source code of the type that you want to inject then you can't mark its constructor with #Inject
In some situations you may want to configure an object before you introduce it in dependency graph, this is not an option with #Inject
Sometimes you want to introduce an Interface as a dependency, for this you can create a method annotated with #Provides which returns Inteface type
Following are the examples of above three points for #Provides
If you can't access source code of a type
// You can't mark constructor of String with #Inject but you can use #Provides
#Provides
fun provideString(): String {
return "Hello World"
}
Configure an object before introducing in the dependency graph
#Provides
fun provideCar(): Car {
val car = Car()
// Do some configuration before introducing it in graph, you can't do this with #Inject
car.setMaxSpeed(100)
car.fillFuel()
return car
}
Inject interface types in dependency graph
interface Logger { fun log() }
class DiscLogger : Logger{ override fun log() { } }
class MemoryLogger : Logger { override fun log() { } }
#Provides
fun provideLogger(): Logger {
val logger = DiscLogger() \\ or MemoryLogger()
return logger
}
#Inject:- It is used to inject dependencies in class.
#provides:- Required to annotated the method with #provide where actual instance is created.
This has covered in Dagger-2 documentation clearly.
What #Inject can Do:
Use #Inject to annotate the constructor that Dagger should use to
create instances of a class. When a new instance is requested, Dagger
will obtain the required parameters values and invoke this
constructor.
If your class has #Inject-annotated fields but no #Inject-annotated
constructor, Dagger will inject those fields if requested, but will
not create new instances. Add a no-argument constructor with the
#Inject annotation to indicate that Dagger may create instances as
well.
Similarly dagger can create for methods also.
Classes that lack #Inject annotations cannot be constructed by
Dagger.
What #Inject can't Do: and can be used #Provides annotation
Interfaces can’t be constructed.
Third-party classes can’t be annotated.
Configurable objects must be configured!

Android Hilt : How I can inject any object in Object class or singleton class

I want to inject application string inside object class SingletonObject. I am new in Hilt and not getting any way to inject this
object SinglentonObject{
#AppQualifier
#Inject
lateinit var applicationString: String
}
The #Inject annotation is available only for EntryPoints like #AndroidEntryPoint, #HiltAndroidApp. As of now, there is no option to inject into a non-entry point classes in Hilt.
You can't use the #Inject annotation but, if you still want to have a single source of truth and use Hilt from anywhere, you can create a custom EntryPoint and then use the ApplicationContext to grab an instance of whatever you are providing with Hilt.
First, you have to declare the EntryPoint (it can be added anywhere you want, but the best practice is to keep it closer to where it's used):
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ApplicationStringInterface {
#AppQualifier fun getApplicationString(): String
}
then you can grab an instance of the ApplicationString like this:
var applicationString = EntryPoints.get(applicationContext, ApplicationStringInterface::class.java).getApplicationString();
If you need help to get an instance of the applicationContext from anywhere, look here: https://stackoverflow.com/a/54076015/293878

What's the difference between Kotlin object and class in the context of a dagger module

I was going through one of my colleagues codebase. And I found this piece of code.
#Module
object SampleAppModule {
#Provides
#JvmStatic
#AppScope
fun provideAppDependency(context: Context): AppDependency = SampleAppDependency(context)
}
And this got me thinking, how's this different from this
#Module
class SampleAppModule {
#Provides
#AppScope
fun provideAppDependency(context: Context): AppDependency = SampleAppDependency(context)
}
I've seen the use of object in dagger modules recently, but I myself never used it because I didn't understand what it does. Would love to get some insights.
p.s. I tried changing the object to class, and it worked. Now I really don't know if there is any difference.
Using object to declare your Dagger modules would create only a single instance of it.
If your modules with #Provides are declared as class instead of object, then an additional object is generated on building the component. So using object, you get better performance.
Another way to do this would be using companion object. But that is not recommended:
Beyond that, don't use companion object for modules. Use object. In
that case the instance will be unused and its initialization code will
be removed by R8 and the methods will be truly static and can also be
inlined just like Java.

Provide all childs of a class with dagger

im creating a highly modular application, i have a lot of clases that need to be injected, all of them are childs (not direct childs) of the same class, none of them have constructor parameters.
I want to avoid having to create a "#Provides" method for each one of them in my module.
Is there a way to tell dagger to automatically provide all the classes that implement a base interface? Or is it possible to do it myself using reflection?
Im using dagger-android with kotlin
Update: Ill post some code to illustrate
In one of the modules i have this interface
interface ExampleClass: BaseExample {
fun doSomething()
}
}
Then in the main app i implement it
class ExampleClassImpl #Inject constructor() : ExampleClass {
override fun doSomething(){
}
}
The class where i need it is a Viewmodel created with dagger so inject works on the constructor.
class ExampleViewModel #Inject constructor(val exmpl :ExampleClass) : BaseViewModel {
}
I want to inject that ExampleClassImpl, to do that i need to create a #module with a method annotated with #Provides or #Bind and return that class.
Without the provider i get an error at compile time:
error: [Dagger/MissingBinding] com.myapp.ExampleClassImpl cannot be provided without an #Provides-annotated method.
You want to inject ExampleClass, but Dagger only knows about ExampleClassImpl. How would Dagger know that you want that specific subclass?
Moreover, you say you have many subclasses that you want to inject. How would Dagger know which one to provide to a constructor expecting the base class?
If you want ExampleViewModel to get an instance of ExampleClassImpl then you can simply change the declaration to:
class ExampleViewModel #Inject constructor(val exmpl :ExampleClassImpl)
Doing so you lose the ability to swap the constructor argument with a different implementation of ExampleClass.
The alternative is to have one #Named #Provides method per subclass. So something like:
// In your module
#Provides
#Named("impl1")
fun provideExampleClassImpl1(impl: ExampleClassImpl): ExampleClass = impl
// When using the named dependency
class ExampleViewModel #Inject constructor(#Named("impl1") val exmpl :ExampleClass)

Categories

Resources