The document discusses view state management in Android. It explains that as views become more complex, the number of states increases, making state management difficult. It introduces sealed classes in Kotlin as a way to better organize view states and prevent issues like crashes from invalid states. Adding new states easily without errors is discussed as an advantage of using sealed classes.
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
}
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 이 계속 추가된다
면?
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가 관리한다.
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)
}
생성 시점에 상태가 가진 값을 알수 있다.
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
}
}
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가 상태에 따른 처리를 하지 않아도 된다.
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가 바뀜
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?)
}
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()
}
}
}
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가 없다.
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영역이 분리된
다.