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"

  2. None
  3. None
  4. • 2015֙ 12ਘ ୹द

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

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

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

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

    Ր੐হח ࢎਊ੗ ੋఠ࠭ܳ ాೠ ѐࢶ • ҅ࣘ ѐࢶغҊ ߸ചೞח ઁಿ • ௏٘о ҅ࣘ ߄Ո׮
  9. None
  10. “߸ച ৻ী ৔ਗೠ Ѫ਷ হ׮.” - Heraclitus

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

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

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

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

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

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

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

    ੸׮. = ௏٘о ੜ ܻ࠙غয੓׮. = ௏٘о ࠄ૕ী ݏѱ ࢸ҅غয੓׮.
  18. ࠄ૕ = { بݫੋ੄ ࠄ૕, ѐߊ ҳઑ ࢚੄ ࠄ૕, উ٘۽੉٘

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

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

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

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

    ҳઑ ࢚੄ ࠄ૕, ... }
  23. https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

  24. 1. Independent of Frameworks

  25. 1. Independent of Frameworks 2. Testable

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

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

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

    4. Independent of Database 5. Independent of External Configuration
  29. https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way

  30. 4 Layers

  31. Presentation

  32. Presentation Data

  33. Presentation Data Domain

  34. Presentation Data Domain Entity

  35. Presentation Data Domain Entity

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

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

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

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

    بݫੋ(࠺ૉפझ ۽૒)ীࢲ ౵ࢤغח ѐ֛ਸ ಴അ • (э਷ ࢲ࠺झ) Android - iOS - ࢲߡ ݽف زੌೠ ഋక
  40. class TransactionCategory(
 val id: String,
 val name: String,
 val transactionType:

    TransactionType,
 val priority: Int ) : Entity
  41. Domain • ࣽࣻೠ Java(Kotlin) ݽٕ

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

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

    Interface ੿੄
  44. class GetBanks(val repository: BanksRepository) : UseCase<List<Bank>>() {
 override fun buildUseCaseObservable():

    Observable<List<Bank>> {
 return repository.getAll()
 }
 }
  45. class GetBanks(val repository: BanksRepository) : UseCase<List<Bank>>() {
 override fun buildUseCaseObservable():

    Observable<List<Bank>> {
 return repository.getAll()
 }
 }
  46. class GetBanks(val repository: BanksRepository) : UseCase<List<Bank>>() {
 override fun buildUseCaseObservable():

    Observable<List<Bank>> {
 return repository.getAll()
 }
 }
  47. interface BanksRepository : Repository {
 fun getAll(): Observable<List<Bank>>
 fun update(bank:

    Bank): Completable
 }
  48. interface BanksRepository : Repository {
 fun getAll(): Observable<List<Bank>>
 fun update(bank:

    Bank): Completable
 }
  49. interface BanksRepository : Repository {
 fun getAll(): Observable<List<Bank>>
 fun update(bank:

    Bank): Completable
 }
  50. Data • Repository੄ पઁ ҳഅ

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

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

    Android ੄ઓࢿ
  53. override fun getAll(): Observable<List<Bank>> {
 return Observable.fromCallable {
 with(Realm.getDefaultInstance()) {


    use {
 where(BankModel::class.java).findAll().map { 
 BankEntityMapper.fromRealmObject(it) 
 }
 } 
 }
 }
 }
  54. override fun getAll(): Observable<List<Bank>> {
 return Observable.fromCallable {
 with(Realm.getDefaultInstance()) {


    use {
 where(BankModel::class.java).findAll().map { 
 BankEntityMapper.fromRealmObject(it) 
 }
 } 
 }
 }
 }
  55. override fun getAll(): Observable<List<Bank>> {
 return Observable.fromCallable {
 with(Realm.getDefaultInstance()) {


    use {
 where(BankModel::class.java).findAll().map { 
 BankEntityMapper.fromRealmObject(it) 
 }
 } 
 }
 }
 }
  56. open class Bank : RealmObject() {
 @PrimaryKey open lateinit var

    id: String
 open lateinit var name: String
 open lateinit var imageUrl: String
 }
  57. override fun getAll(): Observable<List<Bank>> {
 return Observable.fromCallable {
 with(Realm.getDefaultInstance()) {


    use {
 where(BankModel::class.java).findAll().map { 
 BankEntityMapper.fromRealmObject(it)
 }
 } 
 }
 }
 }
  58. object BankEntityMapper : EntityMapper<BankModel, Bank> {
 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
 }
 }
 }
  59. object BankEntityMapper : EntityMapper<BankModel, Bank> {
 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
 }
 }
 }
  60. class BanksNetworkRepository(private val context: Context) : BanksRepository {
 override fun

    getAll(): Observable<List<Bank>> {
 return context.retrofit.banksApi.getSupportedBanks()
 .map(GetSupportedBanksResponse::banks)
 .map { it.map { BankEntityMapper.fromNetworkResponseModel(it) } }
 }
 }
  61. data class Bank(
 @SerializedName("id") var id: String? = null,
 @SerializedName("name")

    var name: String? = null,
 @SerializedName("image_url") var imageUrl: String? = null
 ) : Response
  62. Presentation • UI ۨ߰੄ ୊ܻ

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

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

    Presenter
  65. class OrganizationFragment : BaseFragment<OrganizationPresenter, OrganizationView>(), 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() }
 }
 }
  66. class OrganizationFragment : BaseFragment<OrganizationPresenter, OrganizationView>(), 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() }
 }
 }
  67. class OrganizationFragment : BaseFragment<OrganizationPresenter, OrganizationView>(), 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() }
 }
 }
  68. class OrganizationFragment : BaseFragment<OrganizationPresenter, OrganizationView>(), 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() }
 }
 }
  69. class OrganizationFragment : BaseFragment<OrganizationPresenter, OrganizationView>(), 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() }
 }
 }
  70. class OrganizationPresenter(
 view: OrganizationView,
 context: Context,
 private val getBanks: GetBanks

    = GetBanks(BanksNetworkRepository(context))
 ) : Presenter<OrganizationView>(view) { override fun onDestroy() {
 getBanks.unsubscribe()
 } 
 fun onViewCreated() {
 getBanks.execute(object : DefaultSubscriber<List<Bank>>() {
 override fun onNext(t: List<Bank>) {
 view.showBanks(t)
 }
 })
 }
 
 fun onNextButtonClicked() {
 with(view.getSelectedOrganizations()) {
 if (isNotEmpty()) {
 view.moveToAddCertificatePage(this)
 }
 }
 }
 }
  71. class OrganizationPresenter(
 view: OrganizationView,
 context: Context,
 private val getBanks: GetBanks

    = GetBanks(BanksNetworkRepository(context))
 ) : Presenter<OrganizationView>(view) { override fun onDestroy() {
 getBanks.unsubscribe()
 } 
 fun onViewCreated() {
 getBanks.execute(object : DefaultSubscriber<List<Bank>>() {
 override fun onNext(t: List<Bank>) {
 view.showBanks(t)
 }
 })
 }
 
 fun onNextButtonClicked() {
 with(view.getSelectedOrganizations()) {
 if (isNotEmpty()) {
 view.moveToAddCertificatePage(this)
 }
 }
 }
 }
  72. class OrganizationPresenter(
 view: OrganizationView,
 context: Context,
 private val getBanks: GetBanks

    = GetBanks(BanksNetworkRepository(context))
 ) : Presenter<OrganizationView>(view) { override fun onDestroy() {
 getBanks.unsubscribe()
 } 
 fun onViewCreated() {
 getBanks.execute(object : DefaultSubscriber<List<Bank>>() {
 override fun onNext(t: List<Bank>) {
 view.showBanks(t)
 }
 })
 }
 
 fun onNextButtonClicked() {
 with(view.getSelectedOrganizations()) {
 if (isNotEmpty()) {
 view.moveToAddCertificatePage(this)
 }
 }
 }
 }
  73. class OrganizationPresenter(
 view: OrganizationView,
 context: Context,
 private val getBanks: GetBanks

    = GetBanks(BanksNetworkRepository(context))
 ) : Presenter<OrganizationView>(view) { override fun onDestroy() {
 getBanks.unsubscribe()
 } 
 fun onViewCreated() {
 getBanks.execute(object : DefaultSubscriber<List<Bank>>() {
 override fun onNext(t: List<Bank>) {
 view.showBanks(t)
 }
 })
 }
 
 fun onNextButtonClicked() {
 with(view.getSelectedOrganizations()) {
 if (isNotEmpty()) {
 view.moveToAddCertificatePage(this)
 }
 }
 }
 }
  74. class OrganizationPresenter(
 view: OrganizationView,
 context: Context,
 private val getBanks: GetBanks

    = GetBanks(BanksNetworkRepository(context))
 ) : Presenter<OrganizationView>(view) { override fun onDestroy() {
 getBanks.unsubscribe()
 } 
 fun onViewCreated() {
 getBanks.execute(object : DefaultSubscriber<List<Bank>>() {
 override fun onNext(t: List<Bank>) {
 view.showBanks(t)
 }
 })
 }
 
 fun onNextButtonClicked() {
 with(view.getSelectedOrganizations()) {
 if (isNotEmpty()) {
 view.moveToAddCertificatePage(this)
 }
 }
 }
 }
  75. data class Amount(
 var amount: Long,
 var currency: Currency
 )

    : Parcelable {
 
 companion object {
 @JvmField val CREATOR: Parcelable.Creator<Amount> = object : Parcelable.Creator<Amount> {
 override fun createFromParcel(source: Parcel): Amount = Amount(source)
 override fun newArray(size: Int): Array<Amount?> = 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)
 }
 }
  76. ۨ੉פझ౟ীࢲח • જ਷ ௏٘੄ ҳઑо જ਷ ઁಿਵ۽ ੉য૓׮Ҋ ޺ӝী ੉۞ೠ

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

    ইఃఫ୊ܳ ੸ਊೞ৓णפ׮. • ইఃఫ୊ بੑ੄ ૓ੑ ੢߷ࠁ׮ ߸ചী ੸਽ೡ ࣻ ੓਺ীࢲ য়ח ਬোೣ੉ ؊ ч૑׮Ҋ ࢤпೞৈ ੉۞ೠ ইఃఫ୊ܳ ੸ਊ ೞ৓णפ׮.
  78. ߛ௼࢟۞٘ܳ ݅٘ח ۨ੉פझ౟ীࢲ ੷൞ ৬ ೣԋೡ উ٘۽੉٘ ূ૑פয ٜ࠙ਸ ଺

    Ҋ ੓णפ׮. Kotlin, RxJava, Retrofit, Glide, Realm
  79. Questions github: sunghyunzz email: [email protected]