우리는 화면의 상태를 어떻게 구분하고 관리해야할까?
데이터가 없을때의 엠티뷰는 어떻게 구성하고 데이터를 불러오기까지의 로딩창은 어떻게 띄워야할까?
이를 위해서 UiState라는 개념을 알아야 한다
UiState란?
단어에서 유추할 수 있듯이, Ui + State = 즉 화면의 어떠한 상태를 나타낸다
먼저 공식문서에 나오는 Ui layer의 역할은 다음과 같다
UI의 역할은 화면에 애플리케이션 데이터를 표시하고 사용자 상호작용의 기본 지점으로도 기능하는 것입니다. 사용자 상호작용(예: 버튼 누르기) 또는 외부 입력(예: 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트되어야 합니다. 사실상 UI는 데이터 레이어에서 가져온 애플리케이션 상태를 시각적으로 나타냅니다.
현재 나는 다음과 같은 방법으로 UiState를 관리하고 있다
1. sealed class를 통해 UiState의 종류를 구분한다
필요한 화면의 type에 따라 다르겠지만 대게로 Success/Failure/ Loading 로 나뉜다
그리고 추가적으로 Empty 등의 type도 많이 사용된다
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
object Failure : UiState<Nothing>()
}
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
object Empty : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
object Failure : UiState<Nothing>()
}
2. 초기값으로 loading state를 할당한다
화면의 상태를 관리하는 state 폴더를 따로 생성하고 서버에서 받아오는 값을 UiSTate의 type으로 할당한다
data class DummyState (
val uiState: UiState<PersistentList<ResponseDummyDto>> = UiState.Loading
)
3. 뷰모델을 통해 네트워크 통신결과에 따라서 UiState의 값을 업데이트 해준다
만약 네트워크 통신이 성공했다면 기존 UiState의 값에 Success를 할당해주고,
만약 네트워크 통신이 실패했다면 Failure를 할당해준다.
이 외의 다른 state type을 선언했다면 다양한 로직을 통해 이를 업데이트 해주면 된다!
class DummyViewModel : ViewModel() {
...
private val _state = MutableStateFlow(DummyState())
val state: StateFlow<DummyState>
get() = _state.asStateFlow()
...
fun getMyJogboList() {
viewModelScope.launch {
runCatching {
twosomeService.getDummyLists()
}.onSuccess { dummyList ->
_state.value = _state.value.copy(
uiState = UiState.Success(
dummyList.data.toPersistentList()
)
)
}.onFailure { throwable ->
_state.value = _state.value.copy(
uiState = UiState.Failure
)
...
}
}
}
}
4. 마지막으로 화면을 그리는 역할을 하는 ui 코드에서 state의 값을 관찰해 type에 따라 다른 화면을 출력한다
when (state) {
is UiState.Loading -> {
item {
Text(
modifier = modifier
.noRippleClickable { navigateUp() },
textAlign = TextAlign.Center,
text = "로딩 중...",
fontSize = 30.sp
)
}
}
is UiState.Failure -> {
item {
Text(
modifier = modifier
.noRippleClickable { navigateUp() },
textAlign = TextAlign.Center,
text = "데이터를 불러오지 못했습니다.",
fontSize = 30.sp
)
}
}
is UiState.Success -> {
itemsIndexed(state.data) { index, item ->
DummyItem(
id = item.id,
email = item.email,
firstName = item.firstName,
lastName = item.lastName,
avatarUrl = item.avatar
)
}
}
}
그렇다면 결과는 다음과 같다
데이터 통신을 하는 동안에는 로딩창을 띄우고 -> 데이터 통신이 성공하면 해당 데이터를 출력한다
만약 이러한 로직이 없다면 사용자는 앱에 아무런 동작이 없어도 이게 데이터를 가져오는 중인지.. 네트워크가 이상해서 안불러와지는건지 판단할 수 없다..! 우리는 클라이언트 개발자이므로 클라이언트의 앱 친화도를 높일 수 있는 방안을 항상 마련해야한다!
'프로그래밍 > 안드로이드' 카테고리의 다른 글
MVI 패턴 (1) | 2024.09.20 |
---|---|
당신도 할 수 있다 #api연동 (1) | 2024.03.05 |
Rest API (0) | 2024.01.30 |
구글 로그인 구현하기 (0) | 2024.01.24 |
dialog 크기가 조절되지 않을 때 (0) | 2024.01.22 |