SlideShare una empresa de Scribd logo
1 de 179
Kotlin Sealed Class를 이용한
뷰상태 관리
Speaker.우명인
우명인
Index
• 뷰 상태?
• 상태가 왜 중요한데?
• 어떻게 개선하는데?
• 지속적인 개선
뷰 상태?
상태
뷰 상태?
상태
뷰 상태?
상태
뷰 상태?
뷰 상태?
• 화면에 보여지는 것들은 상태가 있다고 말할 수 있다.
• 화면에 보여지는 것들은 상태가 있다고 말할 수 있다.
• 상태란 상황에 따라 변경 될수 있는 값을 말한다.
뷰 상태?
• 화면에 보여지는 것들은 상태가 있다고 말할 수 있다.
• 상태란 상황에 따라 변경 될수 있는 값을 말한다.
• Android는 상태값을 화면에 보여주는 프로그램이다.
뷰 상태?
그래서 상태가 왜 중요한데???
상태가 왜 중요한데?
상태가 왜 중요한데?
의도 하지 않은 유저경험 제공
상태가 왜 중요한데?
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")
})
}
상태가 왜 중요한데?
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")
})
}
상태가 왜 중요한데?
화면이 복잡해짐 ->
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")
})
}
상태가 왜 중요한데?
화면이 복잡해짐 -> 상태가 많아짐 ->
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")
})
}
상태가 왜 중요한데?
화면이 복잡해짐 -> 상태가 많아짐 ->
관리가 어려워짐 ->
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가 발생하기 쉬움
상태가 왜 중요한데?
crash 발생
상태가 왜 중요한데?
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”)
})
상태가 왜 중요한데?
이게 어때서?
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
}
}
이게 어때서?
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
}
}
이게 어때서?
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가 상태를 관리
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가 상태를 관리
상태를 상수 값으로 관리
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”)
})
이게 어때서?
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”)
})
이게 어때서?
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가 상태를 분기 해서 처리
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가 발생 할 수 있다.
ItemD가 추가된다면?
ItemD가 추가된다면?
companion object {
const val ITEM_A = 1
const val ITEM_B = 2
const val ITEM_C = 3
}
companion object {
const val ITEM_A = 1
const val ITEM_B = 2
const val ITEM_C = 3
const val ITEM_D = 4
}
ItemD가 추가된다면?
ItemD가 추가된다면?
bt_d.setOnClickListener {
updateItem(ITEM_D)
currentItem = ITEM_D
}
ItemD가 추가된다면?
ItemD가 추가된다면?
ItemD가 추가된다면?
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가 추가된다면?
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가 추가된다면?
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가 추가된다면?
ItemN 이 계속 추가된다면?
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
}
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")
})
• 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없
ItemN 이 계속 추가된다
면?
ItemN 이 계속 추가된다
면?
• 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없
• 런타임에 에러가 발생 할 수가 있다.
• 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없
• 런타임에 에러가 발생 할 수가 있다.
• ITEM_N 의 값 1, 2, 3, N은 의미가 없다.
ItemN 이 계속 추가된다
면?
• 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없
• 런타임에 에러가 발생 할 수가 있다.
• ITEM_N 의 값 1, 2, 3, N은 의미가 없다.
• 중복한 N이 작성 될수도 있다.
ItemN 이 계속 추가된다
면?
• 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없
• 런타임에 에러가 발생 할 수가 있다.
• ITEM_N 의 값 1, 2, 3, N은 의미가 없다.
• 중복한 N이 작성 될수도 있다.
• 요구 사항이 늘어 날 때마다 코드 전체가 영향을 받는다.
ItemN 이 계속 추가된다
면?
어떻게 개선하는데?
어떻게 개선하는데?
MVP
어떻게 개선하는데?
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”)
})
}
어떻게 개선하는데?
interface MainView {
fun onUpdatedItem(oldItem: Int, newItem: Int)
}
어떻게 개선하는데?
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
}
}
어떻게 개선하는데?
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”)
})
}
어떻게 개선하는데?
뭐가 개선 됐는데?
뭐가 개선 됐는데?
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가 관리한다.
뭐가 개선 됐는데?
조금 더 개선해보자.
조금 더 개선해보자.
companion object {
const val ITEM_A = 1
const val ITEM_B = 2
const val ITEM_C = 3
}
조금 더 개선해보자.
enum class Item {
ITEM_A, ITEM_B, ITEM_C
}
조금 더 개선해보자.
enum class Item {
ITEM_A, ITEM_B, ITEM_C
}
애매모호한 상수에서 명시적인 class로 변경
조금 더 개선해보자.
enum class Item {
ITEM_A, ITEM_B, ITEM_C
}
애매모호한 상수에서 명시적인 class로 변경
설계 시 모든 상태를 알고 있다.
조금 더 개선해보자.
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”)
})
조금 더 개선해보자.
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")
})
조금 더 개선해보자.
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")
})
ItemD가 추가된다면?
ItemD가 추가된다면?
enum class Item {
ITEM_A, ITEM_B, ITEM_C
}
ItemD가 추가된다면?
enum class Item {
ITEM_A, ITEM_B, ITEM_C, ITEM_D
}
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
})
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
})
컴파일 타임에 에러가 발생
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
})
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로 변경
설계 시 모든 상태를 알고 있다.
값만 추가 할 경우컴파일 타임에 에러가 발생.
조금 더 개선해보자.
조금 더 개선해보자.
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)
}
조금 더 개선해보자.
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)
}
생성 시점에 상태가 가진 값을 알수 있다.
조금 더 개선해보자.
interface MainView {
fun onUpdatedItem(oldItem: Int, newItem: Int)
}
조금 더 개선해보자.
interface MainView {
fun onUpdatedItem(oldItem: Item, newItem: Item)
}
조금 더 개선해보자.
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
}
}
조금 더 개선해보자.
class MainPresenter(val view: MainView) {
private var currentItem: Item = Item.ITEM_A
fun onClicked(item: Item) {
view.onUpdatedItem(currentItem, item)
currentItem = item
}
}
조금 더 개선해보자.
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")
})
}
조금 더 개선해보자.
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))
}
}
조금 더 개선해보자.
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가 상태에 따른 처리를 하지 않아도 된다.
ItemD가 추가된다면?
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)
}
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)
}
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)
}
}
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)
}
}
ItemN 이 계속 추가된다면?
• enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다.
ItemN 이 계속 추가된다
면?
• enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다.
• Presenter(비즈니스 로직)는 변하지 않는다.
ItemN 이 계속 추가된다
면?
• enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다.
• Presenter(비즈니스 로직)는 변하지 않는다.
• UI 업데이트 코드도 변하지 않는다.
ItemN 이 계속 추가된다
면?
조금 더 개선해보자.
MVVM
조금 더 개선해보자.
조금 더 개선해보자.
class MainPresenter(val view: MainView) {
private var currentItem: Item = Item.ITEM_A
fun onClicked(item: Item) {
view.onUpdatedItem(currentItem, item)
currentItem = 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
}
}
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
}
}
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)
}
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()
}
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
}
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
}
조금 더 개선해보자.
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
}
}
조금 더 개선해보자.
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가 바뀜
DataStore 에서
Item 정보를 가져온다면?
DataStore 에서
Item 정보를 가져온다면?
interface MainView {
fun onUpdatedItem(oldItem: Item, newItem: Item)
}
DataStore 에서
Item 정보를 가져온다면?
interface MainView {
fun onUpdatedItem(oldItem: Item, newItem: Item)
fun showProgress()
fun hideProgress()
fun onError(throwable: Throwable?)
}
DataStore 에서
Item 정보를 가져온다면?
enum class ItemType {
ITEM_A, ITEM_B, ITEM_C
}
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))
}
}
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.
}
}
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)
}
DataStore 에서
Item 정보를 가져온다면?
data class Item(val title: String, val color: String)
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
}
}
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()
}
}
}
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를 업데이트하네?
조금 더 개선해보자.
조금 더 개선해보자.
sealed class NetworkState {
class Init : NetworkState()
class Loading : NetworkState()
class Success(val item: Item) : NetworkState()
class Error(val throwable: Throwable?) : NetworkState()
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
조금 더 개선해보자.
sealed class NetworkState {
class Init : NetworkState()
class Loading : NetworkState()
class Success(val item: Item) : NetworkState()
class Error(val throwable: Throwable?) : NetworkState()
}
조금 더 개선해보자.
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>()
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.
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()
}
}
}
조금 더 개선해보자.(Rx)
조금 더 개선해보자.(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()
}
}
}
조금 더 개선해보자.(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)
}
}
}
조금 더 개선해보자.(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)
}
}
}
조금 더 개선해보자.(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)
}
}
}
조금 더 개선해보자.(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)
}
}
}
조금 더 개선해보자.(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)
}
}
}
이렇게 하면 뭐가 좋나
요?
이렇게 하면 뭐가 좋나
요?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)
}
}
}
이렇게 하면 뭐가 좋나
요?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 관련 코드
이렇게 하면 뭐가 좋나
요?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)
}
}
}
상태가 한눈에 보인다.
이렇게 하면 뭐가 좋나
요?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가 명확해진다.
이렇게 하면 뭐가 좋나
요?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)
}
}
}
비즈니스 로직 코드
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)
}
}
}
이렇게 하면 뭐가 좋나
요?
비즈니스 로직 과 상태 관리만 집중
이렇게 하면 뭐가 좋나
요?
implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”
이렇게 하면 뭐가 좋나
요?
implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”
… by Delegates.observable(…)
이렇게 하면 뭐가 좋나
요?
implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”
… by Delegates.observable(…)
data class Item(val title: String, val color: String)
이렇게 하면 뭐가 좋나
요?
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
}
이렇게 하면 뭐가 좋나
요?
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()
}
이렇게 하면 뭐가 좋나
요?
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만 있으면 된다.
이렇게 하면 뭐가 좋나
요?
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()
}
간단하다.
이렇게 하면 뭐가 좋나
요?
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()
}
간단하다.(외부 프레임웍을 사용하는 것보다)
이렇게 하면 뭐가 좋나
요?
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가 없다.
끝?
2개 이상의 중복상태는?
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>()
}
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())
}
}
}
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())
}
}
}
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())
}
}
}
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())
}
}
}
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())
}
}
}
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()
}
}
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()
}
}
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
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()
}
}
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()
}
}
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()
}
}
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()
}
}
2개 이상의 중복상태는?
class NetworkStateTest {
private val item = Item("kotlin", "#FFFFFF")
}
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())
}
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())
}
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())
}
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())
}
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가 용이하다.
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()
}
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()
}
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)
})
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)
})
정리
• observable 을 사용 했기 때문에 상태가 변경되면 자동으로 UI가 갱
신된다.
• observable 을 사용 했기 때문에 중복상태 처리가 없다.
• enum class, sealed class 생성 시점에 모든 상태를 사전에 알수 있
다.
• 런타임이 아닌 컴파일 시점에 상태값 처리 검증이 가능하다.
• 상태에 대한 SideEffect가 없다.
• 테스트코드 작성이 유리하다.
• 비즈니스 로직과 상태값 관리, 상태에따른 UI변화 2영역이 분리된
다.
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

Más contenido relacionado

La actualidad más candente

How to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI ComponentsHow to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI Componentscagataycivici
 
The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212Mahmoud Samir Fayed
 
How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF Luc Bors
 
Programmation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptLoïc Knuchel
 
Scala meetup
Scala meetupScala meetup
Scala meetup扬 明
 
Kotlin Mullets
Kotlin MulletsKotlin Mullets
Kotlin MulletsJames Ward
 
Saving Gaia with GeoDjango
Saving Gaia with GeoDjangoSaving Gaia with GeoDjango
Saving Gaia with GeoDjangoCalvin Cheng
 
Django101 geodjango
Django101 geodjangoDjango101 geodjango
Django101 geodjangoCalvin Cheng
 
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...DrupalCamp MSK
 
Vuexと入力フォーム
Vuexと入力フォームVuexと入力フォーム
Vuexと入力フォームJoe_noh
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wildJoe Morgan
 
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏Masahiro Akita
 
React Performance
React PerformanceReact Performance
React PerformanceMax Kudla
 
Android App Development - 05 Action bar
Android App Development - 05 Action barAndroid App Development - 05 Action bar
Android App Development - 05 Action barDiego Grancini
 
Beyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsBeyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsRebecca Murphey
 
PyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management toolPyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management toolCrea Very
 
Optimization in django orm
Optimization in django ormOptimization in django orm
Optimization in django ormDenys Levchenko
 
React Back to the Future
React Back to the FutureReact Back to the Future
React Back to the Future500Tech
 
Youth Tobacco Survey Analysis
Youth Tobacco Survey AnalysisYouth Tobacco Survey Analysis
Youth Tobacco Survey AnalysisRoshik Ganesan
 

La actualidad más candente (20)

How to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI ComponentsHow to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI Components
 
The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212
 
How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF
 
Programmation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScript
 
Scala meetup
Scala meetupScala meetup
Scala meetup
 
Kotlin Mullets
Kotlin MulletsKotlin Mullets
Kotlin Mullets
 
Saving Gaia with GeoDjango
Saving Gaia with GeoDjangoSaving Gaia with GeoDjango
Saving Gaia with GeoDjango
 
Django101 geodjango
Django101 geodjangoDjango101 geodjango
Django101 geodjango
 
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...
Drupal in aerospace - selling geodetic satellite data with Commerce - Martin ...
 
Vuexと入力フォーム
Vuexと入力フォームVuexと入力フォーム
Vuexと入力フォーム
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
 
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏
CakePHPをさらにDRYにする、ドライケーキレシピ akiyan.com 秋田真宏
 
React Performance
React PerformanceReact Performance
React Performance
 
Android App Development - 05 Action bar
Android App Development - 05 Action barAndroid App Development - 05 Action bar
Android App Development - 05 Action bar
 
Paging Like A Pro
Paging Like A ProPaging Like A Pro
Paging Like A Pro
 
Beyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsBeyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS Apps
 
PyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management toolPyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management tool
 
Optimization in django orm
Optimization in django ormOptimization in django orm
Optimization in django orm
 
React Back to the Future
React Back to the FutureReact Back to the Future
React Back to the Future
 
Youth Tobacco Survey Analysis
Youth Tobacco Survey AnalysisYouth Tobacco Survey Analysis
Youth Tobacco Survey Analysis
 

Similar a DroidKnight 2018 State machine by Selaed class

How Reactive do we need to be
How Reactive do we need to beHow Reactive do we need to be
How Reactive do we need to beJana Karceska
 
UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기NAVER SHOPPING
 
Immutable Libraries for React
Immutable Libraries for ReactImmutable Libraries for React
Immutable Libraries for Reactstbaechler
 
Recompacting your react application
Recompacting your react applicationRecompacting your react application
Recompacting your react applicationGreg Bergé
 
Strategies for Mitigating Complexity in React Based Redux Applicaitons
Strategies for Mitigating Complexity in React Based Redux ApplicaitonsStrategies for Mitigating Complexity in React Based Redux Applicaitons
Strategies for Mitigating Complexity in React Based Redux Applicaitonsgarbles
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Robert DeLuca
 
안드로이드 세미나 2
안드로이드 세미나 2안드로이드 세미나 2
안드로이드 세미나 2Chul Ju Hong
 
안드로이드 세미나 2
안드로이드 세미나 2안드로이드 세미나 2
안드로이드 세미나 2ang0123dev
 
Higher-Order Components — Ilya Gelman
Higher-Order Components — Ilya GelmanHigher-Order Components — Ilya Gelman
Higher-Order Components — Ilya Gelman500Tech
 
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019Codemotion
 
20180721 code defragment
20180721 code defragment20180721 code defragment
20180721 code defragmentChiwon Song
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applicationsGabor Varadi
 
N Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeN Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeAnton Kulyk
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to HooksSoluto
 
MVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayMVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayZeyad Gasser
 

Similar a DroidKnight 2018 State machine by Selaed class (20)

How Reactive do we need to be
How Reactive do we need to beHow Reactive do we need to be
How Reactive do we need to be
 
UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기
 
Immutable Libraries for React
Immutable Libraries for ReactImmutable Libraries for React
Immutable Libraries for React
 
compose_speaker_session.pdf
compose_speaker_session.pdfcompose_speaker_session.pdf
compose_speaker_session.pdf
 
Recompacting your react application
Recompacting your react applicationRecompacting your react application
Recompacting your react application
 
Strategies for Mitigating Complexity in React Based Redux Applicaitons
Strategies for Mitigating Complexity in React Based Redux ApplicaitonsStrategies for Mitigating Complexity in React Based Redux Applicaitons
Strategies for Mitigating Complexity in React Based Redux Applicaitons
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
 
Scala on Your Phone
Scala on Your PhoneScala on Your Phone
Scala on Your Phone
 
React 101
React 101React 101
React 101
 
안드로이드 세미나 2
안드로이드 세미나 2안드로이드 세미나 2
안드로이드 세미나 2
 
안드로이드 세미나 2
안드로이드 세미나 2안드로이드 세미나 2
안드로이드 세미나 2
 
Higher-Order Components — Ilya Gelman
Higher-Order Components — Ilya GelmanHigher-Order Components — Ilya Gelman
Higher-Order Components — Ilya Gelman
 
React hooks
React hooksReact hooks
React hooks
 
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019
Matteo Antony Mistretta - React, the Inglorious way - Codemotion Amsterdam 2019
 
20180721 code defragment
20180721 code defragment20180721 code defragment
20180721 code defragment
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applications
 
N Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeN Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React Native
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to Hooks
 
MVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayMVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin Way
 

Más de Myeongin Woo

Más de Myeongin Woo (10)

Goodbye null
Goodbye nullGoodbye null
Goodbye null
 
Fp basic-kotlin
Fp basic-kotlinFp basic-kotlin
Fp basic-kotlin
 
Lezhin kotlin jetbrain
Lezhin kotlin jetbrainLezhin kotlin jetbrain
Lezhin kotlin jetbrain
 
Kotlin collections
Kotlin collectionsKotlin collections
Kotlin collections
 
Kotlin standard
Kotlin standardKotlin standard
Kotlin standard
 
Kotlin class
Kotlin classKotlin class
Kotlin class
 
Kotlin expression
Kotlin expressionKotlin expression
Kotlin expression
 
Kotlin with fp
Kotlin with fpKotlin with fp
Kotlin with fp
 
토이 프로젝트를 하자.Pptx
토이 프로젝트를 하자.Pptx토이 프로젝트를 하자.Pptx
토이 프로젝트를 하자.Pptx
 
Kotlin.md
Kotlin.mdKotlin.md
Kotlin.md
 

Último

Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceanilsa9823
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 

Último (20)

Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 

DroidKnight 2018 State machine by Selaed class

  • 1. Kotlin Sealed Class를 이용한 뷰상태 관리 Speaker.우명인
  • 3. Index • 뷰 상태? • 상태가 왜 중요한데? • 어떻게 개선하는데? • 지속적인 개선
  • 8. 뷰 상태? • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다.
  • 9. • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다. • 상태란 상황에 따라 변경 될수 있는 값을 말한다. 뷰 상태?
  • 10. • 화면에 보여지는 것들은 상태가 있다고 말할 수 있다. • 상태란 상황에 따라 변경 될수 있는 값을 말한다. • Android는 상태값을 화면에 보여주는 프로그램이다. 뷰 상태?
  • 11. 그래서 상태가 왜 중요한데???
  • 14. 의도 하지 않은 유저경험 제공 상태가 왜 중요한데?
  • 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. 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. 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. 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. 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가 발생하기 쉬움
  • 21. crash 발생 상태가 왜 중요한데?
  • 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. 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”) }) 상태가 왜 중요한데?
  • 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. 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. 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. 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. 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. 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. 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. 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가 발생 할 수 있다.
  • 34. ItemD가 추가된다면? companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 }
  • 35. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 const val ITEM_D = 4 } ItemD가 추가된다면?
  • 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. 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. 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. ItemN 이 계속 추가된다면?
  • 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. 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. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 ItemN 이 계속 추가된다 면?
  • 47. ItemN 이 계속 추가된다 면? • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다.
  • 48. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. ItemN 이 계속 추가된다 면?
  • 49. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. • 중복한 N이 작성 될수도 있다. ItemN 이 계속 추가된다 면?
  • 50. • 상태만 추가하고 비즈니스 로직을 구현하지 않아도 컴파일에 문제가 없 • 런타임에 에러가 발생 할 수가 있다. • ITEM_N 의 값 1, 2, 3, N은 의미가 없다. • 중복한 N이 작성 될수도 있다. • 요구 사항이 늘어 날 때마다 코드 전체가 영향을 받는다. ItemN 이 계속 추가된다 면?
  • 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. interface MainView { fun onUpdatedItem(oldItem: Int, newItem: Int) } 어떻게 개선하는데?
  • 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. 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”) }) } 어떻게 개선하는데?
  • 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가 관리한다.
  • 62. 조금 더 개선해보자. companion object { const val ITEM_A = 1 const val ITEM_B = 2 const val ITEM_C = 3 }
  • 63. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C }
  • 64. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C } 애매모호한 상수에서 명시적인 class로 변경
  • 65. 조금 더 개선해보자. enum class Item { ITEM_A, ITEM_B, ITEM_C } 애매모호한 상수에서 명시적인 class로 변경 설계 시 모든 상태를 알고 있다.
  • 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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") })
  • 70. ItemD가 추가된다면? enum class Item { ITEM_A, ITEM_B, ITEM_C }
  • 71. ItemD가 추가된다면? enum class Item { ITEM_A, ITEM_B, ITEM_C, ITEM_D }
  • 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. 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. 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. 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로 변경 설계 시 모든 상태를 알고 있다. 값만 추가 할 경우컴파일 타임에 에러가 발생.
  • 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. interface MainView { fun onUpdatedItem(oldItem: Int, newItem: Int) }
  • 80. 조금 더 개선해보자. interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) }
  • 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. 조금 더 개선해보자. class MainPresenter(val view: MainView) { private var currentItem: Item = Item.ITEM_A fun onClicked(item: Item) { view.onUpdatedItem(currentItem, item) currentItem = item } }
  • 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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가 상태에 따른 처리를 하지 않아도 된다.
  • 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. 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. 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. 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. ItemN 이 계속 추가된다면?
  • 92. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. ItemN 이 계속 추가된다 면?
  • 93. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. • Presenter(비즈니스 로직)는 변하지 않는다. ItemN 이 계속 추가된다 면?
  • 94. • enum class 추가와, 상태값 전달 코드만 추가구현 하면 된다. • Presenter(비즈니스 로직)는 변하지 않는다. • UI 업데이트 코드도 변하지 않는다. ItemN 이 계속 추가된다 면?
  • 97. 조금 더 개선해보자. class MainPresenter(val view: MainView) { private var currentItem: Item = Item.ITEM_A fun onClicked(item: Item) { view.onUpdatedItem(currentItem, item) currentItem = item } }
  • 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. 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. 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. 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. 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. 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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. DataStore 에서 Item 정보를 가져온다면?
  • 107. DataStore 에서 Item 정보를 가져온다면? interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) }
  • 108. DataStore 에서 Item 정보를 가져온다면? interface MainView { fun onUpdatedItem(oldItem: Item, newItem: Item) fun showProgress() fun hideProgress() fun onError(throwable: Throwable?) }
  • 109. DataStore 에서 Item 정보를 가져온다면? enum class ItemType { ITEM_A, ITEM_B, ITEM_C }
  • 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. 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. 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. DataStore 에서 Item 정보를 가져온다면? data class Item(val title: String, val color: String)
  • 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. 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. 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를 업데이트하네?
  • 118. 조금 더 개선해보자. sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success(val item: Item) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() }
  • 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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() } } }
  • 126. 조금 더 개선해보자. sealed class NetworkState { class Init : NetworkState() class Loading : NetworkState() class Success(val item: Item) : NetworkState() class Error(val throwable: Throwable?) : NetworkState() }
  • 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. 조금 더 개선해보자. 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. 조금 더 개선해보자. 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() } } }
  • 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. 조금 더 개선해보자.(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. 조금 더 개선해보자.(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. 조금 더 개선해보자.(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. 조금 더 개선해보자.(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. 조금 더 개선해보자.(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. 이렇게 하면 뭐가 좋나 요?
  • 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. 이렇게 하면 뭐가 좋나 요?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. 이렇게 하면 뭐가 좋나 요?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. 이렇게 하면 뭐가 좋나 요?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. 이렇게 하면 뭐가 좋나 요?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. 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. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”
  • 145. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…)
  • 146. 이렇게 하면 뭐가 좋나 요? implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version” … by Delegates.observable(…) data class Item(val title: String, val color: String)
  • 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. 이렇게 하면 뭐가 좋나 요? 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. 이렇게 하면 뭐가 좋나 요? 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. 이렇게 하면 뭐가 좋나 요? 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. 이렇게 하면 뭐가 좋나 요? 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. 이렇게 하면 뭐가 좋나 요? 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. 끝?
  • 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. 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. 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. 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. 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. 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. 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. 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() } }
  • 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. 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. 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. 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. 2개 이상의 중복상태는? class NetworkStateTest { private val item = Item("kotlin", "#FFFFFF") }
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 정리 • observable 을 사용 했기 때문에 상태가 변경되면 자동으로 UI가 갱 신된다. • observable 을 사용 했기 때문에 중복상태 처리가 없다. • enum class, sealed class 생성 시점에 모든 상태를 사전에 알수 있 다. • 런타임이 아닌 컴파일 시점에 상태값 처리 검증이 가능하다. • 상태에 대한 SideEffect가 없다. • 테스트코드 작성이 유리하다. • 비즈니스 로직과 상태값 관리, 상태에따른 UI변화 2영역이 분리된 다.