Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

5

Share

Download to read offline

DroidKnight 2018 State machine by Selaed class

Download to read offline

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

DroidKnight 2018 State machine by Selaed class

  1. 1. Kotlin Sealed Class를 이용한 뷰상태 관리 Speaker.우명인
  2. 2. 우명인
  3. 3. Index • 뷰 상태? • 상태가 왜 중요한데? • 어떻게 개선하는데? • 지속적인 개선
  4. 4. 뷰 상태?
  5. 5. 상태 뷰 상태?
  6. 6. 상태 뷰 상태?
  7. 7. 상태 뷰 상태?
  8. 8. 뷰 상태? • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다.
  9. 9. • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다. • 상태란 상황에 따라 변경 될수 있는 값을 말한다. 뷰 상태?
  10. 10. • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다. • 상태란 상황에 따라 변경 될수 있는 값을 말한다. • Android는 상태값을 화면에 보여주는 프로그램이다. 뷰 상태?
  11. 11. 그래서 상태가 왜 중요한데???
  12. 12. 상태가 왜 중요한데?
  13. 13. 상태가 왜 중요한데?
  14. 14. 의도 하지 않은 유저경험 제공 상태가 왜 중요한데?
  15. 15. class MainActivity : AppCompatActivity() { companion object { const val RED = 1 const val GREEN = 2 const val BLUE = 3 } var currentColor: Int = RED override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_red.setOnClickListener { onChangedCount(RED) } bt_green.setOnClickListener { onChangedCount(GREEN) } bt_blue.setOnClickListener { onChangedCount(BLUE) } } private fun onChangedCount(color: Int) { tv_color.text = "${getColorStr(currentColor)} -> ${getColorStr(color)}" iv_color.setBackgroundColor(ContextCompat.getColor(this, when (color) { MainViewModel.RED -> R.color.green MainViewModel.GREEN -> R.color.green MainViewModel.BLUE -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) currentColor = color } private fun getColorStr(color: Int): String = getString(when (color) { MainViewModel.RED -> R.string.red MainViewModel.GREEN -> R.string.green MainViewModel.BLUE -> R.string.blue else -> throw IllegalArgumentException("invalid color") }) } 상태가 왜 중요한데?
  16. 16. class MainActivity : AppCompatActivity() { companion object { const val RED = 1 const val GREEN = 2 const val BLUE = 3 } var currentColor: Int = RED override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_red.setOnClickListener { onChangedCount(RED) } bt_green.setOnClickListener { onChangedCount(GREEN) } bt_blue.setOnClickListener { onChangedCount(BLUE) } } private fun onChangedCount(color: Int) { tv_color.text = "${getColorStr(currentColor)} -> ${getColorStr(color)}" iv_color.setBackgroundColor(ContextCompat.getColor(this, when (color) { MainViewModel.RED -> R.color.green MainViewModel.GREEN -> R.color.green MainViewModel.BLUE -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) currentColor = color } private fun getColorStr(color: Int): String = getString(when (color) { MainViewModel.RED -> R.string.red MainViewModel.GREEN -> R.string.green MainViewModel.BLUE -> R.string.blue else -> throw IllegalArgumentException("invalid color") }) } 상태가 왜 중요한데? 화면이 복잡해짐 ->
  17. 17. class MainActivity : AppCompatActivity() { companion object { const val RED = 1 const val GREEN = 2 const val BLUE = 3 } var currentColor: Int = RED override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_red.setOnClickListener { onChangedCount(RED) } bt_green.setOnClickListener { onChangedCount(GREEN) } bt_blue.setOnClickListener { onChangedCount(BLUE) } } private fun onChangedCount(color: Int) { tv_color.text = "${getColorStr(currentColor)} -> ${getColorStr(color)}" iv_color.setBackgroundColor(ContextCompat.getColor(this, when (color) { MainViewModel.RED -> R.color.green MainViewModel.GREEN -> R.color.green MainViewModel.BLUE -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) currentColor = color } private fun getColorStr(color: Int): String = getString(when (color) { MainViewModel.RED -> R.string.red MainViewModel.GREEN -> R.string.green MainViewModel.BLUE -> R.string.blue else -> throw IllegalArgumentException("invalid color") }) } 상태가 왜 중요한데? 화면이 복잡해짐 -> 상태가 많아짐 ->
  18. 18. class MainActivity : AppCompatActivity() { companion object { const val RED = 1 const val GREEN = 2 const val BLUE = 3 } var currentColor: Int = RED override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_red.setOnClickListener { onChangedCount(RED) } bt_green.setOnClickListener { onChangedCount(GREEN) } bt_blue.setOnClickListener { onChangedCount(BLUE) } } private fun onChangedCount(color: Int) { tv_color.text = "${getColorStr(currentColor)} -> ${getColorStr(color)}" iv_color.setBackgroundColor(ContextCompat.getColor(this, when (color) { MainViewModel.RED -> R.color.green MainViewModel.GREEN -> R.color.green MainViewModel.BLUE -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) currentColor = color } private fun getColorStr(color: Int): String = getString(when (color) { MainViewModel.RED -> R.string.red MainViewModel.GREEN -> R.string.green MainViewModel.BLUE -> R.string.blue else -> throw IllegalArgumentException("invalid color") }) } 상태가 왜 중요한데? 화면이 복잡해짐 -> 상태가 많아짐 -> 관리가 어려워짐 ->
  19. 19. class MainActivity : AppCompatActivity() { companion object { const val RED = 1 const val GREEN = 2 const val BLUE = 3 } var currentColor: Int = RED override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_red.setOnClickListener { onChangedCount(RED) } bt_green.setOnClickListener { onChangedCount(GREEN) } bt_blue.setOnClickListener { onChangedCount(BLUE) } } private fun onChangedCount(color: Int) { tv_color.text = "${getColorStr(currentColor)} -> ${getColorStr(color)}" iv_color.setBackgroundColor(ContextCompat.getColor(this, when (color) { MainViewModel.RED -> R.color.green MainViewModel.GREEN -> R.color.green MainViewModel.BLUE -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) currentColor = color } private fun getColorStr(color: Int): String = getString(when (color) { MainViewModel.RED -> R.string.red MainViewModel.GREEN -> R.string.green MainViewModel.BLUE -> R.string.blue else -> throw IllegalArgumentException("invalid color") }) } 상태가 왜 중요한데? 화면이 복잡해짐 -> 상태가 많아짐 -> 관리가 어려워짐 -> SideEffect가 발생하기 쉬움
  20. 20. 상태가 왜 중요한데?
  21. 21. crash 발생 상태가 왜 중요한데?
  22. 22. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } 상태가 왜 중요한데?
  23. 23. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException(“invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) 상태가 왜 중요한데?
  24. 24. 이게 어때서?
  25. 25. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } 이게 어때서?
  26. 26. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } 이게 어때서?
  27. 27. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } 이게 어때서? Activity가 상태를 관리
  28. 28. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } 이게 어때서? Activity가 상태를 관리 상태를 상수 값으로 관리
  29. 29. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException(“invalid item”) }) 이게 어때서?
  30. 30. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) 이게 어때서?
  31. 31. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) 이게 어때서? Activity가 상태를 분기 해서 처리
  32. 32. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) 이게 어때서? Activity가 상태를 분기 해서 처리 SideEffect가 발생 할 수 있다.
  33. 33. ItemD가 추가된다면?
  34. 34. ItemD가 추가된다면? companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 }
  35. 35. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 const val ITEM_D = 4 } ItemD가 추가된다면?
  36. 36. ItemD가 추가된다면? bt_d.setOnClickListener { updateItem(ITEM_D) currentItem = ITEM_D }
  37. 37. ItemD가 추가된다면?
  38. 38. ItemD가 추가된다면?
  39. 39. ItemD가 추가된다면?
  40. 40. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) ItemD가 추가된다면?
  41. 41. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) ItemD가 추가된다면?
  42. 42. private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue ITEM_D -> R.color.black else -> throw IllegalArgumentException("invalid color") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c ITEM_D -> R.string.d else -> throw IllegalArgumentException("invalid color") }) ItemD가 추가된다면?
  43. 43. ItemN 이 계속 추가된다면?
  44. 44. ItemN 이 계속 추가된다 면? companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 const val ITEM_D = 4 ... ... ... const val ITEM_N = N }
  45. 45. ItemN 이 계속 추가된다 면? private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue . . . . . . ITEM_N -> R.color.N else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c . . . . . . ITEM_N -> R.string.n else -> throw IllegalArgumentException("invalid item") })
  46. 46. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 ItemN 이 계속 추가된다 면?
  47. 47. ItemN 이 계속 추가된다 면? • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다.
  48. 48. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. ItemN 이 계속 추가된다 면?
  49. 49. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. • 중복한 N이 작성 될수도 있다. ItemN 이 계속 추가된다 면?
  50. 50. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. • 중복한 N이 작성 될수도 있다. • 요구 사항이 늘어 날 때마다 코드 전체가 영향을 받는다. ItemN 이 계속 추가된다 면?
  51. 51. 어떻게 개선하는데?
  52. 52. 어떻게 개선하는데?
  53. 53. MVP 어떻게 개선하는데?
  54. 54. class MainActivity : AppCompatActivity() { companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } var currentItem: Int = ITEM_A override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bt_a.setOnClickListener { updateItem(ITEM_A) currentItem = ITEM_A } bt_b.setOnClickListener { updateItem(ITEM_B) currentItem = ITEM_B } bt_c.setOnClickListener { updateItem(ITEM_C) currentItem = ITEM_C } } private fun updateItem(item: Int) { tv_item.text = "${getItemStr(currentItem)} -> ${getItemStr(item)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (item) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException(“invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) } 어떻게 개선하는데?
  55. 55. interface MainView { fun onUpdatedItem(oldItem: Int, newItem: Int) } 어떻게 개선하는데?
  56. 56. class MainPresenter(val view: MainView) { companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } private var currentItem: Int = -1 fun onClicked(item: Int) { view.onUpdatedItem(currentItem, item) currentItem = item } } 어떻게 개선하는데?
  57. 57. class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(ITEM_C) } } override fun onUpdatedItem(oldItem: Int, newItem: Int) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) }) } 어떻게 개선하는데?
  58. 58. 뭐가 개선 됐는데?
  59. 59. 뭐가 개선 됐는데? class MainPresenter(val view: MainView) { companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } private var currentItem: Int = -1 fun onClicked(item: Int) { view.onUpdatedItem(currentItem, item) currentItem = item } } 상태를 Presenter가 관리한다.
  60. 60. 뭐가 개선 됐는데?
  61. 61. 조금 더 개선해보자.
  62. 62. 조금 더 개선해보자. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 }
  63. 63. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C }
  64. 64. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C } 애매모호한 상수에서 명시적인 class로 변경
  65. 65. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C } 애매모호한 상수에서 명시적인 class로 변경 설계 시 모든 상태를 알고 있다.
  66. 66. 조금 더 개선해보자. override fun onUpdatedItem(oldItem: Int, newItem: Int) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item”) })
  67. 67. 조금 더 개선해보자. override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item”) })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item") })
  68. 68. 조금 더 개선해보자. else가 필요 없다. override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid item”) })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid item") })
  69. 69. ItemD가 추가된다면?
  70. 70. ItemD가 추가된다면? enum class Item { ITEM_A, ITEM_B, ITEM_C }
  71. 71. ItemD가 추가된다면? enum class Item { ITEM_A, ITEM_B, ITEM_C, ITEM_D }
  72. 72. ItemD가 추가된다면? override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c })
  73. 73. ItemD가 추가된다면? override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c }) 컴파일 타임에 에러가 발생
  74. 74. ItemD가 추가된다면? override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue ITEM_D -> R.color.black })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c ITEM_D -> R.string.d })
  75. 75. ItemD가 추가된다면? override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue ITEM_D -> R.color.black })) } private fun getItemStr(item: Item): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c ITEM_D -> R.string.d }) 애매모호한 상수에서 명시적인 class로 변경 설계 시 모든 상태를 알고 있다. 값만 추가 할 경우컴파일 타임에 에러가 발생.
  76. 76. 조금 더 개선해보자.
  77. 77. 조금 더 개선해보자. enum class Item(val titleId: Int, val colorId: Int) { ITEM_A(R.string.a, R.color.red), ITEM_B(R.string.b, R.color.green), ITEM_C(R.string.c, R.color.blue) }
  78. 78. 조금 더 개선해보자. enum class Item(val titleId: Int, val colorId: Int) { ITEM_A(R.string.a, R.color.red), ITEM_B(R.string.b, R.color.green), ITEM_C(R.string.c, R.color.blue) } 생성 시점에 상태가 가진 값을 알수 있다.
  79. 79. 조금 더 개선해보자. interface MainView { fun onUpdatedItem(oldItem: Int, newItem: Int) }
  80. 80. 조금 더 개선해보자. interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) }
  81. 81. 조금 더 개선해보자. class MainPresenter(val view: MainView) { companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 } private var currentItem: Int = -1 fun onClicked(item: Int) { view.onUpdatedItem(currentItem, item) currentItem = item } }
  82. 82. 조금 더 개선해보자. class MainPresenter(val view: MainView) { private var currentItem: Item = Item.ITEM_A fun onClicked(item: Item) { view.onUpdatedItem(currentItem, item) currentItem = item } }
  83. 83. 조금 더 개선해보자. class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(ITEM_C) } } override fun onUpdatedItem(oldItem: Int, newItem: Int) { tv_item.text = "${getItemStr(oldItem)} -> ${getItemStr(newItem)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, when (newItem) { ITEM_A -> R.color.red ITEM_B -> R.color.green ITEM_C -> R.color.blue else -> throw IllegalArgumentException("invalid color") })) } private fun getItemStr(item: Int): String = getString(when (item) { ITEM_A -> R.string.a ITEM_B -> R.string.b ITEM_C -> R.string.c else -> throw IllegalArgumentException("invalid color") }) }
  84. 84. 조금 더 개선해보자. class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(Item.ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(Item.ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(Item.ITEM_C) } } override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getString(oldItem.titleId)} -> ${getString(newItem.titleId)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, newItem.colorId)) } }
  85. 85. 조금 더 개선해보자. class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(Item.ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(Item.ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(Item.ITEM_C) } } override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${getString(oldItem.titleId)} -> ${getString(newItem.titleId)}" iv_item.setBackgroundColor(ContextCompat.getColor(this, newItem.colorId)) } } View가 상태에 따른 처리를 하지 않아도 된다.
  86. 86. ItemD가 추가된다면?
  87. 87. ItemD가 추가된다면? enum class Item(val titleId: Int, val colorId: Int) { ITEM_A(R.string.a, R.color.red), ITEM_B(R.string.b, R.color.green), ITEM_C(R.string.c, R.color.blue) }
  88. 88. ItemD가 추가된다면? enum class Item(val titleId: Int, val colorId: Int) { ITEM_A(R.string.a, R.color.red), ITEM_B(R.string.b, R.color.green), ITEM_C(R.string.c, R.color.blue), ITEM_D(R.string.d, R.color.black) }
  89. 89. ItemD가 추가된다면? override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(Item.ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(Item.ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(Item.ITEM_C) } }
  90. 90. ItemD가 추가된다면? override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainPresenter = MainPresenter(this) bt_a.setOnClickListener { mainPresenter.onClicked(Item.ITEM_A) } bt_b.setOnClickListener { mainPresenter.onClicked(Item.ITEM_B) } bt_c.setOnClickListener { mainPresenter.onClicked(Item.ITEM_C) } bt_d.setOnClickListener { mainPresenter.onClicked(Item.ITEM_D) } }
  91. 91. ItemN 이 계속 추가된다면?
  92. 92. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. ItemN 이 계속 추가된다 면?
  93. 93. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. • Presenter(비즈니스 로직)는 변하지 않는다. ItemN 이 계속 추가된다 면?
  94. 94. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. • Presenter(비즈니스 로직)는 변하지 않는다. • UI 업데이트 코드도 변하지 않는다. ItemN 이 계속 추가된다 면?
  95. 95. 조금 더 개선해보자.
  96. 96. MVVM 조금 더 개선해보자.
  97. 97. 조금 더 개선해보자. class MainPresenter(val view: MainView) { private var currentItem: Item = Item.ITEM_A fun onClicked(item: Item) { view.onUpdatedItem(currentItem, item) currentItem = item } }
  98. 98. 조금 더 개선해보자. class MainViewModel(val view: MainView) { private var currentItem: Item by Delegates.observable(Item.ITEM_A, { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun onClicked(item: Item) { currentItem = item } }
  99. 99. Delegates.observable? class MainViewModel(val view: MainView) { private var currentItem: Item by Delegates.observable(Item.ITEM_A, { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun onClicked(item: Item) { currentItem = item } }
  100. 100. Delegates.observable? /** * Returns a property delegate for a read/write property that calls a specified callback function when changed. * @param initialValue the initial value of the property. * @param onChange the callback which is called after the change of the property is made. The value of the property * has already been changed when this callback is invoked. */ public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) }
  101. 101. Delegates.observable? class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() }
  102. 102. Delegates.observable? class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" //<no name> -> first }
  103. 103. Delegates.observable? class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" //<no name> -> first user.name = "second" //first -> second }
  104. 104. 조금 더 개선해보자. class MainViewModel(val view: MainView) { private var currentItem: Item by Delegates.observable(Item.ITEM_A, { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun onClicked(item: Item) { currentItem = item } }
  105. 105. 조금 더 개선해보자. class MainViewModel(val view: MainView) { private var currentItem: Item by Delegates.observable(Item.ITEM_A, { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun onClicked(item: Item) { currentItem = item } } 상태값만 변경하면 View가 바뀜
  106. 106. DataStore 에서 Item 정보를 가져온다면?
  107. 107. DataStore 에서 Item 정보를 가져온다면? interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) }
  108. 108. DataStore 에서 Item 정보를 가져온다면? interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) fun showProgress() fun hideProgress() fun onError(throwable: Throwable?) }
  109. 109. DataStore 에서 Item 정보를 가져온다면? enum class ItemType { ITEM_A, ITEM_B, ITEM_C }
  110. 110. DataStore 에서 Item 정보를 가져온다면? class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainViewModel = MainViewModel(this, DataStore()) bt_a.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_A) } bt_b.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_B) } bt_c.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_C) } } override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${oldItem.title} -> ${newItem.title}" iv_item.setBackgroundColor(Color.parseColor(newItem.color)) } }
  111. 111. DataStore 에서 Item 정보를 가져온다면? class MainActivity : AppCompatActivity(), MainView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainViewModel = MainViewModel(this, DataStore()) bt_a.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_A) } bt_b.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_B) } bt_c.setOnClickListener { mainViewModel.requestItemInfo(ItemType.ITEM_C) } } override fun onUpdatedItem(oldItem: Item, newItem: Item) { tv_item.text = "${oldItem.title} -> ${newItem.title}" iv_item.setBackgroundColor(Color.parseColor(newItem.color)) } override fun showProgress() { pb_item.show() } override fun hideProgress() { pb_item.hide() } override fun onError(throwable: Throwable?) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }
  112. 112. DataStore 에서 Item 정보를 가져온다면? enum class Item(val titleId: Int, val colorId: Int) { ITEM_A(R.string.a, R.color.red), ITEM_B(R.string.b, R.color.green), ITEM_C(R.string.c, R.color.blue) }
  113. 113. DataStore 에서 Item 정보를 가져온다면? data class Item(val title: String, val color: String)
  114. 114. DataStore 에서 Item 정보를 가져온다면? class MainViewModel(val view: MainView) { private var currentItem: Item by Delegates.observable(Item.ITEM_A, { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun onClicked(item: Item) { currentItem = item } }
  115. 115. DataStore 에서 Item 정보를 가져온다면? class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun requestItemInfo(itemType: ItemType) { view.showProgress() try { currentItem = dataStore.getItemInfo(itemType) } catch (error: Throwable?) { view.onError(error) } finally { view.hideProgress() } } }
  116. 116. DataStore 에서 Item 정보를 가져온다면? class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun requestItemInfo(itemType: ItemType) { view.showProgress() try { currentItem = dataStore.getItemInfo(itemType) } catch (error: Throwable?) { view.onError(error) } finally { view.hideProgress() } } } 비즈니스 로직에서 View를 업데이트하네?
  117. 117. 조금 더 개선해보자.
  118. 118. 조금 더 개선해보자. sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success(val item: Item) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() }
  119. 119. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) fun requestItemInfo(itemType: ItemType) { view.showProgress() try { currentItem = dataStore.getItemInfo(itemType) } catch (error: Throwable) { view.onError(error) } finally { view.hideProgress() } } }
  120. 120. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Loading() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable?) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  121. 121. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Loading() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable?) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  122. 122. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Loading() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable?) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  123. 123. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Loading() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable?) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  124. 124. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Loading() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable?) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  125. 125. 조금 더 개선해보자.
  126. 126. 조금 더 개선해보자. sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success(val item: Item) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() }
  127. 127. 조금 더 개선해보자. sealed class NetworkState<out T> { class Init : NetworkState<Nothing>() class Loading : NetworkState<Nothing>() class Success<out T>(val item: T) : NetworkState<T>() class Error(val throwable: Throwable?) : NetworkState<Nothing>() }
  128. 128. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState by Delegates.observable(NetworkState.Init()) { _: KProperty<*>, _: NetworkState, newState: NetworkState -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } } fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Init() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  129. 129. 조금 더 개선해보자. class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Init() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  130. 130. 조금 더 개선해보자.(Rx)
  131. 131. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { currentState = NetworkState.Init() currentState = try { NetworkState.Success(dataStore.getItemInfo(itemType)) } catch (error: Throwable) { NetworkState.Error(error) } finally { currentState = NetworkState.Init() } } }
  132. 132. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  133. 133. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  134. 134. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  135. 135. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  136. 136. 조금 더 개선해보자.(Rx) class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  137. 137. 이렇게 하면 뭐가 좋나 요?
  138. 138. 이렇게 하면 뭐가 좋나 요?class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } }
  139. 139. 이렇게 하면 뭐가 좋나 요?class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } } UI 관련 코드
  140. 140. 이렇게 하면 뭐가 좋나 요?class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } } 상태가 한눈에 보인다.
  141. 141. 이렇게 하면 뭐가 좋나 요?class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } } 상태가 한눈에 보인다. 상태값에 따른 UI가 명확해진다.
  142. 142. 이렇게 하면 뭐가 좋나 요?class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } } 비즈니스 로직 코드
  143. 143. class MainViewModel(val view: MainView, val dataStore: DataStore) { private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() } private var currentItem: Item by Delegates.observable(Item("kotlin", "#FF0000"), { _: KProperty<*>, oldItem: Item, newItem: Item -> view.onUpdatedItem(oldItem, newItem) }) private var currentState: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> when (newState) { is NetworkState.Init -> view.hideProgress() is NetworkState.Loading -> view.showProgress() is NetworkState.Success<Item> -> currentItem = newState.item is NetworkState.Error -> view.onError(newState.throwable) } }) fun requestItemInfo(itemType: ItemType) { dataStore.getItemInfo(itemType) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { currentState = NetworkState.Loading() } .doOnTerminate { currentState = NetworkState.Init() } .subscribe({ currentState = NetworkState.Success(it) }, { currentState = NetworkState.Error(it) }) .also { compositeDisposable.add(it) } } } 이렇게 하면 뭐가 좋나 요? 비즈니스 로직 과 상태 관리만 집중
  144. 144. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”
  145. 145. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…)
  146. 146. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String)
  147. 147. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String) enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D }
  148. 148. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String) enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D } sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success<out T>(val item: T) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() }
  149. 149. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String) enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D } sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success<out T>(val item: T) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() } Kotlin만 있으면 된다.
  150. 150. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String) enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D } sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success<out T>(val item: T) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() } 간단하다.
  151. 151. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String) enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D } sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success<out T>(val item: T) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() } 간단하다.(외부 프레임웍을 사용하는 것보다)
  152. 152. 이렇게 하면 뭐가 좋나 요? enum class ItemType { ITEM_A, ITEM_B, ITEM_C, ITEM_D } sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success<out T>(val item: T) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() } 상태에 SideEffect가 없다.
  153. 153. 끝?
  154. 154. 2개 이상의 중복상태는?
  155. 155. 2개 이상의 중복상태는? sealed class MultiNetworkState<out A, out B> { class Init : MultiNetworkState<Nothing, Nothing>() class Loading : MultiNetworkState<Nothing, Nothing>() class Success<out A, out B>(val itemA: A, val itemB: B) : MultiNetworkState<A, B>() class Error(val throwable: Throwable?) : MultiNetworkState<Nothing, Nothing>() }
  156. 156. 2개 이상의 중복상태는? private var multiNetworkState: MultiNetworkState<Item, Item>by Delegates.observable(MultiNetworkState.Init()) { _: KProperty<*>, _: MultiNetworkState<Item, Item>, newState: MultiNetworkState<Item, Item> -> when (newState) { is MultiNetworkState.Init -> view.hideProgress() is MultiNetworkState.Loading -> view.showProgress() is MultiNetworkState.Success<Item, Item> -> { view.onUpdatedItem(newState.itemA, newState.itemB) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } is MultiNetworkState.Error -> { compositeDisposable.clear() view.onError(newState.throwable) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } } }
  157. 157. 2개 이상의 중복상태는? private var multiNetworkState: MultiNetworkState<Item, Item>by Delegates.observable(MultiNetworkState.Init()) { _: KProperty<*>, _: MultiNetworkState<Item, Item>, newState: MultiNetworkState<Item, Item> -> when (newState) { is MultiNetworkState.Init -> view.hideProgress() is MultiNetworkState.Loading -> view.showProgress() is MultiNetworkState.Success<Item, Item> -> { view.onUpdatedItem(newState.itemA, newState.itemB) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } is MultiNetworkState.Error -> { compositeDisposable.clear() view.onError(newState.throwable) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } } }
  158. 158. 2개 이상의 중복상태는? private var multiNetworkState: MultiNetworkState<Item, Item>by Delegates.observable(MultiNetworkState.Init()) { _: KProperty<*>, _: MultiNetworkState<Item, Item>, newState: MultiNetworkState<Item, Item> -> when (newState) { is MultiNetworkState.Init -> view.hideProgress() is MultiNetworkState.Loading -> view.showProgress() is MultiNetworkState.Success<Item, Item> -> { view.onUpdatedItem(newState.itemA, newState.itemB) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } is MultiNetworkState.Error -> { compositeDisposable.clear() view.onError(newState.throwable) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } } }
  159. 159. 2개 이상의 중복상태는? private var multiNetworkState: MultiNetworkState<Item, Item>by Delegates.observable(MultiNetworkState.Init()) { _: KProperty<*>, _: MultiNetworkState<Item, Item>, newState: MultiNetworkState<Item, Item> -> when (newState) { is MultiNetworkState.Init -> view.hideProgress() is MultiNetworkState.Loading -> view.showProgress() is MultiNetworkState.Success<Item, Item> -> { view.onUpdatedItem(newState.itemA, newState.itemB) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } is MultiNetworkState.Error -> { compositeDisposable.clear() view.onError(newState.throwable) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } } }
  160. 160. 2개 이상의 중복상태는? private var multiNetworkState: MultiNetworkState<Item, Item>by Delegates.observable(MultiNetworkState.Init()) { _: KProperty<*>, _: MultiNetworkState<Item, Item>, newState: MultiNetworkState<Item, Item> -> when (newState) { is MultiNetworkState.Init -> view.hideProgress() is MultiNetworkState.Loading -> view.showProgress() is MultiNetworkState.Success<Item, Item> -> { view.onUpdatedItem(newState.itemA, newState.itemB) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } is MultiNetworkState.Error -> { compositeDisposable.clear() view.onError(newState.throwable) currentNetworkState = currentNetworkState.copy(NetworkState.Init(), NetworkState.Init()) } } }
  161. 161. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  162. 162. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  163. 163. 2개 이상의 중복상태는? Init I I Loading I L Loading I S Error I E Loading L I Loading L L Loading L S Error L E Loading S I Loading S L Success S S Error S E Error E I Error E L Error E S Error E E
  164. 164. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  165. 165. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  166. 166. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  167. 167. 2개 이상의 중복상태는? data class NewNetworkState<out A, out B>(val state1: NetworkState<A>, val state2: NetworkState<B>) { fun getMultiState(): MultiNetworkState<A, B> = when { state1 is NetworkState.Error -> MultiNetworkState.Error(state1.throwable) state2 is NetworkState.Error -> MultiNetworkState.Error(state2.throwable) state1 is NetworkState.Loading || state2 is NetworkState.Loading -> MultiNetworkState.Loading() state1 is NetworkState.Init && state2 is NetworkState.Success -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Init -> MultiNetworkState.Loading() state1 is NetworkState.Success && state2 is NetworkState.Success -> MultiNetworkState.Success(state1.item, state2.item) else -> MultiNetworkState.Init() } }
  168. 168. 2개 이상의 중복상태는? class NetworkStateTest { private val item = Item("kotlin", "#FFFFFF") }
  169. 169. 2개 이상의 중복상태는? @Test fun state1IsInitTest() { assertEquals(MultiNetworkState.Init(), NewNetworkState(NetworkState.Init(), NetworkState.Init()).getMultiState()) assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Init(), NetworkState.Loading()).getMultiState()) assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Init(), NetworkState.Success(item)).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Init(), NetworkState.Error(NullPointerException())).getMultiState()) }
  170. 170. 2개 이상의 중복상태는? @Test fun state1IsLoadingTest() { assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Loading(), NetworkState.Init()).getMultiState()) assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Loading(), NetworkState.Loading()).getMultiState()) assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Loading(), NetworkState.Success(item)).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Loading(), NetworkState.Error(NullPointerException())).getMultiState()) }
  171. 171. 2개 이상의 중복상태는? @Test fun state1IsSuccessTest() { assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Success(item), NetworkState.Init()).getMultiState()) assertEquals(MultiNetworkState.Loading(), NewNetworkState(NetworkState.Success(item), NetworkState.Loading()).getMultiState()) assertEquals(MultiNetworkState.Success(item, item), NewNetworkState(NetworkState.Success(item), NetworkState.Success(item)).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Success(item), NetworkState.Error(NullPointerException())).getMultiState()) }
  172. 172. 2개 이상의 중복상태는? @Test fun state1IsErrorTest() { assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Init()).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Loading()).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Success(item)).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Error(NullPointerException())).getMultiState()) }
  173. 173. 2개 이상의 중복상태는? @Test fun state1IsErrorTest() { assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Init()).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Loading()).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Success(item)).getMultiState()) assertEquals(MultiNetworkState.Error(NullPointerException()), NewNetworkState(NetworkState.Error(NullPointerException()), NetworkState.Error(NullPointerException())).getMultiState()) } Test가 용이하다.
  174. 174. 2개 이상의 중복상태는? private var currentNetworkState: NewNetworkState<Item, Item> by Delegates.observable(NewNetworkState(NetworkState.Init(), NetworkState.Init())) { _: KProperty<*>, _: NewNetworkState<Item, Item>, newState: NewNetworkState<Item, Item> -> multiNetworkState = newState.getMultiState() }
  175. 175. 2개 이상의 중복상태는? private var currentNetworkState: NewNetworkState<Item, Item> by Delegates.observable(NewNetworkState(NetworkState.Init(), NetworkState.Init())) { _: KProperty<*>, _: NewNetworkState<Item, Item>, newState: NewNetworkState<Item, Item> -> multiNetworkState = newState.getMultiState() }
  176. 176. 2개 이상의 중복상태는? private var state1: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> currentNetworkState = currentNetworkState.copy(state1 = newState) }) private var state2: NetworkState<Item>by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> currentNetworkState = currentNetworkState.copy(state2 = newState) })
  177. 177. 2개 이상의 중복상태는? private var state1: NetworkState<Item> by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> currentNetworkState = currentNetworkState.copy(state1 = newState) }) private var state2: NetworkState<Item>by Delegates.observable(NetworkState.Init(), { _: KProperty<*>, _: NetworkState<Item>, newState: NetworkState<Item> -> currentNetworkState = currentNetworkState.copy(state2 = newState) })
  178. 178. 정리 • observable 을 사용 했기 때문에 상태가 변경되면 자동으로 UI가 갱 신된다. • observable 을 사용 했기 때문에 중복상태 처리가 없다. • enum class, sealed class 생성 시점에 모든 상태를 사전에 알수 있 다. • 런타임이 아닌 컴파일 시점에 상태값 처리 검증이 가능하다. • 상태에 대한 SideEffect가 없다. • 테스트코드 작성이 유리하다. • 비즈니스 로직과 상태값 관리, 상태에따른 UI변화 2영역이 분리된 다.
  179. 179. QnA https://github.com/myeonginwoo/DK_SAMPLE om/myeonginwoo/droidknight-2018-umyeongin-selaed-classreul-sayongh https://www.facebook.com/groups/1189616354467814/?ref=bookmarks https://github.com/funfunStudy/study/wiki myeonginwoo@gmail.com https://medium.com/@lazysoul
  • joobn

    Aug. 11, 2018
  • ssuserdea915

    Aug. 4, 2018
  • Blueffect

    Jul. 26, 2018
  • ssuseraa2e15

    May. 21, 2018
  • iolo

    Apr. 23, 2018

Views

Total views

9,570

On Slideshare

0

From embeds

0

Number of embeds

8,508

Actions

Downloads

21

Shares

0

Comments

0

Likes

5

×