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

Clean Architecture in Android

Clean Architecture in Android

Droid Knights 2017에서 발표한 [Clean Architecture in Android] 발표 슬라이드입니다. (https://droidknights.github.io/2017/)

Sunghyun Hwang

March 25, 2017
Tweet

More Decks by Sunghyun Hwang

Other Decks in Programming

Transcript

  1. Clean Architecture in
    Android
    const val presenter = "sunghyunzz"

    View full-size slide

  2. • 2015֙ 12ਘ ୹द

    View full-size slide

  3. • 2015֙ 12ਘ ୹द
    • ف ߣ੄ ؀ӏݽ সؘ੉౟

    View full-size slide

  4. • 2015֙ 12ਘ ୹द
    • ف ߣ੄ ؀ӏݽ সؘ੉౟
    • Ր੐হח ࢎਊ੗ ੋఠ࠭ܳ ాೠ ѐࢶ

    View full-size slide

  5. • 2015֙ 12ਘ ୹द
    • ف ߣ੄ ؀ӏݽ সؘ੉౟
    • Ր੐হח ࢎਊ੗ ੋఠ࠭ܳ ాೠ ѐࢶ
    • ҅ࣘ ѐࢶغҊ ߸ചೞח ઁಿ

    View full-size slide

  6. • 2015֙ 12ਘ ୹द
    • ف ߣ੄ ؀ӏݽ সؘ੉౟
    • Ր੐হח ࢎਊ੗ ੋఠ࠭ܳ ాೠ ѐࢶ
    • ҅ࣘ ѐࢶغҊ ߸ചೞח ઁಿ
    • ௏٘о ҅ࣘ ߄Ո׮

    View full-size slide

  7. “߸ച ৻ী ৔ਗೠ Ѫ਷ হ׮.”
    - Heraclitus

    View full-size slide

  8. “ೖೡ ࣻ হਵݶ ૌѹۄ.”
    - ־ҵо

    View full-size slide

  9. import kotlinx.android.synthetic.main.activity_transactions_temp.*

    View full-size slide

  10. ߸ചী ੜ ؀਽ೡ ࣻ ੓ח ௏٘ח
    যڃ ݽणੌө?

    View full-size slide

  11. ߸ചী ੜ ؀਽ೡ ࣻ ੓׮.
    = ߸ചী ٮܲ ௏٘੄ ߸҃੉ ੸׮.

    View full-size slide

  12. ߸ചী ੜ ؀਽ೡ ࣻ ੓׮.
    = ߸ചী ٮܲ ௏٘੄ ߸҃੉ ੸׮.

    View full-size slide

  13. ߸ചী ੜ ؀਽ೡ ࣻ ੓׮.
    = ߸ചী ٮܲ ௏٘੄ ߸҃੉ ੸׮.
    = ௏٘о ੜ ܻ࠙غয੓׮.

    View full-size slide

  14. ߸ചী ੜ ؀਽ೡ ࣻ ੓׮.
    = ߸ചী ٮܲ ௏٘੄ ߸҃੉ ੸׮.
    = ௏٘о ੜ ܻ࠙غয੓׮.
    = ௏٘о ࠄ૕ী ݏѱ ࢸ҅غয੓׮.

    View full-size slide

  15. ࠄ૕ = {
    بݫੋ੄ ࠄ૕,
    ѐߊ ҳઑ ࢚੄ ࠄ૕,
    উ٘۽੉٘ ೒ۖಬ੄ ࠄ૕,
    ...
    }

    View full-size slide

  16. ࠄ૕ = {
    بݫੋ੄ ࠄ૕,
    ѐߊ ҳઑ ࢚੄ ࠄ૕,
    উ٘۽੉٘ ೒ۖಬ੄ ࠄ૕,
    ...
    }

    View full-size slide

  17. ࠄ૕ = {
    بݫੋ੄ ࠄ૕,
    ѐߊ ҳઑ ࢚੄ ࠄ૕,
    উ٘۽੉٘ ೒ۖಬ੄ ࠄ૕,
    ...
    }

    View full-size slide

  18. ࠄ૕ = {
    بݫੋ੄ ࠄ૕,
    ѐߊ ҳઑ ࢚੄ ࠄ૕,
    উ٘۽੉٘ ҳઑ ࢚੄ ࠄ૕,
    ...
    }

    View full-size slide

  19. ࠄ૕ = {
    بݫੋ੄ ࠄ૕,
    ѐߊ ҳઑ ࢚੄ ࠄ૕,
    উ٘۽੉٘ ҳઑ ࢚੄ ࠄ૕,
    ...
    }

    View full-size slide

  20. https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

    View full-size slide

  21. 1. Independent of Frameworks

    View full-size slide

  22. 1. Independent of Frameworks
    2. Testable

    View full-size slide

  23. 1. Independent of Frameworks
    2. Testable
    3. Independent of UI

    View full-size slide

  24. 1. Independent of Frameworks
    2. Testable
    3. Independent of UI
    4. Independent of Database

    View full-size slide

  25. 1. Independent of Frameworks
    2. Testable
    3. Independent of UI
    4. Independent of Database
    5. Independent of External Configuration

    View full-size slide

  26. https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way

    View full-size slide

  27. Presentation

    View full-size slide

  28. Presentation
    Data

    View full-size slide

  29. Presentation
    Data
    Domain

    View full-size slide

  30. Presentation
    Data
    Domain
    Entity

    View full-size slide

  31. Presentation
    Data
    Domain
    Entity

    View full-size slide

  32. Entity
    • ࣽࣻೠ Java(Kotlin) ݽٕ

    View full-size slide

  33. Entity
    • ࣽࣻೠ Java(Kotlin) ݽٕ
    • Android৬੄ ੄ઓࢿ੉ হ਺

    View full-size slide

  34. Entity
    • ࣽࣻೠ Java(Kotlin) ݽٕ
    • Android৬੄ ੄ઓࢿ੉ হ਺
    • بݫੋ(࠺ૉפझ ۽૒)ীࢲ ౵ࢤغח ѐ֛ਸ ಴അ

    View full-size slide

  35. Entity
    • ࣽࣻೠ Java(Kotlin) ݽٕ
    • Android৬੄ ੄ઓࢿ੉ হ਺
    • بݫੋ(࠺ૉפझ ۽૒)ীࢲ ౵ࢤغח ѐ֛ਸ ಴അ
    • (э਷ ࢲ࠺झ) Android - iOS - ࢲߡ ݽف زੌೠ ഋక

    View full-size slide

  36. class TransactionCategory(

    val id: String,

    val name: String,

    val transactionType: TransactionType,

    val priority: Int
    ) : Entity

    View full-size slide

  37. Domain
    • ࣽࣻೠ Java(Kotlin) ݽٕ

    View full-size slide

  38. Domain
    • ࣽࣻೠ Java(Kotlin) ݽٕ
    • Use Case

    View full-size slide

  39. Domain
    • ࣽࣻೠ Java(Kotlin) ݽٕ
    • Use Case
    • Repository Interface ੿੄

    View full-size slide

  40. class GetBanks(val repository: BanksRepository) : UseCase>() {

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View full-size slide

  41. class GetBanks(val repository: BanksRepository) : UseCase>() {

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View full-size slide

  42. class GetBanks(val repository: BanksRepository) : UseCase>() {

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View full-size slide

  43. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View full-size slide

  44. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View full-size slide

  45. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View full-size slide

  46. Data
    • Repository੄ पઁ ҳഅ

    View full-size slide

  47. Data
    • Repository੄ पઁ ҳഅ
    • Data Source ੄ઓࢿ

    View full-size slide

  48. Data
    • Repository੄ पઁ ҳഅ
    • Data Source ੄ઓࢿ
    • Android ੄ઓࢿ

    View full-size slide

  49. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

    where(BankModel::class.java).findAll().map { 

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View full-size slide

  50. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

    where(BankModel::class.java).findAll().map { 

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View full-size slide

  51. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

    where(BankModel::class.java).findAll().map { 

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View full-size slide

  52. open class Bank : RealmObject() {

    @PrimaryKey open lateinit var id: String

    open lateinit var name: String

    open lateinit var imageUrl: String

    }

    View full-size slide

  53. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

    where(BankModel::class.java).findAll().map { 

    BankEntityMapper.fromRealmObject(it)

    }

    } 

    }

    }

    }

    View full-size slide

  54. object BankEntityMapper : EntityMapper {

    override fun fromRealmObject(obj: BankModel): Bank {

    return Bank(

    obj.id,

    obj.name,

    obj.imageUrl

    )

    }


    override fun toRealmObject(entity: Bank): BankModel {

    return BankModel().apply {

    id = entity.id

    name = entity.name

    imageUrl = entity.imageUrl

    }

    }

    }

    View full-size slide

  55. object BankEntityMapper : EntityMapper {

    override fun fromRealmObject(obj: BankModel): Bank {

    return Bank(

    obj.id,

    obj.name,

    obj.imageUrl

    )

    }


    override fun toRealmObject(entity: Bank): BankModel {

    return BankModel().apply {

    id = entity.id

    name = entity.name

    imageUrl = entity.imageUrl

    }

    }

    }

    View full-size slide

  56. class BanksNetworkRepository(private val context: Context) : BanksRepository {

    override fun getAll(): Observable> {

    return context.retrofit.banksApi.getSupportedBanks()

    .map(GetSupportedBanksResponse::banks)

    .map { it.map { BankEntityMapper.fromNetworkResponseModel(it) } }

    }

    }

    View full-size slide

  57. data class Bank(

    @SerializedName("id") var id: String? = null,

    @SerializedName("name") var name: String? = null,

    @SerializedName("image_url") var imageUrl: String? = null

    ) : Response

    View full-size slide

  58. Presentation
    • UI ۨ߰੄ ୊ܻ

    View full-size slide

  59. Presentation
    • UI ۨ߰੄ ୊ܻ
    • Android ੄ઓࢿ

    View full-size slide

  60. Presentation
    • UI ۨ߰੄ ୊ܻ
    • Android ੄ઓࢿ
    • View৬ Presenter

    View full-size slide

  61. class OrganizationFragment : BaseFragment(), OrganizationView {

    override val presenter: OrganizationPresenter by lazy {

    OrganizationPresenter(this, act)

    }


    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, saved: Bundle?): View? {

    return inflater!!.inflate(R.layout.fragment_signup_organization, container, false)

    }


    override fun onViewCreated(view: View?, saved: Bundle?) {

    super.onViewCreated(view, saved)

    presenter.onViewCreated()

    nextBtn.onClick { presenter.onNextButtonClicked() }

    }

    }

    View full-size slide

  62. class OrganizationFragment : BaseFragment(), OrganizationView {

    override val presenter: OrganizationPresenter by lazy {

    OrganizationPresenter(this, act)

    }


    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, saved: Bundle?): View? {

    return inflater!!.inflate(R.layout.fragment_signup_organization, container, false)

    }


    override fun onViewCreated(view: View?, saved: Bundle?) {

    super.onViewCreated(view, saved)

    presenter.onViewCreated()

    nextBtn.onClick { presenter.onNextButtonClicked() }

    }

    }

    View full-size slide

  63. class OrganizationFragment : BaseFragment(), OrganizationView {

    override val presenter: OrganizationPresenter by lazy {

    OrganizationPresenter(this, act)

    }


    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, saved: Bundle?): View? {

    return inflater!!.inflate(R.layout.fragment_signup_organization, container, false)

    }


    override fun onViewCreated(view: View?, saved: Bundle?) {

    super.onViewCreated(view, saved)

    presenter.onViewCreated()

    nextBtn.onClick { presenter.onNextButtonClicked() }

    }

    }

    View full-size slide

  64. class OrganizationFragment : BaseFragment(), OrganizationView {

    override val presenter: OrganizationPresenter by lazy {

    OrganizationPresenter(this, act)

    }


    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, saved: Bundle?): View? {

    return inflater!!.inflate(R.layout.fragment_signup_organization, container, false)

    }


    override fun onViewCreated(view: View?, saved: Bundle?) {

    super.onViewCreated(view, saved)

    presenter.onViewCreated()

    nextBtn.onClick { presenter.onNextButtonClicked() }

    }

    }

    View full-size slide

  65. class OrganizationFragment : BaseFragment(), OrganizationView {

    override val presenter: OrganizationPresenter by lazy {

    OrganizationPresenter(this, act)

    }


    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, saved: Bundle?): View? {

    return inflater!!.inflate(R.layout.fragment_signup_organization, container, false)

    }


    override fun onViewCreated(view: View?, saved: Bundle?) {

    super.onViewCreated(view, saved)

    presenter.onViewCreated()

    nextBtn.onClick { presenter.onNextButtonClicked() }

    }

    }

    View full-size slide

  66. class OrganizationPresenter(

    view: OrganizationView,

    context: Context,

    private val getBanks: GetBanks = GetBanks(BanksNetworkRepository(context))

    ) : Presenter(view) {
    override fun onDestroy() {

    getBanks.unsubscribe()

    }

    fun onViewCreated() {

    getBanks.execute(object : DefaultSubscriber>() {

    override fun onNext(t: List) {

    view.showBanks(t)

    }

    })

    }


    fun onNextButtonClicked() {

    with(view.getSelectedOrganizations()) {

    if (isNotEmpty()) {

    view.moveToAddCertificatePage(this)

    }

    }

    }

    }

    View full-size slide

  67. class OrganizationPresenter(

    view: OrganizationView,

    context: Context,

    private val getBanks: GetBanks = GetBanks(BanksNetworkRepository(context))

    ) : Presenter(view) {
    override fun onDestroy() {

    getBanks.unsubscribe()

    }

    fun onViewCreated() {

    getBanks.execute(object : DefaultSubscriber>() {

    override fun onNext(t: List) {

    view.showBanks(t)

    }

    })

    }


    fun onNextButtonClicked() {

    with(view.getSelectedOrganizations()) {

    if (isNotEmpty()) {

    view.moveToAddCertificatePage(this)

    }

    }

    }

    }

    View full-size slide

  68. class OrganizationPresenter(

    view: OrganizationView,

    context: Context,

    private val getBanks: GetBanks = GetBanks(BanksNetworkRepository(context))

    ) : Presenter(view) {
    override fun onDestroy() {

    getBanks.unsubscribe()

    }

    fun onViewCreated() {

    getBanks.execute(object : DefaultSubscriber>() {

    override fun onNext(t: List) {

    view.showBanks(t)

    }

    })

    }


    fun onNextButtonClicked() {

    with(view.getSelectedOrganizations()) {

    if (isNotEmpty()) {

    view.moveToAddCertificatePage(this)

    }

    }

    }

    }

    View full-size slide

  69. class OrganizationPresenter(

    view: OrganizationView,

    context: Context,

    private val getBanks: GetBanks = GetBanks(BanksNetworkRepository(context))

    ) : Presenter(view) {
    override fun onDestroy() {

    getBanks.unsubscribe()

    }

    fun onViewCreated() {

    getBanks.execute(object : DefaultSubscriber>() {

    override fun onNext(t: List) {

    view.showBanks(t)

    }

    })

    }


    fun onNextButtonClicked() {

    with(view.getSelectedOrganizations()) {

    if (isNotEmpty()) {

    view.moveToAddCertificatePage(this)

    }

    }

    }

    }

    View full-size slide

  70. class OrganizationPresenter(

    view: OrganizationView,

    context: Context,

    private val getBanks: GetBanks = GetBanks(BanksNetworkRepository(context))

    ) : Presenter(view) {
    override fun onDestroy() {

    getBanks.unsubscribe()

    }

    fun onViewCreated() {

    getBanks.execute(object : DefaultSubscriber>() {

    override fun onNext(t: List) {

    view.showBanks(t)

    }

    })

    }


    fun onNextButtonClicked() {

    with(view.getSelectedOrganizations()) {

    if (isNotEmpty()) {

    view.moveToAddCertificatePage(this)

    }

    }

    }

    }

    View full-size slide

  71. data class Amount(

    var amount: Long,

    var currency: Currency

    ) : Parcelable {


    companion object {

    @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator {

    override fun createFromParcel(source: Parcel): Amount = Amount(source)

    override fun newArray(size: Int): Array = arrayOfNulls(size)

    }

    }


    constructor(valueObject: AmountValueObject) : this(

    valueObject.amount,

    Currency.from(valueObject.currency)

    )


    constructor(source: Parcel) : this(

    source.readLong(),

    Currency.from(source.readString())

    )


    override fun describeContents() = 0


    override fun writeToParcel(dest: Parcel?, flags: Int) {

    dest?.writeLong(amount)

    dest?.writeString(currency.code)

    }

    }

    View full-size slide

  72. ۨ੉פझ౟ীࢲח
    • જ਷ ௏٘੄ ҳઑо જ਷ ઁಿਵ۽ ੉য૓׮Ҋ ޺ӝী ੉۞ೠ
    ইఃఫ୊ܳ ੸ਊೞ৓णפ׮.

    View full-size slide

  73. ۨ੉פझ౟ীࢲח
    • જ਷ ௏٘੄ ҳઑо જ਷ ઁಿਵ۽ ੉য૓׮Ҋ ޺ӝী ੉۞ೠ
    ইఃఫ୊ܳ ੸ਊೞ৓णפ׮.
    • ইఃఫ୊ بੑ੄ ૓ੑ ੢߷ࠁ׮ ߸ചী ੸਽ೡ ࣻ ੓਺ীࢲ
    য়ח ਬোೣ੉ ؊ ч૑׮Ҋ ࢤпೞৈ ੉۞ೠ ইఃఫ୊ܳ ੸ਊ
    ೞ৓णפ׮.

    View full-size slide

  74. ߛ௼࢟۞٘ܳ ݅٘ח ۨ੉פझ౟ীࢲ ੷൞
    ৬ ೣԋೡ উ٘۽੉٘ ূ૑פয ٜ࠙ਸ ଺
    Ҋ ੓णפ׮.
    Kotlin, RxJava, Retrofit, Glide, Realm

    View full-size slide

  75. Questions
    github: sunghyunzz
    email: [email protected]

    View full-size slide