2017-08-16 39 views
0

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 @ProvidePresenter 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!

Antwort

1

Ich würde die Konzepte in Ihrem Kopf trennen und an ihnen einzeln arbeiten. Sobald Sie in beiden Konzepten fließend/meisterhaft sind, können Sie versuchen, sie zu kombinieren.

Versuchen Sie zum Beispiel, eine einfache multiple Aktivität/Fragment-Anwendung das MVP-Entwurfsmuster zu erstellen. Mit MVP werden Sie Zwei-Wege-Schnittstellenverträge zwischen dem Presenter (einem Objekt, das die Ansichtslogik enthält und die Ansicht steuert sowie dem Verhalten, das von der Ansicht erfasst und weitergeleitet wird) und der Ansicht (ein Ansichtsobjekt, in der Regel eine native Komponente wie Fragment oder Aktivität, die für die Anzeige einer Ansicht und die Verarbeitung von Benutzereingaben wie Berührungsereignissen verantwortlich ist.

Mit Dagger2 lernen Sie das Abhängigkeitsinjektionsmuster/Architekturstil. Sie erstellen Module, die sich zu Komponenten zusammenfügen und diese Komponenten dann zum Einbringen von Objekten verwenden.

Die Kombination der beiden beginnt mit dem Verständnis jedes einzelnen Konzepts.

Im Google Architectural Blueprint-Repository finden Sie Beispiele für MVP und Dagger2. https://github.com/googlesamples/android-architecture

1

Auch habe ich zwei @Scopes: ActivityScope und FragmentScope, so wie ich das verstehen, die Existenz von nur einer Komponente für die Zeit garantieren jede Komponente in der Anwendung benötigen wird

Dolch des Scopes aren Es ist ein magischer Feenstaub, der die Lebenszeiten für dich verwalten wird. Die Verwendung von Gültigkeitsbereichen ist nur eine Validierungshilfe, die Ihnen hilft, Abhängigkeiten nicht mit unterschiedlichen Lebensdauern zu kombinieren. Sie müssen Komponenten- und Modulobjekte selbst verwalten und korrekte Parameter an ihre Konstruktoren übergeben. Die Abhängigkeitsgraphen, die von verschiedenen Komponenteninstanzen verwaltet werden, sind unabhängig und an ihre @Component-Objekte gebunden. Beachten Sie, dass ich in einem gewissen Sinne "gebunden" sage, dass sie von ihnen erzeugt werden (via Konstruktor), und optional in ihnen gespeichert ist, gibt es absolut keine andere Magie, die hinter den Kulissen arbeitet. Wenn Sie also eine Reihe von Abhängigkeiten zwischen Teilen der Anwendung teilen müssen, müssen Sie möglicherweise einige Komponenten- und Modulinstanzen weitergeben.

Im Falle von Fragmenten gibt es eine starke Abhängigkeit mit komplexer Lebensdauer - die Aktivität. Sie erhalten diese Abhängigkeit während onAttach und verlieren sie während onDetach. Wenn Sie also wirklich aktivitätsabhängige Elemente an Fragmente übergeben möchten, müssen Sie diese aktivitätsabhängigen Komponenten/Module weitergeben, so wie Sie die Aktivität-Instanz in Abwesenheit von MVP weitergeben würden.

Ebenso, wenn Ihre Aktivität eine gewisse Abhängigkeit über Intent erhält, werden Sie diese Abhängigkeit davon deserialisieren müssen, und ein Modul erstellen, die auf Vorsatz Inhalt ...

Die Komplexität der Aktivität und Fragment Lifecycles verschärft Probleme, die gemeinhin erfüllt, wenn Dolch-Injektion verwendet wird. Android Framework basiert auf der Annahme, dass Aktivitäten und Fragmente alle ihre Abhängigkeiten in serialisierter Form erhalten. Schließlich sollte eine gut geschriebene Anwendung nicht zu viele Abhängigkeiten zwischen den Modulen haben (siehe "enge Kopplung"). Aus diesen Gründen ist die Verwendung von Dagger für lokalisierte Activity/Fagment-Bereiche in Ihrem Projekt möglicherweise überhaupt nicht sinnvoll, wenn Sie nicht dem strikten Einzelaktivitätsparadigma folgen. Viele tun es immer noch, auch wenn es sich nicht lohnt, aber IMO, das ist nur eine Frage der persönlichen Vorlieben.