2017-08-16 67 views
0

我正在學習Dagger2 + MVP並在Kotlin上做。而且我在理解Dagger2或MVP或那裏的組合方面存在問題。Kotlin上的Dagger2 + MVP

構建一個應用程序和想法應該如何工作非常簡單。該應用程序由MenuActivity與左側導航和幾個Fragments(比方說3)應改變在FrameLayoutactivity_menu.xml

我已經閱讀了幾篇文章,花了幾天的時間學習Dagger2。 (1)AppComponent,(2)MenuActivityComponent和(3)AccountFragmentComponent。在我的想法中,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都應該是@ProvidePresenter,而在MenuActivity和其他Fragments,比如AccountFragment中應該是@Inject

的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) 
} 

而且我有兩個@Scope S:ActivityScope和FragmentScope,使我明白,這將保證在應用程序中需要每個組件的時間僅存在一個Component。

ActivityScope

@Scope 
annotation class ActivityScope 

FragmentScope

@Scope 
annotation class FragmentScope 

在應用I類創建的@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 MenuActivityComponent的子組件,並給出一個構造函數變量MenuActivity,但我明白這是錯誤的,即使我希望實例應該只有一個在應用程序中,我也不能創建另一個實例。

問題:我哪裏有錯?在我的代碼中,在架構中,在理解依賴注入方面還是在所有三方面?感謝您的幫助!

回答

1

我會分開你的頭腦中的概念,並單獨工作。一旦你在這兩個概念中變得流利/優秀,你可以嘗試將它們結合起來。

例如,嘗試構建一個簡單的多活動/片段應用程序MVP設計模式。使用MVP,您將在Presenter(一個包含視圖邏輯並控制視圖的對象,以及處理視圖收集和轉發的行爲的對象)之間編寫雙向接口契約,以及View(視圖對象,通常是像Fragment或Activity這樣的本地組件,負責顯示視圖並處理用戶輸入,如觸摸事件)。

使用Dagger2,您將學習依賴注入設計模式/架構風格。您將構建組合的模塊,以形成組件,然後使用這些組件來注入對象。

將兩者結合起來就是從理解每個概念開始。

查看Google Architectural Blueprint存儲庫以獲取MVP和Dagger2的示例。 https://github.com/googlesamples/android-architecture

1

而且我有兩個@Scopes:ActivityScope和FragmentScope,所以按照我的理解,這將保證只有一個組件的存在,每一個部件需要在應用

匕首的範圍AREN時間一些神奇的仙塵,會爲你管理生命。範圍的使用僅僅是驗證幫助,它可以幫助您不依賴不同的生命週期。您仍然必須自己管理組件和模塊對象,並將正確的參數傳遞給它們的構造函數。由不同組件實例管理的依賴關係圖是獨立的,並綁定到它們的@Component對象。請注意,我在某種意義上說「綁定」,它們是由它們(通過構造函數)創建的,並且可選地存儲在它們內部,所以在幕後工作絕對沒有其他魔法。因此,如果您需要在應用程序的各個部分之間共享一堆依賴關係,則可能需要傳遞一些組件和模塊實例。

在碎片的情況下,對複雜的生命週期有很強的依賴性 - 活動。您在onAttach期間收到該依賴項,並在onDetach期間丟失該依賴項。所以如果你真的想將一些Activity依賴的東西傳遞給Fragments,你必須傳遞那些依賴於Activity的組件/模塊,就像你在沒有MVP的情況下傳遞Activity實例一樣。

同樣的,如果你的活動通過意向接收一些依賴,你將不得不反序列化的依賴性和它,並創建一個模塊的基礎上,意向內容...

活動的複雜性和片段的生命週期加重的問題,一般在使用匕首注射時會見。 Android框架圍繞假設建立,活動和片段以序列化的形式接收所有的依賴關係。最後,一個寫得很好的應用程序不應該在模塊之間有太多依賴關係(查找「緊耦合」)。由於這些原因,如果您不遵循嚴格的單一活動範例,則在本地化的活動/ Fagment範圍中使用Dagger在您的項目中可能根本不值得。許多人仍然這樣做,即使這不值得,但海事組織,這只是個人喜好的問題。