Droid Knights 2017에서 발표한 [Clean Architecture in Android] 발표 슬라이드입니다. (https://droidknights.github.io/2017/)
Clean Architecture inAndroidconst val presenter = "sunghyunzz"
View Slide
• 2015֙ 12ਘ द
• 2015֙ 12ਘ द• ف ߣ ӏݽ সؘ
• 2015֙ 12ਘ द• ف ߣ ӏݽ সؘ• Րহח ࢎਊ ੋఠ࠭ܳ ాೠ ѐࢶ
• 2015֙ 12ਘ द• ف ߣ ӏݽ সؘ• Րহח ࢎਊ ੋఠ࠭ܳ ాೠ ѐࢶ• ҅ࣘ ѐࢶغҊ ߸ചೞח ઁಿ
• 2015֙ 12ਘ द• ف ߣ ӏݽ সؘ• Րহח ࢎਊ ੋఠ࠭ܳ ాೠ ѐࢶ• ҅ࣘ ѐࢶغҊ ߸ചೞח ઁಿ• ٘о ҅ࣘ ߄Ո
“߸ച ৻ী ਗೠ Ѫ হ.”- Heraclitus
“ೖೡ ࣻ হਵݶ ૌѹۄ.”- ־ҵо
import kotlinx.android.synthetic.main.activity_transactions_temp.*
߸ചী ੜ ೡ ࣻ ח ٘חযڃ ݽणੌө?
߸ചী ੜ ೡ ࣻ .= ߸ചী ٮܲ ٘ ߸҃ .
߸ചী ੜ ೡ ࣻ .= ߸ചী ٮܲ ٘ ߸҃ .= ٘о ੜ ܻ࠙غয.
߸ചী ੜ ೡ ࣻ .= ߸ചী ٮܲ ٘ ߸҃ .= ٘о ੜ ܻ࠙غয.= ٘о ࠄী ݏѱ ࢸ҅غয.
ࠄ = {بݫੋ ࠄ,ѐߊ ҳઑ ࢚ ࠄ,উ٘۽٘ ۖಬ ࠄ,...}
ࠄ = {بݫੋ ࠄ,ѐߊ ҳઑ ࢚ ࠄ,উ٘۽٘ ҳઑ ࢚ ࠄ,...}
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
1. Independent of Frameworks
1. Independent of Frameworks2. Testable
1. Independent of Frameworks2. Testable3. Independent of UI
1. Independent of Frameworks2. Testable3. Independent of UI4. Independent of Database
1. Independent of Frameworks2. Testable3. Independent of UI4. Independent of Database5. Independent of External Configuration
https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way
4 Layers
Presentation
PresentationData
PresentationDataDomain
PresentationDataDomainEntity
Entity• ࣽࣻೠ Java(Kotlin) ݽٕ
Entity• ࣽࣻೠ Java(Kotlin) ݽٕ• Android৬ ઓࢿ হ
Entity• ࣽࣻೠ Java(Kotlin) ݽٕ• Android৬ ઓࢿ হ• بݫੋ(࠺ૉפझ ۽)ীࢲ ࢤغח ѐ֛ਸ അ
Entity• ࣽࣻೠ Java(Kotlin) ݽٕ• Android৬ ઓࢿ হ• بݫੋ(࠺ૉפझ ۽)ীࢲ ࢤغח ѐ֛ਸ അ• (э ࢲ࠺झ) Android - iOS - ࢲߡ ݽف زੌೠ ഋక
class TransactionCategory( val id: String, val name: String, val transactionType: TransactionType, val priority: Int) : Entity
Domain• ࣽࣻೠ Java(Kotlin) ݽٕ
Domain• ࣽࣻೠ Java(Kotlin) ݽٕ• Use Case
Domain• ࣽࣻೠ Java(Kotlin) ݽٕ• Use Case• Repository Interface
class GetBanks(val repository: BanksRepository) : UseCase>() { override fun buildUseCaseObservable(): Observable> { return repository.getAll() } }
interface BanksRepository : Repository { fun getAll(): Observable> fun update(bank: Bank): Completable }
Data• Repository पઁ ҳഅ
Data• Repository पઁ ҳഅ• Data Source ઓࢿ
Data• Repository पઁ ҳഅ• Data Source ઓࢿ• Android ઓࢿ
override fun getAll(): Observable> { return Observable.fromCallable { with(Realm.getDefaultInstance()) { use { where(BankModel::class.java).findAll().map { BankEntityMapper.fromRealmObject(it) } } } } }
open class Bank : RealmObject() { @PrimaryKey open lateinit var id: String open lateinit var name: String open lateinit var imageUrl: String }
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 } } }
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) } } } }
data class Bank( @SerializedName("id") var id: String? = null, @SerializedName("name") var name: String? = null, @SerializedName("image_url") var imageUrl: String? = null ) : Response
Presentation• UI ۨ߰ ܻ
Presentation• UI ۨ߰ ܻ• Android ઓࢿ
Presentation• UI ۨ߰ ܻ• Android ઓࢿ• View৬ Presenter
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() } } }
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) } } } }
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) } }
ۨפझীࢲח• જ ٘ ҳઑо જ ઁಿਵ۽ যҊ ӝী ۞ೠইఃఫܳ ਊೞणפ.
ۨפझীࢲח• જ ٘ ҳઑо જ ઁಿਵ۽ যҊ ӝী ۞ೠইఃఫܳ ਊೞणפ.• ইఃఫ بੑ ੑ ߷ࠁ ߸ചী ೡ ࣻ ীࢲয়ח ਬোೣ ؊ чҊ ࢤпೞৈ ۞ೠ ইఃఫܳ ਊೞणפ.
ߛ࢟۞٘ܳ ݅٘ח ۨפझীࢲ ൞৬ ೣԋೡ উ٘۽٘ ূפয ٜ࠙ਸ Ҋ णפ.Kotlin, RxJava, Retrofit, Glide, Realm
Questionsgithub: sunghyunzzemail: [email protected]