들어가기 전
현재 나는 부모님과의 문답 서비스 엄빠도 어렸다 라는 프로젝트를 진행하고 있다.
프로젝트 진행 중 서버로부터 받아온 데이터가 UI에 나타날 때 빈 화면(데이터가 덜 표시된 화면)이 노출되는 경우가 종종 발생했다.
이 부분을 해결하고자 로딩뷰를 추가하기로 하였고, 공부하면서 로딩뷰를 어떻게 관리할까? 라는 의문이 생겼다.
그 과정에서 활용을 위해 UI State가 무엇인지에 관련된 글을 작성해보겠다.
UI Layer
UI의 역할은 화면에 애플리케이션 데이터를 표시하고 사용자의 상호작용의 기본 지점으로서의 역할을 수행하는 것이다.
상호작용 (Button 누르기) 또는 외부압력 (네트워크 응답)으로 인해 데이터가 변할 때 마다 변경사항이 반영하도록 UI가 업데이트 되어야 한다.
https://hyeonlog-developer.tistory.com/124
[그림 1]와 같은 과정을 통해 UI Layer는 data layer로 부터 데이터를 가져올 수 있다. 관련된 더 자세한 설명은 위 포스팅을 참고하길 바란다.
따라서 UI Layer에서는 다음과 같은 단계를 실행해야한다.
- 앱 데이터를 사용하고 UI에서 쉽게 렌더링할 수 있는 데이터로 변환한다.
- UI 렌더링 가능 데이터를 사용하고 사용자에게 표시할 UI 요소로 변환한다.
- 이렇게 조합된 UI 요소의 사용자 입력 이벤트를 사용하고 입력 이벤트의 결과를 필요에 따라 UI 데이터에 반영한다.
- 1~3단계를 필요한 만큼 반복합니다.
이런 UI Layer의 기본적인 단계를 구현 및 수행하기 위해 알아야 할 UI State란 무엇일까?
UI State란?
게시글 목록이 있는 애플리케이션이 있다고 가정해보자.
DataLayer로부터 게시물에 관련된 데이터들을 받아오고 데이터들과 함께 게시물이 보여진다.
이 때 UI에 해당 게시물들이 보여지게 되는 것이고, 앱에서 사용자에게 표시하는 이 정보가 UI State이다.
공식문서에서는 아래와 같이 표현한다.
사용자가 보는 항목이 UI라면 UI State는 앱에서 사용자가 봐야 한다고 지정하는 항목입니다.
데이터를 가져오는 과정에서 나타날 수 있는 상태는 데이터를 가져오기 전 기본상태, 데이터를 가져오는 도중의 로딩 상태 또는 데이터를 가져오는데 실패했을 때 이렇게 있을 수 있다.
따라서 앱은 이런 UI State에 따라서 사용자에게 보이는 모습들을 달리 해주어야 한다.
State Modeling
안드로이드에서 UI State를 위해 sealed class를 활용한 State Modeling 전략이 이용된다.
sealed class란?
sealed class 는 자식 클래스의 종류를 제한하는 특성이 있어, 정의된 하위 클래스 외에 다른 하위클래스는 존재하지 않음을 컴파일로로 부터 알 수 있다.
sealed class DogState
class Running : DogState()
class Eating : DogState()
class Barking : DogState()
fun getStateMessage(dogState:DogState) : String {
return when(dogState) {
is Running -> "running"
is Eating -> "eating"
is Barking -> "barking"
}
}
위 코드와 같이 sealed class를 활용하면 when 문에서 else를 사용하지 않아도 오류가 생기지 않는다.
그 이유는 컴파일러에서 sealed class인 DogState의 자식클래스가 Running, Eating, Barking 만 존재함을 알고있기 때문이다.
그러므로 else branch를 사용하지 않고 필요한 메시지만 수신 할 수 있다.
더 자세한 sealed class에 관련된 내용은 아래 포스팅을 참고하면 된다.
https://hyeonlog-developer.tistory.com/130
sealed class를 활용해서 안드로이드에 UI State를 어떻게 사용할 수 있을까?
UI State 사용방법
UI State 클래스
sealed class UiState {
data class Success<T>(val data: T): UiState()
data class Error(val error: Throwable?): UiState()
object Loading: UiState()
}
ViewModel
private val _bookReport = MutableStateFlow<UiState<BookReport>>(UiState.Loading)
val bookReport: StateFlow<UiState<BookReport>> = _bookReport.asStateFlow()
fun getBookReportDetail(isbn: String) {
viewModelScope.launch {
bookReportRepository.getBookReportDetail(isbn)
.catch {
_bookReport.value = UiState.Error
}
.collect {
_bookReport.value = UiState.Success(it)
}
}
}
View
private fun setBookReport() {
collectStateFlow(bookReportDetailViewModel.bookReport) { uiState ->
when (uiState) {
is UiState.Loading -> {
binding.progressBar.isVisible = true
}
is UiState.Success -> {
binding.progressBar.isVisible = false
val bookReport = uiState.data
with(binding) {
// UI 설정
}
}
is UiState.Error -> {
binding.progressBar.isVisible = false
Toast.makeText(requireContext(), "데이터를 불러오지 못했습니다.", Toast.LENGTH_SHORT).show()
}
}
}
}
이렇게 UI State를 관리할 수 있다.
공부한 내용들을 바탕으로 엄빠도 어렸다 로딩뷰 페이지에 활용하고 관련 포스팅도 빠른 시일내에 올려야겠다.
배운 점 및 의문 점
UI State를 효율적으로 관리하는 방법에 대해 알게되었다. 그 과정에서 여러 예제들을 살펴보았는데,
UI State를 사용할 때 sealed class를 사용한 예제도 있고, sealed interface를 사용한 예제도 있어 두 부분의 차이가 무엇인지를 좀 더 공부해봐야겠다. 또한 UI State를 표현하는 다양한 방식들도 공부해봐야겠다.
Sealed Class와 Sealed Interface
참고 레퍼런스
'Android > 공부' 카테고리의 다른 글
[Android] 첫 안드로이드 앱 릴리즈 과정에서 겪은 트러블슈팅들 (0) | 2023.09.11 |
---|---|
[Android] LifecycleOwner란? viewLifecycleOwner와의 비교 (0) | 2023.08.24 |
[Android] Dependency Injection이란? + Hilt (1) | 2023.08.07 |
[Android] Clean Architecture + MVVM 패턴 (1) | 2023.08.06 |
[Android] 알림 커스텀과 안드로이드 13 알림 권한 설정 (0) | 2023.07.21 |