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

Android Architecture with MVP and Clean Code

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.

Gerard

January 19, 2018
Tweet

More Decks by Gerard

Other Decks in Programming

Transcript

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

    View full-size slide

  2. PLANNING DOMYOS
    SAMPLE APP

    View full-size slide

  3. SAMPLE APP
    LIBRAIRIES USED
    ▸ Kotlin
    ▸ Retrofit + OkHttp
    ▸ Picasso
    ▸ Timber
    ▸ Dagger
    ▸ RxJava 2
    ▸ Room

    View full-size slide

  4. WHAT IS MVP AND CLEAN CODE?
    MVP

    View full-size slide

  5. interface ClubContracts {
    interface View {
    fun onClubsReceived(clubs: List)
    fun onError()
    }
    interface Presenter : KContracts.Presenter {
    }
    }

    View full-size slide

  6. class ClubPresenter(
    private val view: ClubContracts.View
    ) : ClubContracts.Presenter, KPresenter() {
    private fun load() {
    // ...
    view.onClubsReceived(it)
    }
    init {
    load()
    }
    }

    View full-size slide

  7. class ClubFragment : KFragment(), ClubContracts.View {
    override fun layout() = R.layout.fragment_clubs
    override fun inject(appComponent: AppComponent) {
    AndroidSupportInjection.inject(this)
    }
    override fun onClubsReceived(clubs: List) {
    // ...
    }
    override fun onError() {
    // ...
    }
    companion object {
    val TAG = "ClubFragment"
    fun newInstance(): Fragment {
    return ClubFragment()
    }
    }
    }

    View full-size slide

  8. abstract class KFragment : 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)
    }

    View full-size slide

  9. WHAT IS MVP AND CLEAN CODE?
    CLEAN CODE

    View full-size slide

  10. WHAT IS MVP AND CLEAN CODE?
    CLEAN CODE

    View full-size slide

  11. 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
    )

    View full-size slide

  12. 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
    )

    View full-size slide

  13. class ClubModelUiMapper : Mapper {
    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()
    )
    }

    View full-size slide

  14. class GetClubUseCase(private val repository: ClubRepository) {
    fun execute(): Single> {
    return repository.clubs()
    .toObservable()
    .flatMapIterable { it }
    .filter { it.id != 252 }
    .toList()
    }
    }

    View full-size slide

  15. class ClubRepositoryImpl(
    private val databaseSource: ClubDatabaseSource,
    private val serviceSource: ClubServiceSource
    ) : ClubRepository {
    override fun clubs(): Single> {
    return databaseSource.clubs()
    .toObservable()
    .filter { !it.isEmpty() }
    .switchIfEmpty(serviceSource.clubs()
    .doOnSuccess { databaseSource.saveClubs(it) }
    .toObservable())
    .singleOrError()
    }
    }

    View full-size slide

  16. class ClubPresenter(
    private val view: ClubContracts.View,
    private val getClubUseCase: GetClubUseCase,
    private val clubMapper: Mapper,
    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()
    }
    }

    View full-size slide

  17. TESTS
    LIBRAIRIES USED
    ▸ JUnit
    ▸ AssertJ + AssertK
    ▸ Mockito + Mockito Kotlin
    ▸ Android Support Test

    View full-size slide

  18. class ClubUseCaseTest {
    private val repository: ClubRepository = mock()
    private lateinit var clubUseCase: ClubUseCase
    @Before
    fun setUp() {
    clubUseCase = ClubUseCase(repository)
    }
    // tests...
    }

    View full-size slide

  19. @Test
    fun shouldLoadClubs() {
    val clubExpected = Club(253, "Domyos Lille", "", "Professor Langevin",
    "Lille", "[email protected]", "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", "[email protected]", "0606060606")).toList())
    val clubs = clubUseCase.execute().test().assertNoErrors().values().first()
    verify(repository).clubs()
    assert(clubs).isEmpty()
    }

    View full-size slide

  20. class ClubPresenterTest {
    private val view: ClubContracts.View = mock()
    private val repository: ClubRepository = mock()
    private val clubMapper: Mapper = mock()
    private val schedulers: KSchedulers = SchedulersTest()
    private lateinit var clubUseCase: ClubUseCase
    @Before
    fun setUp() {
    clubUseCase = ClubUseCase(repository)
    }
    // tests...
    }

    View full-size slide

  21. @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()
    }

    View full-size slide

  22. itcommunities.slack.com

    View full-size slide

  23. android-dev-france.slack.com

    View full-size slide

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

    View full-size slide