Save 37% off PRO during our Black Friday Sale! »

Android Architecture with MVP and Clean Code

B6bf611a9e4f70a8455cb175339e0587?s=47 Gerard
January 19, 2018

Android Architecture with MVP and Clean Code

MVP or MVVM architectures are adopted by Android developers and allows a better development, test and maintenance of their applications. This talk presents a MVP architecture with a Clean Code architecture which can fit into some Android application.

B6bf611a9e4f70a8455cb175339e0587?s=128

Gerard

January 19, 2018
Tweet

Transcript

  1. ANDROID ARCHITECTURE WITH MVP AND CLEAN CODE Gérard Paligot

  2. INSTORE

  3. None
  4. None
  5. None
  6. None
  7. PLANNING DOMYOS SAMPLE APP

  8. None
  9. SAMPLE APP LIBRAIRIES USED ▸ Kotlin ▸ Retrofit + OkHttp

    ▸ Picasso ▸ Timber ▸ Dagger ▸ RxJava 2 ▸ Room
  10. WHAT IS MVP AND CLEAN CODE? MVP

  11. interface ClubContracts { interface View { fun onClubsReceived(clubs: List<ClubModelUi>) fun

    onError() } interface Presenter : KContracts.Presenter { } }
  12. class ClubPresenter( private val view: ClubContracts.View ) : ClubContracts.Presenter, KPresenter()

    { private fun load() { // ... view.onClubsReceived(it) } init { load() } }
  13. class ClubFragment : KFragment<ClubContracts.Presenter>(), ClubContracts.View { override fun layout() =

    R.layout.fragment_clubs override fun inject(appComponent: AppComponent) { AndroidSupportInjection.inject(this) } override fun onClubsReceived(clubs: List<ClubModelUi>) { // ... } override fun onError() { // ... } companion object { val TAG = "ClubFragment" fun newInstance(): Fragment { return ClubFragment() } } }
  14. abstract class KFragment<T : KContracts.Presenter> : Fragment() { @Inject lateinit

    var presenter: T override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater!!.inflate(layout(), container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) inject(context.component) } @LayoutRes abstract fun layout(): Int abstract fun inject(appComponent: AppComponent) }
  15. WHAT IS MVP AND CLEAN CODE? CLEAN CODE

  16. WHAT IS MVP AND CLEAN CODE? CLEAN CODE

  17. MODELS

  18. data class ClubServiceModel( @SerializedName("id") val id: Int, @SerializedName("name") val name:

    String, @SerializedName("picture") val picture: String, @SerializedName("disable") val disable: Boolean, @SerializedName("address") val address: String, @SerializedName("postcode") val postcode: String, @SerializedName("city") val city: String, @SerializedName("mail") val mail: String, @SerializedName("phone") val phone: String? ) @Entity(tableName = "clubs") data class ClubDaoModel( @PrimaryKey val id: Int, val name: String, val picture: String, val address: String, val city: String, val mail: String, val phone: String?, @ColumnInfo(name = "created_at", index = true) val createdAt: Long )
  19. data class Club( val id: Int, val name: String, val

    picture: String, val address: String, val city: String, val mail: String, val phone: String? ) open class ClubModelUi( val id: Int, val name: String, val pictureLink: String, val phoneLink: Uri, val mapLink: Uri, val mailLink: Uri )
  20. class ClubModelUiMapper : Mapper<Club, ClubModelUi> { override fun to(from: Club):

    ClubModelUi = ClubModelUi( id = from.id, name = from.name, pictureLink = from.picture, phoneLink = if (from.phone != null) "tel:${from.phone}".toUri() else "".toUri(), mapLink = "https://www.google.com/maps/${from.address} ${from.city}".toUri(), mailLink = "mailto:${from.mail}".toUri() ) }
  21. USECASE

  22. class GetClubUseCase(private val repository: ClubRepository) { fun execute(): Single<List<Club>> {

    return repository.clubs() .toObservable() .flatMapIterable { it } .filter { it.id != 252 } .toList() } }
  23. class ClubRepositoryImpl( private val databaseSource: ClubDatabaseSource, private val serviceSource: ClubServiceSource

    ) : ClubRepository { override fun clubs(): Single<List<Club>> { return databaseSource.clubs() .toObservable() .filter { !it.isEmpty() } .switchIfEmpty(serviceSource.clubs() .doOnSuccess { databaseSource.saveClubs(it) } .toObservable()) .singleOrError() } }
  24. class ClubPresenter( private val view: ClubContracts.View, private val getClubUseCase: GetClubUseCase,

    private val clubMapper: Mapper<Club, ClubModelUi>, private val schedulers: KSchedulers) : ClubContracts.Presenter, KPresenter() { private fun load() { subscription.add(getClubUseCase.execute() .toObservable() .flatMapIterable { it } .map { clubMapper.to(it) } .toList() .subscribeOn(schedulers.newThread) .observeOn(schedulers.main) .subscribe({ view.onClubsReceived(it) }, { Timber.e(it) view.onError() }) ) } init { load() } }
  25. TESTS

  26. TESTS LIBRAIRIES USED ▸ JUnit ▸ AssertJ + AssertK ▸

    Mockito + Mockito Kotlin ▸ Android Support Test
  27. class ClubUseCaseTest { private val repository: ClubRepository = mock() private

    lateinit var clubUseCase: ClubUseCase @Before fun setUp() { clubUseCase = ClubUseCase(repository) } // tests... }
  28. @Test fun shouldLoadClubs() { val clubExpected = Club(253, "Domyos Lille",

    "", "Professor Langevin", "Lille", "contact@domyos.fr", "0606060606") whenever(repository.clubs()).thenReturn(Observable.fromArray(clubExpected).toList()) val clubs = clubUseCase.execute().test().assertNoErrors().values().first() verify(repository).clubs() assert(clubs).hasSize(1) assert(clubs).containsExactly(clubExpected) } @Test fun shouldNotLoadClub252() { whenever(repository.clubs()).thenReturn(Observable.fromArray( Club(252, "Domyos Lille", "", "Professor Langevin", "Lille", "contact@domyos.fr", "0606060606")).toList()) val clubs = clubUseCase.execute().test().assertNoErrors().values().first() verify(repository).clubs() assert(clubs).isEmpty() }
  29. class ClubPresenterTest { private val view: ClubContracts.View = mock() private

    val repository: ClubRepository = mock() private val clubMapper: Mapper<Club, ClubModelUi> = mock() private val schedulers: KSchedulers = SchedulersTest() private lateinit var clubUseCase: ClubUseCase @Before fun setUp() { clubUseCase = ClubUseCase(repository) } // tests... }
  30. @Test fun shouldNotifyViewWhenUseCaseGiveUsListOfClubs() { val clubs = arrayListOf(Club(253, "Domyos Club",

    "", "", "", "", "")) val clubExpected: ClubModelUi = mock() whenever(repository.clubs()).thenReturn(Single.just(clubs)) whenever(clubMapper.to(clubs[0])).thenReturn(clubExpected) ClubPresenter(view, clubUseCase, clubMapper, schedulers) verify(view).onClubsReceived(arrayListOf(clubExpected)) } @Test fun shouldNotifyViewWhenAnErrorOccurred() { whenever(repository.clubs()).thenReturn(Single.error(Throwable())) ClubPresenter(view, clubUseCase, clubMapper, schedulers) verify(view).onError() }
  31. DEMO

  32. itcommunities.slack.com

  33. android-dev-france.slack.com

  34. ANDROID ARCHITECTURE WITH MVP AND CLEAN CODE twitter.com/ github.com/ GerardPaligot