$30 off During Our Annual Pro Sale. View Details »

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 Slide

  2. View Slide

  3. View Slide

  4. • 2015֙ 12ਘ ୹द

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  24. 1. Independent of Frameworks

    View Slide

  25. 1. Independent of Frameworks
    2. Testable

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 4 Layers

    View Slide

  31. Presentation

    View Slide

  32. Presentation
    Data

    View Slide

  33. Presentation
    Data
    Domain

    View Slide

  34. Presentation
    Data
    Domain
    Entity

    View Slide

  35. Presentation
    Data
    Domain
    Entity

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. class TransactionCategory(

    val id: String,

    val name: String,

    val transactionType: TransactionType,

    val priority: Int
    ) : Entity

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View Slide

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

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View Slide

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

    override fun buildUseCaseObservable(): Observable> {

    return repository.getAll()

    }

    }

    View Slide

  47. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View Slide

  48. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View Slide

  49. interface BanksRepository : Repository {

    fun getAll(): Observable>

    fun update(bank: Bank): Completable

    }

    View Slide

  50. Data
    • Repository੄ पઁ ҳഅ

    View Slide

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

    View Slide

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

    View Slide

  53. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

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

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View Slide

  54. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

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

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View Slide

  55. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

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

    BankEntityMapper.fromRealmObject(it) 

    }

    } 

    }

    }

    }

    View Slide

  56. open class Bank : RealmObject() {

    @PrimaryKey open lateinit var id: String

    open lateinit var name: String

    open lateinit var imageUrl: String

    }

    View Slide

  57. override fun getAll(): Observable> {

    return Observable.fromCallable {

    with(Realm.getDefaultInstance()) {

    use {

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

    BankEntityMapper.fromRealmObject(it)

    }

    } 

    }

    }

    }

    View Slide

  58. 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 Slide

  59. 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 Slide

  60. 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 Slide

  61. data class Bank(

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

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

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

    ) : Response

    View Slide

  62. Presentation
    • UI ۨ߰੄ ୊ܻ

    View Slide

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

    View Slide

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

    View 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 Slide

  66. 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 Slide

  67. 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 Slide

  68. 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 Slide

  69. 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 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 Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

  74. 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 Slide

  75. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide