Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to use Dagger 2 in Android

How to use Dagger 2 in Android

Dagger is a complex and powerful library. Because of its flexibility Dagger can be integrated in many different ways in an Android application. This talk aims to describe a pattern on how to use Dagger avoiding common pitfalls and leveraging the main advantages of Dependency Injection (DI).

Marcelo Benites

April 11, 2018
Tweet

More Decks by Marcelo Benites

Other Decks in Programming

Transcript

  1. Dagger 2 • Better performance. • No reflection. • First

    to implement the full stack with generated code. • Easier to debug. • Easy to use Proguard. • Harder to replace dependencies for testing. • Way more complex.
  2. Why Dagger? • Eases DI in Android components (ContentProvider, Activity,

    Service, Application and BroadcastReceiver) • Eases object's lifecycle management (Scopes). • "Eases" the replacement of dependencies for Functional (UI) Testing. • Standardizes object creation (large codebases/big teams).
  3. Why NOT Dagger? • Steep learning curve. • Too many

    features you don't necessarily need. • May cause performance problems (if misused). • Easely leaks into all layers of your application. • Hard to get rid of.
  4. How to use Dagger? • dagger.android module? • @Inject annotated

    constructor or @Provides annotated methods? • How many components? • How do I group my classes in modules? • How do I use scopes? • Multibinding? • Producers? • Subcomponents?
  5. Dagger Android Pattern • Dagger solves - and only solves

    - Android components injection and lifecycle problems. • Always @Produces in modules never @Inject in constructors (only for Android components dependencies). • Application component. • Activity component as Application subcomponent. • Fragment component as Activity subcomponent. • BaseActivity, BaseFragment and BaseApplication manage components lifecycle. • Three scopes ApplicationScope, ActivityScope and FragmentScope. • Modules based on your architecture.
  6. abstract class BaseApplication : Application() { lateinit var component: ApplicationComponent

    override fun onCreate() { super.onCreate() component = DaggerApplicationComponent .builder() .interactorModule(InteractorModule()) .gatewayModule(GatewayModule(this)) .build() } }
  7. @Module class GatewayModule(private val application: Application) { @ApplicationScope @Provides fun

    provideContactsGateway(): ContactsGateway { return ContentResolverContactsGateway(application.contentResolver) } }
  8. class ContactsApplication: BaseApplication() { @Inject lateinit var contactsInteractor: ContactsInteractor override

    fun onCreate() { super.onCreate() component.inject(this) contactsInteractor.setup() } }
  9. Contacts Presentation • ContactsActivity container. • ContactListFragment - display list

    of contacts. • ContactDetailFragment - display contact detail. • MVP architecture. • ContactsListPresenter • ContactDetailPresenter
  10. abstract class BaseActivity : AppCompatActivity() { lateinit var component: ActivityComponent

    private set override fun onCreate(savedInstanceState: Bundle?) { component = (application as BaseApplication).component.plus(NavigatorModule(this)) super.onCreate(savedInstanceState) } }
  11. @Module class NavigatorModule(private val activity: AppCompatActivity) { @ActivityScope @Provides fun

    provideContactsNavigator(): ContactsNavigator { return FragmentManagerContactsNavigator(activity.supportFragmentManager!!, R.id.activity_contacts_container) } }
  12. class ContactsActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_contacts) if (savedInstanceState == null) { supportFragmentManager .beginTransaction() .replace(R.id.activity_contacts_container, ContactListFragment.create()) .commit() } } }
  13. abstract class BaseFragment : Fragment() { lateinit var component: FragmentComponent

    private set override fun onAttach(context: Context?) { component = (activity as BaseActivity).component.plus(PresenterModule(this)) super.onAttach(context) } }
  14. @Module class PresenterModule { @FragmentScope @Provides fun provideContactListPresenter(contactsInteractor: ContactsInteractor, contactsNavigator:

    ContactsNavigator): ContactListPresenter { return ContactListPresenter(contactsInteractor, contactsNavigator) } @FragmentScope @Provides fun provideContactDetailPresenter(contactsInteractor: ContactsInteractor): ContactDetailPresenter { return ContactDetailPresenter(contactsInteractor) } }
  15. class ContactListFragment: BaseFragment(), ContactListView { @Inject lateinit var presenter: ContactListPresenter

    override fun onAttach(context: Context?) { super.onAttach(context) component!!.inject(this) } override fun onResume() { super.onResume() presenter.attachView(this) } override fun onPause() { presenter.detachView() super.onPause() } …….. }
  16. class ContactDetailFragment: BaseFragment(), ContactDetailView { @Inject lateinit var presenter: ContactDetailPresenter

    override fun onAttach(context: Context?) { super.onAttach(context) component!!.inject(this) } override fun onResume() { super.onResume() presenter.attachView(this) } override fun onPause() { presenter.detachView() super.onPause() } …... }
  17. abstract class BaseApplication : Application() { lateinit var component: ApplicationComponent

    private set override fun onCreate() { super.onCreate() component = createComponent() } open fun createComponent(): ApplicationComponent { return DaggerApplicationComponent.builder() .interactorModule(InteractorModule()) .gatewayModule(GatewayModule(this)) .build() } }
  18. class TestComponentApplication: ContactsApplication() { override fun createComponent(): ApplicationComponent { return

    DaggerTestApplicationComponent .builder() .interactorModule(InteractorModule()) .testGatewayModule(TestGatewayModule()) .build() } }
  19. class TestComponentRunner: AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, className: String?,

    context: Context?): Application { return super.newApplication(cl, TestComponentApplication::class.java.name, context) } }
  20. @RunWith(AndroidJUnit4::class) class ContactsActivityTest { @Rule @JvmField val rule: ActivityTestRule<ContactsActivity> =

    ActivityTestRule(ContactsActivity::class.java) @Inject lateinit var contactsGateway: ContactsGateway @Test fun shouldShowContacts() { val testComponentApplication = InstrumentationRegistry.getTargetContext().applicationContext as TestComponentApplication (testComponentApplication.component as TestApplicationComponent).inject(this) (contactsGateway as FakeContactsGateway).fakeContacts = arrayListOf(Contact("Bob Marley", "+19999299299")) // Exercise the UI // Assert result } }
  21. To inject or not to inject? • To Inject ◦

    Interactors ◦ Gateways ◦ Presenters • Not to Inject ◦ View classes (RecyclerView.Adapter, LayoutManager, Views, etc...)