Я использую Кинжал 2 для внедрения зависимостей в свой проект
Проблема в том, что я пытаюсь ввести ViewPageAdapter внутри класса фрагмента, который зависит от FragmentManager, как показано ниже:
@Inject
lateinit var mainTripsFragmentAdapter: MainTripsFragmentAdapter
Я получаю следующую ошибку сборки:
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
Я исследовал 3 дня и прочитал много статей, но не мог понять, в чем проблема!
Я отправлю связанный код, извините, если он слишком длинный
Fragment, которому нужен 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
}
}
Базовый фрагмент:
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
}
Вот 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
}
А вот классы, связанные с DI:
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
}
Основная деятельность:
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
}
Класс приложения:
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)
}
}
}
Один момент заключается в том, что если я @Inject PagerAdapter Inside My Activity (я также должен добавить для него модуль), он работает нормально.
Какая часть реализована неправильно?
Если вы посмотрите на ошибку, внизу вы увидите, что проблема в том, что вы пытаетесь ввести tripsFragment в свой MainActivity. Это неправильная область действия, и ваш компонент mainActivity не имеет доступа к вашему графику FragmentSubComponents (который привязка с совпадающим ключом существует в компоненте пытается вам сказать)
@Debanjan Я его нигде не предоставил, но я внедрил конструктор, поэтому считаю, что кинжал выполнит свои зависимости
@DavidMedenjak Поскольку я новичок в dagger2 и DI, я не могу понять, как прицелы связаны друг с другом. Можете ли вы сказать, можно ли это исправить, или я все делаю неправильно
Здесь есть ряд проблем:
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 в PagerAdapter означает «создать элемент». Когда вы используете PagerAdapter, вы делегируете адаптеру создание фрагментов. Вы не должны писать код для кеширования и хранения фрагментов, это работа FragmentManager.
Вместо этого ваш FragmentAdapter должен выглядеть примерно так:
override fun getItem(position: Int) = when (position) {
0 -> MainCurrentTripsFragment.instantiate()
1 -> MainHistoryTripsFragment.instantiate()
else -> MainCurrentTripsFragment.instantiate()
}
Если метод instantiate объявлен внутри сопутствующего объекта внутри ваших фрагментов:
class MainCurrentTripsFragment: Fragment() {
companion object {
@JvmStatic
fun instantiate(args: Bundle?) {
val frag = MainCurrentTripsFragment()
frag.arguments = args
}
}
}
Это стандартная идиома для использования PagerAdapter с фрагментами в Android. Вы не должны предоставлять PagerAdapters или Fragments в качестве зависимостей в графе вашего объекта. Фактически, многие классы Android, управляемые ОС, такие как FragmentManager, Activity, Fragment, BroadcastReceiver, Service, не являются хорошими кандидатами на роль зависимостей в вашем графе объектов с помощью Dagger 2.
Распространенная ошибка при запуске Dagger 2 - это попытаться ввести все, как если бы новое обновление вручную было неправильным. Это просто невозможно в Android.
Пожалуйста, удалите фрагменты, FragmentManager, PagerAdapter и т. д. Из своих модулей. Не пытайтесь предоставить их или связать их с помощью Dagger 2. Затем используйте Dagger 2, чтобы внедрить уровень домена или объекты уровня данных, такие как репозитории, услуги по модернизации и т. д. Если вы застряли, вы всегда можете задать другой вопрос.
И взгляните на официальный Чертежи архитектуры Google Android. В идеале ваше решение должно выглядеть примерно так.
Хотя это не был прямой ответ на вопрос, но я многому научился из вашего ответа, поэтому вы обычно имеете в виду, что вещи Android не должны предоставляться кинжалом, и мы должны просто создать их новый экземпляр в любом месте, где нам нужно?
@Mosius Я понимаю, что это не полный ответ, но одна из причин, по которой вы столкнулись с такими трудностями, заключается в том, что вы пытаетесь сделать все зависимостью, которую нужно внедрить с помощью Dagger 2. FragmentManager не обязательно должен быть зависимостью, просто позвоните getFragmentManager. То же самое и с PagerAdapter, вы можете просто обновить его в onCreate.
Если вы посмотрите на образец проекта, вы получите хорошее представление о том, что вам следует и не следует вводить в виде зависимостей с помощью Dagger 2.
Я использую dagger 2 по старинке, но где вы разместили MainTripsFragmentAdapter в своем модуле?