Ich studiere Dagger2 + MVP und tun es auf Kotlin. Und ich habe ein Problem, entweder Dagger2 oder MVP oder dort Kombination zu verstehen.Dolch2 + MVP auf Kotlin
Aufbau einer Anwendung und Idee, wie es funktionieren sollte, sind sehr einfach. Die App besteht aus MenuActivity
mit der linken Navigation und mehreren Fragments
(sagen wir 3), die in der FrameLayout
in activity_menu.xml
geändert werden sollte.
Ich habe mehrere Artikel gelesen und verbringe schon ein paar Tage damit, Dagger2 zu studieren. Dieser Artikel, den ich als Tutorial, mein Beispiel bauen: https://proandroiddev.com/dagger-2-part-ii-custom-scopes-component-dependencies-subcomponents-697c1fa1cfc
In meiner Idee, Dagger Architektur sollte aus drei @Component
s bestehen: (1) AppComponent, (2) MenuActivityComponent und (3) AccountFragmentComponent. Und aus meinem Verständnis und das Bild einer Architektur im Artikel meiner Architektur wie das sein kann: (3) ist abhängig von -> (2) ist abhängig von -> (1)
Jeder @Component
hat ein @Module
: (1) AppModule, (2) MenuActivityModule bzw. (3) AccountFragmentModule. Für den saubereren Weg der MVP-Abhängigkeiten sollten sowohl (2) MenuActivityModule als auch (3) AccountFragmentModule @Provide
Presenter
s von MVP Ideologie @Inject
in MenuActivity
und andere Fragment
s sein, wie 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
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)
}
Auch ich habe zwei @Scope
s: ActivityScope und FragmentScope, so wie ich das verstehen wird die Garantie Vorhandensein von nur einer Komponente für die Zeit, die jede Komponente in der Anwendung benötigt wird.
ActivityScope
@Scope
annotation class ActivityScope
FragmentScope
@Scope
annotation class FragmentScope
In Anwendungsklasse erstelle ich ein Diagramm von @Singleton
Abhängigkeiten.
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)
}
}
In 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>
Und der Ort, wo ich eine Bruchstelle in meinem Verständnis habe:
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
}
}
Mein Missverständnis in diesem letzten Teil im component
Wert. Ich kam zu der Situation, die ich brauche plus
Unterkomponente der MenuActivityComponent und geben als Konstruktor Variable MenuActivity, aber ich verstehe, dass dies falsch ist und ich kann keine andere Instanz erstellen, auch wenn ich möchte, dass die Instanz nur eins in der Anwendung sein soll .
Frage: Wo habe ich einen Fehler? In meinem Code, in der Architektur, im Verständnis der Abhängigkeitsinjektion oder in allen dreien? Danke für Ihre Hilfe!