Hilt injected adapter via builder is null - android

I'm new to dagger and hilt, I'm trying to use Hilt to get my adapter injected to my fragment using a builder (the fragment is actually in a fragment pager adapter so there's multiple instance of it and the adapter), it seems set up correctly, I build the component before super in onCreate but the adapter is null when trying to access it later in the fragment, I wonder if maybe I need to provide it somehow later on so for instance I have my adapter in my fragment
#AndroidEntryPoint
public class CardHolderFragment extends Fragment {
public CardAdapter cardAdapter;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
DaggerCardAdapterComponent.builder()
.cardAdapterDependencies(() -> layoutIdentifier)
.build().inject(this);
super.onCreate(savedInstanceState);
cardAdapter.notifyDataSetChanged(); // CardAdapter.notifyDataSetChanged()' on a null object reference
so my question is if the component is setup correctly should this work or am I missing a step?
What I feel like doing is something like cardAdapter = DaggerCardAdapterComponent.getCardAdapter but I can't put a provides methods in a component, I had a look through the dagger migration guide here which is a little complicated for me as I only had a few weeks with dagger before switching to hilt, but the guide mentions
Finally, you may have to redesign some things if they were configured differently for different activity or fragment components. For example, you could use a new interface on the activity to provide the object.
so I tried this which makes me implement the interface and then return a card adapter but I wasn't sure what I should be returning here, any help welcome
heres my card adapter component
#Component(dependencies = CardAdapterDependencies.class, modules = {TypeFactoryModule.class,
ItemTouchListenerModule.class, GlideModule.class})
public interface CardAdapterComponent {
void inject(CardHolderFragment cardHolderFragment);
#Component.Builder
interface Builder {
Builder cardAdapterDependencies(CardAdapterDependencies cardAdapterDependencies);
CardAdapterComponent build();
}
interface HasCardAdapter{
CardAdapter getCardAdapter();
}
}
and heres my card adapter constructor
#Inject
public CardAdapter(
ItemTouchListener onItemTouchListener,
String layoutIdentifier,
TypeFactory typeFactory,
RequestManager glide
) {
this.onItemTouchListener = onItemTouchListener;
this.typeFactory = typeFactory;
this.layoutIdentifier = layoutIdentifier;
this.glide = glide;
this.elements = new ArrayList<>();
}

First of all you don't need to use #AndroidEntryPoint if you are building your dependent component, even in my case at dynamic feature modules using #AndroidEntryPoint caused it not to compile.
Edit: In your situation building a dependent component or building component with builder does not look necessary. If so, you can use the answer below will help, if you don't need dependent component I will add another edit to do it simple way.
You should use EntryPointAccessors.fromApplication(), EntryPointAccessors.fromActivity() or EntryPointAccessors.fromFragment() to create an interface that has provision methods with #InstallIn and #EntryPoint annotations. I explained here how to build with dependent components and dynamic feature modules using Hilt and you can find the full sample in this git repo.
First create a module with dependencies you provide:
#Module
#InstallIn(ApplicationComponent::class)
class CoreModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#Provides
fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
}
You can change ApplicationComponent to ActivityComponent or FragmentComponent depending on your structure.
Then build a dependencies interface with #EntryPoint and #InstallIn annotations and provision methods which is how you provide the dependencies to dependent components.
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface CoreDependencies {
/*
* Provision methods to provide dependencies to components that
* depend on this component
*/
fun coreDependency(): CoreDependency
fun coreActivityDependency(): CoreActivityDependency
}
Now build the component you should create, it's CardAdapterComponent for you
#Component(
dependencies = [CoreDependencies::class],
modules = [CameraModule::class]
)
interface CameraComponent {
fun inject(cameraFragment1: CameraFragment1)
fun inject(cameraFragment2: CameraFragment2)
fun inject(cameraActivity: CameraActivity)
#Component.Factory
interface Factory {
fun create(coreDependencies: CoreDependencies, #BindsInstance application: Application): CameraComponent
}
}
I used factory pattern, you can use builder if you wish, both the same.
To inject your dependent component to Activity you need to get component using EntryPoints.
private fun initHiltDependencyInjection() {
val coreComponentDependencies = EntryPointAccessors.fromApplication(
applicationContext,
CoreDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreComponentDependencies,
application
)
.inject(this)
}
To inject to the Fragment:
private fun initCoreDependentInjection() {
val coreComponentDependencies = EntryPointAccessors.fromApplication(
requireActivity().applicationContext,
CoreDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreComponentDependencies,
requireActivity().application
)
.inject(this)
}

Related

Hilt using in android library

I would like to try Hilt DI in the android library.
It is a dependency on another project, with its own submodule. The very first problem I've encountered is the requirement of marking Application with #HiltAndroidApp. Now I do not have anything that extends Application in my library ofc but would like to utilize Hilt and its predefined components.
Is it possible or should I go with Dagger only in such a case? I've found a solution for Dagger, where library dependency injection is made totally independently (the client is unaware of the library's DI): Dagger solution, would love to hear any opinion on that, maybe someone already put a great effort into that issue and can share his insights.
If you're trying to include Hilt in an android library, then you should expect the android app (client of your library) to mark its Application with #HiltAndroidApp.
You should include your whole setup (entry points, modules, dependencies, ... whatever you want to have in your library) in the library module, and make the requirement for the client of the library to use the #HiltAndroidApp to use your library correctly.
You don't need to include #HiltAndroidApp in library module to inject the dependencies in library modules to app module or any dynamic feature modules.
This sample has only core library module, app, and dynamic feature modules. Dynamic feature module implementation is optional.
Result of injecting from core library module to App's Activity and Fragment is as
Project dependency Structure
feature_hilt_camera feature_hilt_photos (Dynamic Feature Modules)
| | |
| ----App----
| |
core(android-library)
In core library module have a dagger module as
#InstallIn(ApplicationComponent::class)
#Module
class CoreModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#Provides
fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
#Provides
fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()
#Provides
fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()
#Provides
fun provideAnotherDependency() = AnotherDependency()
}
And inject to Activity as
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
/**
* Injected from [CoreModule] with #Singleton scope
*/
#Inject
lateinit var coreDependency: CoreDependency
/**
* Injected from [CoreModule] with no scope
*/
#Inject
lateinit var coreActivityDependency: CoreActivityDependency
/**
* Injected from [MainActivityModule] with no scope
*/
#Inject
lateinit var toastMaker: ToastMaker
/**
*
* Injected from [MainActivityModule] with #ActivityScoped
* * To inject this there should be #Binds that gets Context from an Application
*/
#Inject
lateinit var mainActivityObject: MainActivityObject
/**
* Injected via constructor injection with no scope
*/
#Inject
lateinit var sensorController: SensorController
/**
* Injected via constructor injection with #Singleton scope
*
* ### Unlike Tutorial 9-2 This can be injected because MainActivity's component does not
* depend on any component with another scope
*/
#Inject
lateinit var singletonObject: SingletonObject
#Inject
lateinit var anotherDependency: AnotherDependency
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.tvInfo).text =
"CoreModule #Singleton coreDependency: ${coreDependency.hashCode()}\n" +
"CoreModule no scope coreActivityDependency: ${coreActivityDependency.hashCode()}\n" +
"CoreModule no scope anotherDependency: ${anotherDependency.hashCode()}\n" +
"MainActivityModule #ActivityScoped mainActivityObject: ${mainActivityObject.hashCode()}\n" +
"MainActivityModule no scope toastMaker: ${toastMaker.hashCode()}\n" +
"Constructor no scope sensorController: ${sensorController.hashCode()}\n"
"Constructor #Singleton singletonObject: ${singletonObject.hashCode()}"
}
}
and it's same for HomeFragment which is in app module
#AndroidEntryPoint
class HomeFragment : Fragment() {
/**
* Injected from [CoreModule] with #Singleton scope
*/
#Inject
lateinit var coreDependency: CoreDependency
/**
* Injected from [CoreModule] with no scope
*/
#Inject
lateinit var coreActivityDependency: CoreActivityDependency
#Inject
lateinit var homeFragmentObject: HomeFragmentObject
/**
* This dependency cannot be injected since this fragment's component does not depend on CoreComponent
* unlike Tutorial 9-2 counterpart
*/
#Inject
lateinit var mainActivityObject: MainActivityObject
#Inject
lateinit var fragmentObject: FragmentObject
}
If you also wish to inject to dynamic feature modules you need a provision module in your library module as
/**
* This component is required for adding component to DFM dependencies
*/
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface CoreModuleDependencies {
/*
🔥 Provision methods to provide dependencies to components that depend on this component
*/
fun coreDependency(): CoreDependency
fun coreActivityDependency(): CoreActivityDependency
fun coreCameraDependency(): CoreCameraDependency
fun corePhotoDependency(): CorePhotoDependency
}
and dynamic feature module you will use this interface as dependent component
In camera dynamic feature module have a component like this
#Component(
dependencies = [CoreModuleDependencies::class],
modules = [CameraModule::class]
)
interface CameraComponent {
fun inject(cameraFragment1: CameraFragment1)
fun inject(cameraFragment2: CameraFragment2)
fun inject(cameraActivity: CameraActivity)
#Component.Factory
interface Factory {
fun create(coreComponentDependencies: CoreModuleDependencies,
#BindsInstance application: Application): CameraComponent
}
}
and inject it to your dynamic feature fragment with
private fun initCoreDependentInjection() {
val coreModuleDependencies = EntryPointAccessors.fromApplication(
requireActivity().applicationContext,
CoreModuleDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreModuleDependencies,
requireActivity().application
)
.inject(this)
}
Full sample that in image is here, and you check out implementation for both libraries and dynamic feature modules in this sample project.
It's possible to integrate Hilt into your library, but you will have to handle the case where the app is not a Hilt application. You can handle this case by annotating your Activity/Fragment with #OptionalInject and then checking OptionalInjectCheck#wasInjectedByHilt() to check if the Activity/Fragment was injected by Hilt or not.
#OptionalInject
#AndroidEntryPoint
public final class MyFragment extends Fragment {
...
#Override public void onAttach(Activity activity) {
super.onAttach(activity); // Injection will happen here, but only if the Activity used Hilt
if (!OptionalInjectCheck.wasInjectedByHilt(this)) {
// Get Dagger components the previous way and inject manually
}
}
}
Note that doing this will not make your library simpler (it'll actually get more complex since you need to support both Hilt and non-Hilt applications). The main benefit would be to your clients that use Hilt, since they wouldn't need to do any component/module setup to get your library up and running in their app.

Dagger2 dependency Cycle by Using #Binds and #Inject fields

I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.
My top level component looks like
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
val activityComponentBuilder: ActivityComponent.Builder
}
#Module(subcomponents = [ActivityComponent::class])
interface AppModule {
#Binds
fun mockWebService(mockWebService: MockWebService): MockWebService
}
Next my subcomponent looks like
#ActivityComponent
#Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(sharedViewModel: SharedViewModel)
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun storage(storage: Storage): Builder
fun build(): ActivityComponent
}
}
In my activity module I bind two objects
#Binds
abstract fun controller(controller: Controller): Controller
#Binds
abstract fun navigator(navigator: Navigator): Navigator
Each object has an #Inject constructor
class Navigator #Inject constructor(private val storage: Storage)
class Controller #Inject constructor(
private val webService: MockWebService,
private val navigator: Navigator,
private val storage: Storage
) {
Inside my shared view model I try to build my component and inject the fields
#Inject
lateinit var navigator: Navigator
#Inject
lateinit var controller: Controller
init {
MainApplication.component.activityComponentBuilder
.storage(InMemoryStorage.from(UUID.randomUUID().toString()))
.build()
.inject(this)
}
But dagger won't build. I get an error
[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {
MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at
But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?
#Binds is used to let dagger know the different implementations of an interface. You don't need #Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have #Inject constructor, which means dagger can instantiate them and we don't need to write extra #Provides functions for those classes.
#Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add #XScope with #Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.
As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).

How to get component dependencies in Dagger 2 module using Kotlin on Android?

I have a Dagger2 Component for Fragment on Android. I initialize the component by injecting the fragments dynamically. Now, I need to provide activity context to dependencies from the fragment module. I assumed writing a provider method with Fragment as a parameter will automatically get me the Fragment reference in the module, and I can extract the context out of it. But I can't compile my code.
The application component also offers a Context, therefore I have added a qualifier to get activity context. That shouldn't create any issues I believe. Here's my code :
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class FragmentScope
#FragmentScope
#Component(modules = [FragmentModule::class],
dependencies = [AppComponent::class])
interface FragmentComponent {
fun inject(myFragment: MyFragment)
#Component.Builder
interface Builder {
fun appComponent(component: AppComponent): Builder
fun build(): FragmentComponent
}
}
#Module
object FragmentModule {
#Provides
#JvmStatic
#Named("Fragment")
fun provideContext(myFragment: MyFragment): Context = myFragment.context!!
}
Compilation Error:
[Dagger/MissingBinding] MyFragment cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
A binding with matching key exists in component: FragmentComponent
I think you are mistaking the inject method of the FragmentComponent. That will trigger an injection to the fragment, not injection of fragment to the component. If you want to get the context of the activity from the fragment, you have to pass it to your module during initialization.
#Module
class FragmentModule(val fragment : Fragment) {
#Provides
fun provideContext(): Context = fragment.context!!
}

Dagger 2 and dependency injection hell?

How do you use dagger from Kotlin?
I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1
Here is all I need:
AppDependencies
GenericActivityDependencies
PerActivityDependency
Here are my main dependencies:
App
#Module
class ApplicationModule(private val application: Application) {
#Provides
#Singleton
fun provideContext(): Application = this.application
}
#Singleton
#Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
val app: Application
}
Why do I need to once provide the dependency in the module and another time define it in the component?
Activity Module
#Module
class ActivityModule(private val activity: Activity) {
#PerActivity
#Provides
#ActivityContext
fun provideContext(): Context = activity
}
#Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
#ActivityContext
interface ActivityComponent {
fun inject(activity: MainActivity)
}
HomeModule
#Module
class LandingModule {
#PerActivity
#Provides
fun provideSomethig(): Something {
return Something()
}
}
#SomeActivity
#Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
val something: Something
}
By this point, I have written more code than there needs to be in my whole activity.
I get errors like can't inherit from a scopes component
Can't generate Dagger gencode
Subcomponent needs a different scope
How do I achieve this?
Is there a better di for kotlin?
Is there a sample somewhere I could follow that has per activity module?
I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context
}
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun provideApplication(): Application= application
}
The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,
Why the heck do I need to once provide the dependency in the module
and another time define it in the component. This is plain stupid.
is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
You are not exposing anything here but you can still get the activity context from the container thru inject.
Now let's proceed to scoping.
Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:
#PerActivity
#Component(dependencies = [AppComponent::class],
modules = [ActivityModule::class])
interface ActivityComponent{
fun inject(activity: MainActivity)
}
The corresponding module should only be scoped with PerActivity as well.
class ActivityModule(activity:Activity) {
#PerActivity
#Provides
fun provideActivity() = activity
}
Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.
As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.
I have written a small guide to learning dagger 2. If you are interested, you can go check it out.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.
I agree, but don't be discouraged by that fact, because once you learn to master it you'll learn to appreciate it and really take advantage. I've been using version 2.2 for a while without any issues. I've only needed to define one annotation, add a pair of additional dependencies (AutoDagger among them, which takes care of that component feature), and use the following structure:
/* Dagger */
implementation "com.google.dagger:dagger:2.2"
// Fix: github.com/rharter/auto-value-gson/issues/43#issuecomment-219994018
kapt 'com.squareup:javapoet:1.9.0'
kapt "com.google.dagger:dagger-compiler:2.2"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
/* Autodagger */
kapt "com.github.lukaspili.autodagger2:autodagger2-compiler:1.1"
implementation "com.github.lukaspili.autodagger2:autodagger2:1.1"
DaggerScope.java
#Retention(RetentionPolicy.RUNTIME)
#Scope
#interface DaggerScope {
}
YourApp.kt
#AutoComponent(modules = [YourApp.Module::class])
#AutoInjector
#DaggerScope
class YourApp : Application() {
...
#Singleton #dagger.Module
inner class Module(private val app : YourApp) {
#Provides #AutoExpose(YourApp::class) fun application(): Application = app
#Provides #AutoExpose(YourApp::class) fun context(): Context = app
...
// Stuff like your database or base service can go here
}
}
SomeActivity.kt
#AutoComponent(dependencies = [YourApp::class],
modules = [SomeActivity.Module::class]) // you are free to add other modules here
#AutoInjector
#DaggerScope
class SomeActivity : AppCompatActivity() {
...
#dagger.Module
inner class Module() {
#Provides #AutoExpose(SomeActivity::class) fun something(): Something {
return some way of creating Something
}
/* specific deps for SomeAcitivity's actions, like specific services.
You can also access DAOs as you've got access to the DB */
}
}
You can also mimic this structure with a Fragment instead of an Activity.
Hope this helps you!

Dagger 2 how to access same component everywhere

How do I create a singleton object that I can access anywhere from any place in my code?
Objects(1) from which I want to inject my signleton component can't be created withing dagger.
Those objects don't have common dependencies which I can access (like getApplication()
Quick to an example:
InjectedClass:
public class InjectedClass {
public InjectedClass() {
System.out.println("injected class created");
}
}
HolderClassA:
public class HolderClassA { // this is one of object I marked with (1)
#Inject InjectedClass b;
public HolderClassA() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
HolderClassB:
public class HolderClassB { // this is another object I marked with (1)
#Inject InjectedClass b;
public HolderClassB() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
Provider:
#Module
public class Provider {
#Provides
#Singleton
InjectedClass provideInjectedClass() {
return new InjectedClass();
}
}
Injector:
#Component(modules = {Provider.class})
#Singleton
public interface Injector {
void inject(HolderClassA a);
void inject(HolderClassB a);
}
Somewhere in code:
new HolderClassA();
Somewhere else in code that is NO WAY related to previous code, nor has the same parent or can access same objects (like in dagger guide with getApplication()):
new HolderClassB();
Actual result: two instances of InjectedClass are crated
Expected result: a single instance of InjectedClass is created.
The issue is DaggerInjector.builder().build(); creates different scopes which don't know about each other. For example, I can solve this issue by creating static variable DaggerInjector.builder().build() and call inject from it. But thus it wouldn't be dependency injection anymore, but rather not trivial singleton pattern.
From what I understand from comments, you want separate components for your holder classes, that would inject same instance? Pardon me if I am wrong.
You can try this.
#Component(modules = arrayOf(AppModule::class))
#Singleton
interface AppComponent {
//sub components
fun plusHolderClass1(holderModule: HolderModule1):HolderComponent1
fun plusHolderClass2(holderModule: HolderModule2):HolderComponent2
//provision methods
fun getInjectedClass():InjectedClass
}
This is your application component, or the top level component, that you initialise in your application, of course using your Module class that will provide the Injected class as a singleton.
#Subcomponent(modules = arrayOf(HolderModule1::class))
#ActivityScope
interface HolderComponent1{
fun inject(holder:Holder1)
}
And a similar one for Holder2 class. You can define your local scope dependencies in the modules.
But of course even in this case you have to store the instance of appComponent in Application class.
While injecting
appComponent.plusHolderComponent1(HolderModule1()).inject(yourObject)
The InjectedClass object will be injected to yourObject by fetching it from provision methods

Categories

Resources