Injecting fragment with activity reference - android

I have a class DashboardActivity with two fragments, A and B, in a viewpager. Each Fragment has its own ViewModel, AViewModel and BViewModel, respectively.
Now currently I'm creating a Subcomponent for the DashboardActivity, which binds both viewmodel instances and injects them into the fragments via ContributesAndroidFragment.
How could I create a Subcomponent per Fragment, so that dependencies requiring an activity (in this case NavigationController) could still be fullfilled? i.e. How can I create this subcomponent by providing the activity the fragment is being attached to?
Consider that these fragments could be added elsewhere in the app so the attaching activity could be different in runtime.
This is how I've done it:
AppComponent:
#Singleton
#Component(
modules = [
ApplicationModule::class,
AndroidSupportInjectionModule::class,
NetModule::class,
...
ActivityBindings::class
]
)
interface ApplicationComponent : AndroidInjector<MyApp> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): ApplicationComponent
}
}
ActivityBindings:
#Module
abstract class ActivityBindings {
#ContributesAndroidInjector(modules = [DashboardModule::class])
#PerActivity
abstract fun dashboardModule(): DashboardActivity
...
}
DashboardModule:
#Module(includes = [CommonActivityModule::class])
abstract class DashboardModule {
#Binds
abstract fun bindsActivity(activity: DashboardActivity): AppCompatActivity
#ContributesAndroidInjector
abstract fun contributeFragmentA(): FragmentA
#ContributesAndroidInjector
abstract fun contributesFragmentB(): FragmentB
#Binds
#IntoMap
#ViewModelKey(BViewModel::class)
#PerActivity
internal abstract fun bViewModel(bViewModel: BViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
#PerActivity
internal abstract fun aViewModel(aViewModel: AViewModel): ViewModel
}
CommonActivityModule:
#Module(includes = [ViewContainerModule::class, ViewModelBuilder::class])
abstract class CommonActivityModule {
#Binds
#ForActivity
abstract fun bindsContext(activity: AppCompatActivity): Context
#Module
companion object {
#Provides
#PerActivity
#JvmStatic
fun provideNavigationController(activity: AppCompatActivity) = NavigationController(activity)
}
}
PS. I need to be able to inject more fragments from fragmentA and fragmentB, so they should also act as AndroidInjectors

Related

dagger error binding with matching key exists in component

Dagger 2
/di/AppComponent.java:19: error: [Dagger/MissingBinding] ProductListFragment cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent {
^
A binding with matching key exists in component: MainFragmentProvider_BindProductListFragment.ProductListFragmentSubcomponent
ProductListFragment is injected at
productlist.ProductListModule.provideChildFragmentManager(productListFragment)
androidx.fragment.app.FragmentManager is injected at
productlist.adapter.NewProductListPagerAdapter(…, fragmentManager)
productlist.adapter.NewProductListPagerAdapter is injected at
productlist.ProductListV2Fragment.mPagerAdapter
productlist.ProductListV2Fragment is injected at
dagger.android.AndroidInjector.inject(T) [di.AppComponent → di.BuilderModule_BindMainActivity.MainActivitySubcomponent → MainFragmentProvider_BindProductListV2Fragment.ProductListV2FragmentSubcomponent]
I have the following module with these 2 fragments here:
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class])
abstract fun bindProductListFragment(): ProductListFragment
}
In my ProductListModule I have the following:
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: ProductListFragment) =
productListFragment.childFragmentManager
}
The fragmentManager will be injected into the following class:
class NewProductListPagerAdapter #Inject constructor(
#ActivityContext private val context: Context,
fragmentManager: FragmentManager
) { ..... }
And both fragments will inject this NewProductListPagerAdapter in them.
class ProductListV2Fragment : BaseProductListFragment() {
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
}
class ProductListFragment : BaseProductListFragment() {
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
}
My AppComponent:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ProductModule::class,
BaseAppModule::class
]
)
interface AppComponent {
#Component.Factory
interface Factory {
fun create(
#BindsInstance application: Application,
): AppComponent
}
fun inject(tsApplication: TsApplication)
}
==== UPDATE ====
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class, ProductListBindingModule::class])
abstract fun bindProductListFragment(): ProductListFragment
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class, ProductListV2BindingModule::class])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
}
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: Fragment): FragmentManager =
productListFragment.childFragmentManager
}
#Module
abstract class ProductListBindingModule {
#Binds
abstract fun bindFragment(fragment: ProductListFragment): Fragment
}
#Module
abstract class ProductListV2BindingModule {
#Binds
abstract fun bindFragment(fragment: ProductListV2Fragment): Fragment
}
The stack trace is below which is indicating that the Fragment is bound multiple times. The reason being is because each fragment injects the following dependency:
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
And in the NewProductListPagerAdapter constructor:
class NewProductListPagerAdapter #Inject constructor(
#ActivityContext private val context: Context,
fragmentManager: FragmentManager
)
Which needs to provide the FragmentManager;
app/build/tmp/kapt3/stubs/uatDebug/tech/central/tops/di/AppComponent.java:22: error: [Dagger/DuplicateBindings] androidx.fragment.app.Fragment is bound multiple times:
public abstract interface AppComponent {
#org.jetbrains.annotations.NotNull #Binds androidx.fragment.app.Fragment productlist.ProductListBindingModule.bindFragment(productlist.ProductListFragment)
#org.jetbrains.annotations.NotNull #Binds androidx.fragment.app.Fragment productlist.ProductListV2BindingModule.bindFragment(productlist.ProductListV2Fragment)
androidx.fragment.app.Fragment is injected at
authentication.usecase.FacebookLoginUseCase(fragment, …)
authentication.usecase.FacebookLoginUseCase is injected at
authentication.usecase.SocialLoginUseCase(…, facebookLoginUseCase, …)
authentication.usecase.SocialLoginUseCase is injected at
authentication.FacebookLoginViewModel(…, socialLoginUseCase, …)
authentication.FacebookLoginViewModel is injected at
authentication.FacebookLoginViewModelModule.facebookViewModelChildFactory(facebookLoginViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
di.factory.ViewModelProviderFactory(classToViewModel)
di.factory.ViewModelProviderFactory is injected at
.authentication.login.LoginFragment.mViewModelChildFactory
authentication.login.LoginFragment is injected at
dagger.android.AndroidInjector.inject(T) [tech.central.tops.di.AppComponent → di.BuilderModule_BindMainActivity.MainActivitySubcomponent → FragmentProvider_BindLoginFragment.LoginFragmentSubcomponent]
Your module depends on ProductListFragment to obtain a child FragmentManager. However, that same module is used to inject into ProductListV2Fragment, and there is no ProductListFragment available at that point.
In order to reuse that module, you will need to split the dependency on ProductListFragment into a separate module. This module should be used for ProductListFragment, and a similar module should be used for ProductListV2Fragment. In these modules, you can either provide FragmentManager directly or bind both fragments to a common superclass such as Fragment or BaseProductListFragment.
If you bind both fragments to Fragment, the resulting code will look like this:
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [
ProductListModule::class,
ProductListV2BindingModule::class
])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
#PerFragment
#ContributesAndroidInjector(modules = [
ProductListModule::class,
ProductListBindingModule::class
])
abstract fun bindProductListFragment(): ProductListFragment
}
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: Fragment) =
productListFragment.childFragmentManager
}
#Module
abstract class ProductListBindingModule {
#Binds
fun bindFragment(fragment: ProductListFragment): Fragment
}
#Module
abstract class ProductListV2BindingModule {
#Binds
fun bindFragment(fragment: ProductListV2Fragment): Fragment
}
As for the error message itself, it tells you three things:
The generated ProductListV2FragmentSubcomponent requires a ProductListFragment, but there is no available binding for it.
The subcomponent may be able to inject dependencies into an existing ProductListFragment. This can be useful information in other situations, but it isn't relevant to what you're trying to do.
There is a component in your app which can provide ProductListFragment. Specifically, this is the generated ProductListFragmentSubcomponent, which takes a ProductListFragment in its factory.

Field injection in AppWidgetProvider in kotlin using dagger 2

I am using dagger2 and kotlin in my project. I have injected activity and viewmodels and now I want to inject appwidgetprovider class for app widgets. I can`t find a way to inject fields in to appwidgetprovider class. Here is my dagger2 implementaion.
this is App Component class
#Singleton
#Component(
modules = [
UserInformationModule::class,
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
ServiceBuilderModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: BaseClass)
}
This is AppModule class
#Module(includes = [ViewModelModule::class, CoreDataModule::class])
class AppModule {
#Singleton
#Provides
fun provideMyMyAppService(
#MyAppAPI okHttpClient: OkHttpClient,
converterFactory: MoshiConverterFactory
) = provideService(okHttpClient, converterFactory, MyMyAppApi::class.java)
#MyAppAPI
#Provides
fun providePrivateOkHttpClient(
upstreamClient: OkHttpClient
): OkHttpClient {
return upstreamClient.newBuilder().build()
}
#Singleton
#Provides
fun provideRemoteDataSource(myMyAppService: MyMyAppApi) = RemoteDataSource(myMyAppService)
#Singleton
#Provides
fun provideDb(app: Application) = AppDatabase.getInstance(app)
//other code
This is Fragment Builder Module
#Suppress("unused")
#Module
abstract class FragmentBuildersModule {
#ContributesAndroidInjector
abstract fun homeFragment(): HomeFragment
#ContributesAndroidInjector
abstract fun fragHome(): FragHome
//other code
}
this is my Main Activity Module
#Suppress("unused")
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): HomeActivity
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeSplashActivity(): SplashActivity
}
This is my ViewModel Module
#Suppress("unused")
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel
///other code
}
I tried to inject appwidgetprivider class using
AndroidInjection.inject(this)
as I did in Service. But this method only excepts Activity, Fragment, service, broadcast receiver and contentproviders. Any help please.
I am using dagger 2.23.2 and kotlin 1.3.41
Appwidget provicer can be injected the same way a broadcast receiver is injected.
By looking at your provided code you can do some thing like this.
Create an abstract function
#ContributesAndroidInjector
internal abstract fun contributeWidget(): YourWidgetClass
extend your Baseclass with HasBroadcastReceiverInjector and implement broadcastReceiverInjector
#Inject
lateinit var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver>
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> {
return broadcastReceiverInjector
}
and fillany inject in the widgetprovider class in onreceive
before super call
AndroidInjection.inject(this, context)

Dagger class cannot be provided without an #Inject constructor or an #Provides-annotated method

Hello I'm new to Dagger and I have crated a simple project to learn more about it. I have a class PermissionManager that has activity as constructor parameter
class PermissionManager(activity: MainActivity) {
}
and my MainFragment has a dependency on it. So I created BindingModule
#Module
interface BindingModule {
#DaggerScope(MainActivity::class)
#ContributesAndroidInjector(modules = [MainActivityModule::class])
fun provideMainActivity(): MainActivity
#FragmentScope
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
fun provideMainFragment(): MainFragment
}
Here's my MainActivityModule that provides PermissionManager
#Module
abstract class MainActivityModule private constructor() {
#Module
companion object {
#Provides
#JvmStatic
fun providePermissionManager(activity: MainActivity): PermissionManager = PermissionManager(activity)
}
}
and here's my MainFragmentModule that has to use PermissionManager that was created in my MainActivityModule
#Module
abstract class MainFragmentModule private constructor() {
#Module
companion object {
#JvmStatic
#Provides
#IntoMap
#ViewModelKey(MyTestViewModel::class)
fun provideModelFactory(
permissionManager: PermissionManager
): ViewModel = MyTestViewModel(permissionManager)
}
}
and here's what I get
com\nav\component\di\AppComponent.java:12: error: [Dagger/MissingBinding] com.nav.component.utils.PermissionManager cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.nav.component.MyTestDaggerApp>
So first of all I don't understand why I can't use dependency that was created for activity in my fragment? Any ideas how to solve this?
EDIT:
Here's how Binding Module is used
#AppScope
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
BindingModule::class,
NetworkingModule::class
]
)
interface AppComponent : AndroidInjector<MyTestDaggerApp> {
/**
* AppComponent Builder interface. All implementation part is handled by a dagger compiler.
*/
#Component.Factory
interface Factory : AndroidInjector.Factory<MyTestDaggerApp>
}
Make your fragment a subcomponent of BindingModule to get bindings from it. Don't forget to make MainActivity implement HasAndroidInjector (if it doesn’t already).
#Module
interface BindingModule {
#DaggerScope(MainActivity::class)
#ContributesAndroidInjector(modules = [MainActivityModule::class, MainFragmentBindingModule::class])
fun provideMainActivity(): MainActivity
}
#Module
interface MainFragmentBindingModule {
#FragmentScope
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
fun provideMainFragment(): MainFragment
}

Android Dagger get Parent Fragment into child fragment

I have been trying to, unsuccessfully, inject the Parent Fragment into its sub fragments for navigation purposes. I have followed a couple of different posts but I can't seem to understand what am I missing in my implementation.
I have a MainActivity that contains a ViewPager with one such page containing EventsFragment. This fragment in turn has two child fragments EventsListFragment and EventsDetailFragment. I wanted to inject EventsFragment fragment into EventsListFragment so that I can tell it to navigate to EventsDetailFragment.
I know this is probably a repetition of the posts below and I really apologize for that but I honestly cannot see what am I missing. These are the posts I've found:
Dagger 2.10 Android subcomponents and builders
How to create custom scoped modules in dagger 2.10
https://google.github.io/dagger/subcomponents.html
My implementation is as follows:
ApplicationComponent
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityModule::class,
BaseApplicationModule::class,
ApplicationModule::class])
interface ApplicationComponent : AndroidInjector<AndroidApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<AndroidApplication>()
}
ActivityModule
#Module(includes = [MainActivityProvider::class])
abstract class ActivityModule{
}
MainActivityProvider
#Module(includes = [EventsFragmentProvider::class])
abstract class MainActivityProvider {
#PerActivity
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun provideMainActivityFactory(): MainActivity
}
EventsFragmentProvider
#Module(includes = [EventsListProvider::class,
EventsDetailProvider::class])
abstract class EventsFragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsFragmentModule::class])
abstract fun provideEventsFragmentFactory(): EventsFragment
}
EventsFragmentModule
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
EventsListProvider
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
EventsFragment
class EventsFragment : DaggerFragment(), EventContract.Flow {
override fun navigateToList() {
addFragment(navigator.getEventsListFragment(context!!))
}
override fun navigateToDetail(id: String) {
println("id = ${id}")
}
...
}
EventContract
interface EventContract {
interface Flow {
fun navigateToList()
fun navigateToDetail(id: String)
}
}
EventsListFragment
class EventsListFragment : DaggerFragment() {
#Inject
lateinit var eventsFlow: EventContract.Flow
...
}
Error
[Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] EventContract.Flow cannot be provided without an #Provides-annotated method.
public abstract interface ApplicationComponent extends dagger.android.AndroidInjector<AndroidApplication> {
^
EventContract.Flow is injected at
EventsListFragment.eventsFlow
EventsListFragment is injected at
dagger.android.AndroidInjector.inject(T)
component path: ApplicationComponent →EventsListProvider_ProvideEventsListFragmentFactory.EventsListFragmentSubcomponent
I may be using a anti-pattern but this is what got me working to this point. Im open to changes that may help me achieve this
The error is caused by the following module:
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
Dagger would not provide you with a Fragment and you shouldn't do that.
Moreover, I think you misunderstand the meaning of #ContributesAndroidInjector. It means creating an AndroidInjector for you but proving an instance.
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
So you should pass your EventsFragment instance into the module like this post instead of using field injection.

Inject #ActivityScoped object into a Fragment

How can I inject a module that is #ActivityScoped into a fragment.
The module I need inside the Fragment looks like this (its injected fine into the activities)
#ActivityScoped
class ClipManager #Inject constructor(private val activity: Activity) { ... }
To bind my MainActivity to Activity I am using an ActivityModule
#Module
abstract class MainActivityModule {
#Binds
#ActivityScoped
internal abstract fun bindActivity(mainActivity: MainActivity): Activity
}
to be able to inject into my MainActivity i use this one
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
}
I am having the following dagger component:
#Singleton
#Component(modules = [
ApplicationModule::class,
AndroidSupportInjectionModule::class,
ActivityModule::class,
FragmentModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): AppComponent.Builder
fun build(): AppComponent
}
}
with a regular context binding ApplicationModule
#Module
abstract class ApplicationModule {
#Binds
#Singleton
internal abstract fun bindContext(application: Application): Context
}
Now I am having the FragmentModule which enables injection into the fragments
#Module
abstract class FragmentModule {
#FragmentScoped
#ContributesAndroidInjector
internal abstract fun fragment1(): Fragment1
}
How can I extend dagger to be able to inject the ClipManager within to Fragment1 (which itself lives in the MainActivity)?
You need to make your FragmentComponent a Subcomponent of your ActivityComponent. Doing this will allow the Fragment to inject anything bound in its parent component.
All you really have to do is remove FragmentModule::class from the list of modules on your AppComponent and add it to your ActivityComponent instead:
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class, FragmentModule::class])
internal abstract fun mainActivity(): MainActivity
}
This way it will be a subcomponent of your MainActivityComponent.

Categories

Resources