Dagger2 + MVP на Котлине

Я изучаю Dagger2 + MVP и делаю это на Котлине. И у меня есть проблема в понимании Dagger2 или MVP или там сочетание.

Построение приложения и идеи, как он должен работать, очень просты. Приложение состоит из MenuActivity с левосторонней навигацией и несколькими Fragments (скажем, 3), которые должны быть изменены в FrameLayout в FrameLayout .

Я прочитал несколько статей и потрачу уже пару дней на изучение Dagger2. Эта статья, которую я использую в качестве учебника для создания моего примера: https://proandroiddev.com/dagger-2-part-ii-custom-scopes-component-dependencies-subcomponents-697c1fa1cfc

В моей идее архитектура Dagger должна состоять из трех @Component s: (1) AppComponent, (2) MenuActivityComponent и (3) AccountFragmentComponent. И из моего понимания и картины архитектуры в статье моя архитектура может быть такой: (3) зависит от -> (2) зависит от -> (1)

Каждый @Component имеет @Module : (1) AppModule, (2) MenuActivityModule и (3) AccountFragmentModule соответственно. Для более чистого способа MVP-зависимостей, насколько я понимаю, оба (2) MenuActivityModule и (3) AccountFragmentModule должны @Provide Presenter s из идеологии MVP быть @Inject в MenuActivity и других Fragment , таких как AccountFragment .

AppModule

 @Module class AppModule(val app : App){ @Provides @Singleton fun provideApp() = app } 

AppComponent

 @Singleton @Component(modules = arrayOf(AppModule::class)) interface AppComponent{ fun inject(app : App) fun plus(menuActivityModule: MenuActivityModule): MenuActivityComponent } 

MenuActivityModule

 @Module class MenuActivityModule(val activity : MenuActivity) { @Provides @ActivityScope fun provideMenuActivityPresenter() = MenuActivityPresenter(activity) @Provides fun provideActivity() = activity } 

MenuActivityComponent

 @ActivityScope @Subcomponent(modules = arrayOf(MenuActivityModule::class)) interface MenuActivityComponent { fun inject(activity: MenuActivity) fun plus(accountsModule : AccountsFragmentModule) : AccountsFragmentComponent } 

AccountsFragmentModule

 @Module class AccountsFragmentModule(val fragment: AccountsFragment){ @FragmentScope @Provides fun provideAccountsFragmentPresenter() = AccountsFragmentPresenter(fragment) } 

AccountsFragmentComponent

 @FragmentScope @Subcomponent(modules = arrayOf(AccountsFragmentModule::class)) interface AccountsFragmentComponent { fun inject(fragment: AccountsFragment) } 

Кроме того, у меня есть два @Scope s: ActivityScope и FragmentScope, так как я понимаю, что это гарантирует существование только одного Компонента на время, необходимое каждому компоненту в приложении.

ActivityScope

 @Scope annotation class ActivityScope 

FragmentScope

 @Scope annotation class FragmentScope 

В классе Application я создаю график зависимостей @Singleton .

 class App : Application(){ val component : AppComponent by lazy { DaggerAppComponent .builder() .appModule(AppModule(this)) .build() } companion object { lateinit var instance : App private set } override fun onCreate() { super.onCreate() component.inject(this) } } 

В MenuActivity:

 class MenuActivity: AppCompatActivity() @Inject lateinit var presenter : MenuActivityPresenter val Activity.app : App get() = application as App val component by lazy { app.component.plus(MenuActivityModule(this)) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_menu) /* setup dependency injection */ component.inject(this) /* setup UI */ setupMenu() presenter.init() } private fun setupMenu(){ navigationView.setNavigationItemSelectedListener({ menuItem: MenuItem -> selectDrawerItem(menuItem) true }) /* Hamburger icon for left-side menu */ supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp) supportActionBar?.setDisplayHomeAsUpEnabled(true) drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close); drawerLayout.addDrawerListener(drawerToggle as ActionBarDrawerToggle) } private fun selectDrawerItem(menuItem: MenuItem){ presenter.menuItemSelected(menuItem) // Highlight the selected item has been done by NavigationView menuItem.isChecked = true // Set action bar title title = menuItem.title // Close the navigation drawer drawerLayout.closeDrawers() } @SuppressLint("CommitTransaction") override fun showFragment(fragment: Fragment, isReplace: Boolean, backStackTag: String?, isEnabled: Boolean) { /* Defining fragment transaction */ with(supportFragmentManager.beginTransaction()){ /* Select if to replace or add a fragment */ if(isReplace) replace(R.id.frameLayoutContent, fragment, backStackTag) else add(R.id.frameLayoutContent, fragment) backStackTag?.let { this.addToBackStack(it) } commit() } enableDrawer(isEnabled) } private fun enableDrawer(isEnabled: Boolean) { drawerLayout.setDrawerLockMode(if(isEnabled) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawerToggle?.onDrawerStateChanged(if(isEnabled) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawerToggle?.isDrawerIndicatorEnabled = isEnabled drawerToggle?.syncState() } override fun onOptionsItemSelected(item: MenuItem?): Boolean { if (drawerToggle!!.onOptionsItemSelected(item)) { return true } return super.onOptionsItemSelected(item) } override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onPostCreate(savedInstanceState, persistentState) drawerToggle?.syncState() } override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) drawerToggle?.onConfigurationChanged(newConfig) } } 

MainActivityPresenter

 class MenuActivityPresenter(val menuActivity: MenuActivity){ fun init(){ menuActivity.showFragment(AccountsFragment.newInstance(), isReplace = false) } fun menuItemSelected(menuItem: MenuItem){ val fragment = when(menuItem.itemId){ R.id.nav_accounts_fragment -> { AccountsFragment.newInstance() } R.id.nav_income_fragment -> { IncomeFragment.newInstance() } R.id.nav_settings -> { IncomeFragment.newInstance() } R.id.nav_feedback -> { OutcomeFragment.newInstance() } else -> { IncomeFragment.newInstance() } } menuActivity.showFragment(fragment) } } 

activity_menu.xml

 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- This LinearLayout represents the contents of the screen --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- The ActionBar displayed at the top --> <include layout="@layout/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!-- The main content view where fragments are loaded --> <FrameLayout android:id="@+id/frameLayoutContent" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <!-- The navigation drawer that comes from the left --> <!-- Note that `android:layout_gravity` needs to be set to 'start' --> <android.support.design.widget.NavigationView android:id="@+id/navigationView" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:background="@android:color/white" app:menu="@menu/main_menu" app:headerLayout="@layout/nav_header" /> </android.support.v4.widget.DrawerLayout> 

И место, где у меня есть точка разрыва в моем понимании:

 class AccountsFragment : Fragment() { companion object { fun newInstance() = AccountsFragment() } val Activity.app : App get() = application as App val component by lazy { app.component .plus(MenuActivityModule(activity as MenuActivity)) .plus(AccountsFragmentModule(this)) } override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater?.inflate(R.layout.fragment_accounts, container, false) setHasOptionsMenu(true) component.inject(this) return view } } 

Мое недоразумение в этой заключительной части значения component . Я пришел к тому, что мне нужно plus Subcomponent MenuActivityComponent и указать в качестве переменной конструктора MenuActivity, но я понимаю, что это неправильно, и я не могу создать другой экземпляр, даже если я хочу, чтобы экземпляр был только одним в приложении ,

Вопрос : Где у меня ошибка? В моем коде, в архитектуре, в понимании инъекции зависимостей или во всех трех? Спасибо за помощь!

Я бы разделил концепции в своем уме и работал над ними индивидуально. После того, как вы освоитесь в обеих концепциях, вы можете попробовать их комбинировать.

Например, попробуйте создать простое приложение с несколькими приложениями / фрагментами шаблона проектирования MVP. С MVP вы будете писать двухсторонние интерфейсные контракты между Presenter (объектом, который содержит логику представления и управляет представлением, а также обрабатывает поведение, которое просматривает и пересылает представление), и View (объект вида, обычно являющийся нативным компонентом, таким как Fragment или Activity, который отвечает за отображение представления и обработку пользовательского ввода, например, события касания).

С Dagger2 вы будете изучать шаблон дизайна / дизайн дизайна инъекций зависимости. Вы будете создавать модули, которые объединяются для формирования компонентов, а затем использовать эти компоненты для ввода объектов.

Объединение двух начинается с понимания каждой концепции на ее собственной.

Ознакомьтесь с репозиторием Google Architectural Blueprint для примеров MVP и Dagger2. https://github.com/googlesamples/android-architecture

Также у меня есть два @Scopes: ActivityScope и FragmentScope, так как я понимаю, что это гарантирует существование только одного Компонента на время, необходимое каждому компоненту в приложении

Области кинжала – это не волшебная фея, которая будет управлять временем жизни для вас. Использование областей – это просто вспомогательная помощь, которая помогает вам не смешивать зависимости с разными сроками службы. Вы все равно должны сами управлять объектами компонентов и модулей, передавая правильные параметры своим конструкторам. Графы зависимостей, управляемые разными экземплярами компонентов, независимы и привязаны к объектам @Component. Обратите внимание, что я говорю «связанный» в некотором смысле, что они созданы ими (через конструктор) и, возможно, хранятся внутри них, нет никакой другой магии, работающей за кулисами. Поэтому, если вам нужно поделиться связью зависимостей между частями приложения, вам может потребоваться передать некоторые экземпляры компонентов и модулей.

В случае фрагментов существует жесткая зависимость от сложного времени жизни – Activity. Вы получаете эту зависимость во время onAttach и теряете ее во время onDetach . Поэтому, если вы действительно хотите передать некоторые элементы, зависящие от Activity, в Fragments, вам необходимо передать эти зависящие от деятельности компоненты / модули, так же, как вы будете передавать экземпляр Activity в отсутствие MVP.

Аналогично, если ваша активность получает определенную зависимость через Intent, вам придется десериализовать эту зависимость от нее и создать модуль на основе содержимого Intent …

Сложность жизненных циклов активности и фрагмента усугубляет проблемы, обычно встречающиеся при использовании инъекции кинжала. Основы Android основаны на предположении, что действия и фрагменты получают все свои зависимости в сериализованной форме. Наконец, у хорошо написанного приложения не должно быть слишком много зависимостей между модулями, чтобы начать (посмотрите вверх по «жесткой связи»). Из-за этих причин, если вы не соблюдаете строгую парадигму одиночной активности, использование кинжала в локализованных областях деятельности / инструментария может оказаться нецелесообразным в вашем проекте. Многие люди все еще это делают, даже если это не стоит, но ИМО, это только вопрос личных предпочтений.

ОК. После нескольких недель глубокого изучения. Я сделал основы проекта github, чтобы каждый мог просмотреть его и иметь рабочий пример.

https://github.com/Belka1000867/Dagger2Kotlin