Reputation: 1682
I'm using Dagger 2 for dependency injection in my project
The problem is when I'm trying to inject a ViewPageAdapter
inside the fragment class which depend on FragmentManager
like bellow:
@Inject
lateinit var mainTripsFragmentAdapter: MainTripsFragmentAdapter
I get the following build error:
error: [dagger.android.AndroidInjector.inject(T)] android.support.v4.app.FragmentManager cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector { ^ android.support.v4.app.FragmentManager is injected at com.example.presenters.main.trips.MainTripsFragmentAdapter.(fragmentManager, …) com.example.presenters.main.trips.MainTripsFragmentAdapter is injected at com.example.presenters.main.trips.MainTripsFragment.mainTripsFragmentAdapter com.example.presenters.main.trips.MainTripsFragment is injected at com.example.presenters.main.MainActivity.tripsFragment com.example.presenters.main.MainActivity is injected at dagger.android.AndroidInjector.inject(arg0) A binding with matching key exists in component: com.example.di.FragmentBuilder_BindMainTripsFragment$app_debug.MainTripsFragmentSubcomponent
I've researched for 3 days and I've read many articles but I couldn't figure out where the problem is!
I'll post the related code, sorry if it's too long
The Fragment
which needs PagerAdapter
:
class MainTripsFragment @Inject constructor() : BaseFragment() {
@Inject
lateinit var mainTripsFragmentAdapter: MainTripsFragmentAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pager.adapter = mainTripsFragmentAdapter
}
}
BaseFragment:
abstract class BaseFragment : DaggerFragment() {
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(getLayout(), container, false)
abstract fun getLayout(): Int
}
Here is the FragmentPagerAdapter
:
class MainTripsFragmentAdapter @Inject constructor(
fragmentManager: FragmentManager,
private val mainCurrentTripsFragment: MainCurrentTripsFragment,
private val mainHistoryTripsFragment: MainHistoryTripsFragment
) : FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int) = when (position) {
0 -> mainCurrentTripsFragment
1 -> mainHistoryTripsFragment
else -> mainCurrentTripsFragment
}
override fun getCount() = 2
}
And here is DI related classes:
AppComponent:
@Singleton
@Component(modules = [
AndroidInjectionModule::class,
ActivityBuilder::class,
FragmentBuilder::class
])
interface AppComponent : AndroidInjector<DaggerApplication> {
fun inject(app: App)
override fun inject(instance: DaggerApplication)
@Component.Builder
interface Builder {
@BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
FragmentBuilder:
@Module
abstract class FragmentBuilder {
@ContributesAndroidInjector
internal abstract fun bindMainHomeFragment(): MainHomeFragment
@Module
internal interface MainTripsFragmentModule {
@Binds
fun bindMainTripsFragment(fragment: MainTripsFragment): Fragment
}
@ContributesAndroidInjector(modules = [FragmentModule::class, MainTripsFragmentModule::class])
internal abstract fun bindMainTripsFragment(): MainTripsFragment
@ContributesAndroidInjector
internal abstract fun bindMainCurrentTripsFragment(): MainCurrentTripsFragment
@ContributesAndroidInjector
internal abstract fun bindMainHistoryTripsFragment(): MainHistoryTripsFragment
}
FragmentModule:
@Module
class FragmentModule {
@Provides
fun provideFragmentManager(fragment: Fragment) = fragment.childFragmentManager
}
ActivityBuilder:
@Module
abstract class ActivityBuilder {
@ContributesAndroidInjector
internal abstract fun contributeMainActivity(): MainActivity
}
MainActivity:
class MainActivity : BaseActivity() {
@Inject
lateinit var tripsFragment: MainTripsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showFragment(tripsFragment)
}
private fun showFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.layout_container, fragment)
.commit()
}
}
BaseActivity:
@SuppressLint("Registered")
abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector() = fragmentInjector
}
Application Class:
class App : Application(), HasActivityInjector {
@Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.apply {
inject(this@App)
}
}
}
One point is that if I @Inject
PagerAdapter Inside My Activity(I also should add a module for it) it works fine.
Which part is Implemented wrong?
Upvotes: 4
Views: 4217
Reputation: 21407
There are a number of problems here:
class MainTripsFragmentAdapter @Inject constructor(
fragmentManager: FragmentManager,
private val mainCurrentTripsFragment: MainCurrentTripsFragment,
private val mainHistoryTripsFragment: MainHistoryTripsFragment
) : FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int) = when (position) {
0 -> mainCurrentTripsFragment //no!!
1 -> mainHistoryTripsFragment
else -> mainCurrentTripsFragment
}
override fun getCount() = 2
}
getItem
in the PagerAdapter
means "create item". When you use a PagerAdapter
you delegate creation of Fragments to the adapter. You shouldn't write code to cache and store the Fragments, this is the job of the FragmentManager
.
Instead your FragmentAdapter
should look something like this:
override fun getItem(position: Int) = when (position) {
0 -> MainCurrentTripsFragment.instantiate()
1 -> MainHistoryTripsFragment.instantiate()
else -> MainCurrentTripsFragment.instantiate()
}
Where the instantiate
method is declared inside a companion object inside your Fragments:
class MainCurrentTripsFragment: Fragment() {
companion object {
@JvmStatic
fun instantiate(args: Bundle?) {
val frag = MainCurrentTripsFragment()
frag.arguments = args
}
}
}
That is the standard idiom for using a PagerAdapter
with Fragments in Android. You shouldn't provide PagerAdapters or Fragments as dependencies in your object graph. In fact, many Android classes controlled by the OS like FragmentManager
, Activity
, Fragment
, BroadcastReceiver
, Service
are not good candidates for becoming dependencies in your object graph using Dagger 2.
A common error when starting to use Dagger 2 is try and inject everything, as if newing up anything manually is wrong. This is just not possible in Android.
Please take the Fragments, the FragmentManager, the PagerAdapter etc out of your modules. Don't try to provide these or bind these using Dagger 2. Then use Dagger 2 to inject domain layer or data layer objects like repositories, Retrofit services etc. If you get stuck, you can always post another question.
And have a look at the offical Google Android Architecture blueprints. Your solution should, ideally, look something like them.
Upvotes: 3