Inject Adapter class to Fragment using Dagger2 - android

I have followed Android Architecture Blueprints Dagger2 for dependency injection: URL
Now I want to inject Adapter to my Fragment class:
#ActivityScoped
class MainFragment #Inject
constructor(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
Main Module class:
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
MainFragmentModule:
#Module
class MainFragmentModule {
#Provides
fun provideArtistAdapter() = ArtistAdapter()
}
And this is my adapter class:
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)
When I build the project I get following Kotlin compiler error:
error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sample.android.lastfm.LastFmApp> {
^
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainModule.bindArtistClickCallback$app_debug(mainFragment)
com.sample.android.lastfm.ArtistClickCallback is injected at
com.sample.android.lastfm.ui.main.ArtistAdapter.artistClickCallback
com.sample.android.lastfm.ui.main.ArtistAdapter is injected at
com.sample.android.lastfm.ui.main.MainFragment.adapter
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainActivity.mainFragment
com.sample.android.lastfm.ui.main.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sample.android.lastfm.di.AppComponent → com.sample.android.lastfm.di.ActivityBindingModule_MainActivity$app_debug.MainActivitySubcomponent]
Can you suggest me how to solve this problem?
Codes can be found at URL

Your fragment should probably not have #ActivityScoped as a scope. Further do not use constructor injection with fragments (or any other framework type)! The Android framework will create those objects in some cases, and you will end up with the wrong reference in your classes. Add the fragment to the corresponding component via its builder.
Also you're using a provides annotated method as well as constructor injection (#Inject constructor()). Pick one. Since you also use field injection within the ArtistAdapter the next "error" you would encounter would be a null callback because you don't inject the adapter anywhere. You just create the object.
Constructor injection should usually be favored, which will also inject fields. Remove the following completely, keep the annotation on the construcor:
#Provides
fun provideArtistAdapter() = ArtistAdapter()
Moving on, your error originates in MainActivitySubcomponent (last line) and seems to be because your MainFragment is bound as an ArtistClickCallback, but requires a ArtistAdapter which requires a ArtistClickCallback...hence your dependency cycle.
This issue should resolve itself once you fix the problems mentioned (#Inject constructor on the fragment in this case) above, since it originates through the MainFragment being constructed by Dagger within the MainActivitySubcomponent, which is the wrong place anyways since your fragment should have a lower scope than the Activity.
Further you need to move your binding (#Binds fun bindArtistClickCallback) into the MainFragmentModule, since there is no fragment to bind in the Activity component (where you add the binding currently)
When you fix all those issues, you will bind your fragment to the correct FragmentSubcomponent, where you will bind it as a Callback, with which you can then create the Adapter and it should work.
I recommend you have a more thorough look on Dagger and make sure to understand all the issues / fixes pointed out.
This is how it should look
#FragmentScoped
class MainFragment(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
}
#Module
class MainFragmentModule {
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)

Related

Android Hilt : fragment needs to be provided for

I am converting an old app to HILT however I am having some difficulties with an interface.
fragment having the issue
#AndroidEntryPoint
class mainFragment : baseFragment(), Listener
My fragment implements a interface called Listener
viewModel
#HiltViewModel
class PviewModel #Inject constructor #Inject constructor(val listener: Listener) : baseViewModel()
I found what I thought was a possible solution here How to Bind/Provide Activity or Fragment with Hilt? but it gives me an error about providing the fragment still
here is the module
#Module
#InstallIn(FragmentComponent::class)
object FragmentModule {
#Provides
fun provideCallback(fragment: mainFragment) =
fragment as Listener
}
Error itself
error: [Dagger/MissingBinding] ....mainFragment cannot be provided without an #Inject constructor or an #Provides-annotated method.

Dagger2 Can't provide dependency of activity to dagger

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.

Hilt Inject into ViewModel without constructor params

With the new dependency injection library Hilt, how to inject some classes into ViewModel without constructor params and ViewModelFactory?
Is it possible?
Like in Fragment, we use only #AndroidEntryPoint and #Inject.
how to inject some classes into ViewModel without constructor params and ViewModelFactory? Is it possible?
Hilt supports constructor injection of ViewModel via the #HiltViewModel (previously #ViewModelInject) annotation.
This allows for any #AndroidEntryPoint-annotated class to redefine their defaultViewModelProviderFactory to be the HiltViewModelFactory, which allows the creation of #HiltViewModel-annotated ViewModels correctly instantiated via Dagger/Hilt.
NEW HILT VERSION:
#HiltViewModel
class RegistrationViewModel #Inject constructor(
private val someDependency: SomeDependency,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
OLD HILT VERSION:
class RegistrationViewModel #ViewModelInject constructor(
private val someDependency: SomeDependency,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
And then
#AndroidEntryPoint
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by viewModels<RegistrationViewModel>() // <-- uses defaultViewModelProviderFactory
Yes, it is possible to inject dependency into a ViewModel class without constructor params. First we need to create a new interface annotated with #EntryPoint to access it.
An entry point is an interface with an accessor method for each
binding type we want (including its qualifier). Also, the interface
must be annotated with #InstallIn to specify the component in which to
install the entry point.
The best practice is adding the new entry point interface inside the class that uses it.
public class HomeViewModel extends ViewModel {
LiveData<List<MyEntity>> myListLiveData;
#ViewModelInject
public HomeViewModel(#ApplicationContext Context context) {
myListLiveData = getMyDao(context).getAllPosts();
}
public LiveData<List<MyEntity>> getAllEntities() {
return myListLiveData;
}
#InstallIn(ApplicationComponent.class)
#EntryPoint
interface MyDaoEntryPoint {
MyDao myDao();
}
private MyDao getMyDao(Context appConext) {
MyDaoEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
appConext,
MyDaoEntryPoint.class
);
return hiltEntryPoint.myDao();
}
}
In the code above we created a method named getMyDao and used EntryPointAccessors to retrieve MyDao from Application container.
Notice that the interface is annotated with the #EntryPoint and it's
installed in the ApplicationComponent since we want the dependency
from an instance of the Application container.
#Module
#InstallIn(ApplicationComponent.class)
public class DatabaseModule {
#Provides
public static MyDao provideMyDao(MyDatabase db) {
return db.MyDao();
}
}
Though the code above has been tested and worked properly but it is not the recommended way to inject dependency into ViewModel by android officials; and unless we know what we're doing, the best way is to inject dependency into ViewModel through constructor injection.

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).

Dagger2 creating a map of concrete instances fails with "cannot be provided without an #Provides-annotate"

I am playing around with Dagger collection, in particular with map.
I want to use Dagger2 to inject a map whose key is an enum and the values a concrete
implementation of an interface. The map
is injected in a presenter. An unique component instantiates the presenter, an
activity uses the instantiation to display the strings produced by the value
of the map.
I get the following error:
e: ../DaggerMap/app/build/tmp/kapt3/stubs/debug/com/aklal/briquedagger2/MainComponent.java:7: error: [Dagger/MissingBinding] java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> cannot be provided without an #Provides-annotated method.
public abstract interface MainComponent {
^
java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> is injected at
com.aklal.briquedagger2.MainPresenter(mapOfLapse)
com.aklal.briquedagger2.MainPresenter is injected at
com.aklal.briquedagger2.MainModule.getMainPresenter(connection)
com.aklal.briquedagger2.Presenter is provided at
com.aklal.briquedagger2.MainComponent.presenter()
FAILURE: Build failed with an exception.
The "project" can be found here
Implementation
I have an interface ChristmasTime which is implemented by two classes:
TimeUntilNextChristmas
TimeSinceLastChristmas
These implementation are similarly simply defined as follow:
class TimeSinceLastChristmas #Inject constructor(): ChristmasTime {
override fun getLapseOfTime() = "SINCE TEST"
}
I want to let Dagger2 create a map with
an enum value as key
ChristmasTime as type value
The key value is defined as follow:
#MapKey
annotation class TimeKey(val value: TIME)
enum class TIME {
UNTIL,
SINCE
}
I created a module to provide concrete implementations of type ChristmasTime:
#Module
interface LapseOfTimeModule {
#Binds
#IntoMap
#TimeKey(TIME.UNTIL)
fun provideLapsesOfTimeUntil(t: TimeUntilNextChristmas): ChristmasTime
#Binds
#IntoMap
#TimeKey(TIME.SINCE)
fun provideLapsesOfTimeSince(t: TimeSinceLastChristmas): ChristmasTime
}
I want to displayed the string returned by the concrete implementations on the
screen. To do so, a presenter communicates with an activity the strings contained
in the map (that has been injected in the presenter):
class MainPresenter #Inject constructor(
private val mapOfLapse: Map<TIME, ChristmasTime>
) : Presenter {
override fun getDateUntil(): String = mapOfLapse[TIME.UNTIL]?.getLapseOfTime() ?: "UNTIL FAILED"
}
The component to instantiate the presenter in the MainActivity takes the module that defines the map (LapseOfTimeModule) and a MainModule
#Component(modules = [MainModule::class, LapseOfTimeModule::class])
interface MainComponent {
fun presenter(): Presenter
}
MainModule is:
#Module
interface MainModule {
#Binds
fun getMainPresenter(connection: MainPresenter): Presenter
}
And the MainActivity is:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var presenter: Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter = DaggerMainComponent.create().presenter()
bttDisplayNewDate.setOnClickListener{
displayDate(
presenter.getDateSince(),
presenter.getDateUntil()
)
}
}
fun displayDate(since: String, until: String) {
tvSince.text = since
tvUntil.text = until
}
}
Does anyone know how to fix that ?
Here are some threads that I read but they did not help much:
Kotlin dagger 2 Android ViewModel injection error
Dagger2 Inherited subcomponent multibindings
Thanks in advance!!
ps: The version of dagger used is 2.24 and kotlin version is 1.3.41
It's a little bit tricky how it works. In Java it will work. Kotlin decompile Map<K,V> just to Map, and Dagger can't find a Map without types of K and V. To fix it please use just java.util.Map for autogenerated daggers class.
class MainPresenter #Inject constructor(
private val mapOfLapse: java.util.Map<TIME, ChristmasTime>
) : Presenter {
of course, then you need to map it into kotlin map to have all extension functions.

Categories

Resources