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

Clean Architecture (in Android) Revised

Clean Architecture (in Android) Revised

[Droid Knights 2019] 행사에서 발표한 [Clean Architecture (in Android) Revised] 발표 자료입니다. 이전에 발표했던 [Clean Architecture in Android] 발표를 2019년의 시각으로 재구성을 시도합니다.

Sunghyun Hwang

April 05, 2019
Tweet

More Decks by Sunghyun Hwang

Other Decks in Programming

Transcript

  1. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  2. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  3. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  4. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  5. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  6. <6TF$BTF>#VZ(PPET  #VZFSDBMMTJOXJUIBQVSDIBTFSFRVFTU  $PNQBOZDBQUVSFTCVZFSTOBNF BEESFTT SFRVFTUFEHPPET FUD  $PNQBOZHJWFTCVZFSJOGPSNBUJPOPOHPPET

    QSJDFT EFMJWFSZ EBUFT FUD  #VZFSTJHOTGPSPSEFS  IUUQXXXDTPUBHPBDO[DPVSTFXPSLDPTDVDUFNQMBIUN
  7. External Interfaces Android UI DB Network W eb iOS Cache

    Controllers G atew ays Presenters Use Cases Entities 5IF$MFBO "SDIJUFDUVSF
  8. External Interfaces Android UI DB Network W eb iOS Cache

    Use Cases Entities &OUJUZ⛓ \%# )551"1* ^
  9. External Interfaces Android UI DB Network W eb iOS Cache

    Use Cases Entities &OUJUZ⛓ \%# )551"1* ^
  10. External Interfaces Android UI DB Network W eb iOS Cache

    Controllers G atew ays Presenters Use Cases Entities *OUFSGBDF "EBQUFST
  11. External Interfaces Android UI DB Network W eb iOS Cache

    Controllers G atew ays Presenters Use Cases Entities 4FQBSBUF #VTJOFTT-PHJD GSPN UIF$IBOHFBCMF
  12. External Interfaces Android UI DB Network W eb iOS Cache

    Controllers G atew ays Presenters Use Cases Entities 5IF %FQFOEFODZ 3VMF
  13. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT
  14. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT
  15. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT 
  16. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT  
  17. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT  
  18. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT   *UT*NQMFNFOUBUJPO
  19. IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM $PODMVTJPO $POGPSNJOHUPUIFTFTJNQMFSVMFTJTOPUIBSE BOEXJMMTBWFZPVBMPUPGIFBEBDIFT HPJOH GPSXBSE #Z TFQBSBUJOH UIF TPGUXBSF

    JOUP MBZFST  BOE DPOGPSNJOH UP 5IF %FQFOEFODZ3VMF ZPVXJMMDSFBUFBTZTUFNUIBUJTJOUSJOTJDBMMZUFTUBCMF XJUIBMMUIF CFOFGJUTUIBUJNQMJFT8IFOBOZPGUIFFYUFSOBMQBSUTPGUIFTZTUFNCFDPNFPCTPMFUF  MJLFUIFEBUBCBTF PSUIFXFCGSBNFXPSL ZPVDBOSFQMBDFUIPTFPCTPMFUFFMFNFOUT XJUIBNJOJNVNPGGVTT   5BPPGUIF$" *UT*NQMFNFOUBUJPO
  20. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  21. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  22. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  23. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  24. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  25. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } fun handleAddTodo(sub: Scheduler, ob: Scheduler) { val d = addTodo(requestAddTodo(LocalDateTime.now())) .flatMap { getTodos() } .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } } fun handleDestroy() { compositeDisposable.dispose() }
  26. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } fun handleAddTodo(sub: Scheduler, ob: Scheduler) { val d = addTodo(requestAddTodo(LocalDateTime.now())) .flatMap { getTodos() } .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } } fun handleDestroy() { compositeDisposable.dispose() }
  27. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } fun handleAddTodo(sub: Scheduler, ob: Scheduler) { val d = addTodo(requestAddTodo(LocalDateTime.now())) .flatMap { getTodos() } .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } } fun handleDestroy() { compositeDisposable.dispose() } import io.reactivex.Scheduler import io.reactivex.disposables.CompositeDisposable import java.time.LocalDateTime
  28. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } fun handleAddTodo(sub: Scheduler, ob: Scheduler) { val d = addTodo(requestAddTodo(LocalDateTime.now())) .flatMap { getTodos() } .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } } fun handleDestroy() { compositeDisposable.dispose() }
  29. interface GetTodos : TodoGateway, TodoServices { fun getTodos(): Single<GetTodosResult> =

    get().map { with(calculateProgress(it)) { GetTodosResult( it, this.first, this.second, this.third ) } } }
  30. interface TodoServices { fun calculateProgress(todos: List<Todo>): Triple<Int, Int, Float> {

    val nrOfDone = todos.count { it.done } val nrOfTotal = todos.count() return Triple(nrOfDone, nrOfTotal, nrOfDone / nrOfTotal.toFloat()) } } interface GetTodos : TodoGateway, TodoServices { fun getTodos(): Single<GetTodosResult> = get().map { with(calculateProgress(it)) { GetTodosResult( it, this.first, this.second, this.third ) } } }
  31. interface TodoServices { fun calculateProgress(todos: List<Todo>): Triple<Int, Int, Float> {

    val nrOfDone = todos.count { it.done } val nrOfTotal = todos.count() return Triple(nrOfDone, nrOfTotal, nrOfDone / nrOfTotal.toFloat()) } } interface GetTodos : TodoGateway, TodoServices { fun getTodos(): Single<GetTodosResult> = get().map { with(calculateProgress(it)) { GetTodosResult( it, this.first, this.second, this.third ) } } } data class Todo(val id: Long, val description: String, val done: Boolean)
  32. interface TodoServices { fun calculateProgress(todos: List<Todo>): Triple<Int, Int, Float> {

    val nrOfDone = todos.count { it.done } val nrOfTotal = todos.count() return Triple(nrOfDone, nrOfTotal, nrOfDone / nrOfTotal.toFloat()) } } interface GetTodos : TodoGateway, TodoServices { fun getTodos(): Single<GetTodosResult> = get().map { with(calculateProgress(it)) { GetTodosResult( it, this.first, this.second, this.third ) } } } data class Todo(val id: Long, val description: String, val done: Boolean)
  33. interface GetTodos : TodoGateway, TodoServices { fun getTodos(): Single<GetTodosResult> =

    get().map { with(calculateProgress(it)) { GetTodosResult( it, this.first, this.second, this.third ) } } } interface TodoGateway { fun get(): Single<List<Todo>> fun get(id: Int): Maybe<Todo> fun add(description: String, done: Boolean): Single<Todo> }
  34. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override val

    compositeDisposable: CompositeDisposable get() = CompositeDisposable() override val todoDao: TodoDao get() = AppDatabase.getInstance(this).todosDao() override fun renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) fab.setOnClickListener { handleAddTodo(Schedulers.io(), AndroidSchedulers.mainThread()) } handleStart(Schedulers.io(), AndroidSchedulers.mainThread()) } override fun onDestroy() { super.onDestroy() handleDestroy() } }
  35. interface TodoGatewayImpl : TodoGateway, TodoDaoProvider { private fun convert(d: TodoDataModel):

    Todo = Todo(d.id!!, d.description, d.done) override fun get(): Single<List<Todo>> = todoDao.get().map { it.map(::convert) } override fun get(id: Int): Maybe<Todo> = todoDao.get(id).map(::convert) override fun add(description: String, done: Boolean): Single<Todo> = todoDao.insert(TodoDataModel(null, description, done)).map { id -> Todo(id, description, done) } }
  36. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } }
  37. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } }
  38. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } }
  39. interface TodosView : AddTodo, GetTodos, TodosPresenter, TodosController { val compositeDisposable:

    CompositeDisposable fun renderTodos(vm: TodosViewModel) fun handleStart(sub: Scheduler, ob: Scheduler) { val d = getTodos() .subscribeOn(sub) .observeOn(ob) .subscribe { r -> renderTodos( presentTodos(r) ) } compositeDisposable.add(d) } }
  40. interface TodosPresenter { fun presentTodos(result: GetTodosResult): TodosViewModel = TodosViewModel( result.todos.map

    { TodoViewModel(it.done, it.description.take(maxDescLength)) }, "${result.nrOfDone} / ${result.nrOfTotal}" ) companion object { private const val maxDescLength = 20 } }
  41. class MainActivity : AppCompatActivity(), TodoGatewayImpl, TodoDaoProvider, TodosView { override fun

    renderTodos(vm: TodosViewModel) { someTextView.text = vm.statusDescription } }
  42. interface TodosController { fun requestAddTodo(dt: LocalDateTime): AddTodoRequest = AddTodoRequest(dt.format(dtFormat), false)

    companion object { private val dtFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") } }
  43. Entity • ࣽࣻೠ Java(Kotlin) ݽٕ • Android৬੄ ੄ઓࢿ੉ হ਺ •

    بݫੋ(࠺ૉפझ ۽૒)ীࢲ ౵ࢤغח ѐ֛ਸ ಴അ • (э਷ ࢲ࠺झ) Android - iOS - ࢲߡ ݽف زੌೠ ഋక
  44. 8FSF)JSJOH  "OESPJE&OHJOFFS  J04&OHJOFFSJOH  4FSWFS&OHJOFFS 1ZUIPO4DBMB  

    %FW0QT&OHJOFFS  *OGSB&OHJOFFS4ZTUFN&OHJOFFS  %BUB&OHJOFFS  1SPEVDU"OBMZTU  2"&OHJOFFS