SlideShare uma empresa 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

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Semelhante a Kotlin Sealed 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
 

Semelhante a Kotlin Sealed 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
 

Mais de Myeongin Woo

Mais 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

OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingShane Coughlan
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfDrew Moseley
 
SAM Training Session - How to use EXCEL ?
SAM Training Session - How to use EXCEL ?SAM Training Session - How to use EXCEL ?
SAM Training Session - How to use EXCEL ?Alexandre Beguel
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsSafe Software
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...Bert Jan Schrijver
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringHironori Washizaki
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profileakrivarotava
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...OnePlan Solutions
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsJean Silva
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorTier1 app
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slidesvaideheekore1
 
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfRTS corp
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Rob Geurden
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 

Último (20)

OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdf
 
SAM Training Session - How to use EXCEL ?
SAM Training Session - How to use EXCEL ?SAM Training Session - How to use EXCEL ?
SAM Training Session - How to use EXCEL ?
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data Streams
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their Engineering
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profile
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...
Tech Tuesday Slides - Introduction to Project Management with OnePlan's Work ...
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero results
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryError
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slides
 
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 

Kotlin Sealed 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영역이 분리된 다.