Android) State 패턴을 통해서 UI 상태 관리하기
오늘은 제가 요즘 유용하게 사용하고 있는 State 패턴에 대해서 글을 작성해보려고 합니다.
State 패턴
- 객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의합니다.
- 각 상태 클래스들을 캡슐화하여, 클라이언트에서 호출하는 방식
- UI 상태관리를 Readable하게 하기 위한 방법 중 하나
이런 특징들로 회사에 출근하기 위한 State 패턴을 예로 만들어보자면 아래와 같은 상태로 나눌 수 있지 않을까 싶습니다.
각 상황에 따라 대처할 행위를 명시해 두는 것이죠. 그래면 해당 상황에 직면했을 때 대비하는 것이 쉬워집니다.
when (상태) {
is 출근.준비 -> {
씻기()
밥먹기()
}
is 출근.실패 -> {
늦잠()
회사에연락()
}
is 출근.성공 -> {
업무()
회의()
}
}
Usage
interface, enum class, sealed class 모두로 State 패턴을 구현할 수 있는데 sealed class로 구현하는 것을 추천드립니다.
sealed 클래스는 Child 클래스의 종류를 제한하는 특성이 있어, 정의된 하위 클래스 외에 다른 하위 클래스는 존재하지 않는다는 것을 컴파일러에게 알려주는 것과 같은 효과를 냅니다.
즉 when 문으로 모든 케이스에 대해서 처리가 되어야 하기 때문에 다른 클래스들은 else 구문이 들어가야 하지만,
sealed class는 컴파일 시점에 하위 클래스들이 정해져 있기 때문에 else 없이도 클래스들을 관리할 수 있습니다.
sealed class의 하위클래스는 class, data class, object 로 정의할 수 있습니다.
State Class
sealed class UiState {
object Loading : UiState()
object Empty : UiState()
data class Success(val list: List<FavoriteItem>) : UiState()
data class Error(val message: String?) : UiState()
}
위와 같이 State 클래스를 선언했습니다.
ViewModel
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
private fun getSomething() {
trendingRepository.getSomething()
.onStart {
_uiState.value = UiState.Loading
delay(300)
}
.catch { exception -> UiState.Error(exception.localizedMessage) }
.collect { _uiState.value = UiState.Success(it) }
}
그런 후에 뷰모델에서 위와 같이 사용할 수 있습니다. 저는 코루틴의 flow와 함께 사용했습니다.
View
private fun showSomething() {
viewModel.uiState.asLiveData().observe(viewLifecycleOwner, Observer {
when (it) {
is UiState.Loading -> {
showLoadingView()
hideRecyclerView()
}
is UiState.Empty -> {
hideLoadingView()
showEmptyText()
}
is UiState.Success -> {
hideLoadingView()
showRecyclerView()
adapter.submitList(it)
}
is UiState.Error -> {
hideLoadingView()
showErrorText(it.message.toString())
}
}
})
}
그런 후에 View(Fragment)에서 State를 나누어 해당 상태에 맞는 코드를 작성하면 됩니다.
State 패턴을 사용한다면 굉장히 직관적이고 가독성 있는 코드를 작성할 수 있지 않을까요?
State 패턴은 UI 상태가 복잡해질수록 직관적이고, Side Effect 적은 방식이기 때문에 이런 경우에 유용한 패턴이라고 생각합니다.