반응형
Type과 State의 구분
SNS 탭에서 게시글을 API를 통해 불러오는 기능을 구현하는 과정에서, 두 가지 주요 동작에 직면했습니다:
- 가장 최신 100개 불러오기 (FetchFeeds)
- 특정 ID부터 시작하여 일정 수의 게시글 불러오기 (FetchMoreFeeds)
이 두 API 동작은 각각 세 가지 상태(로딩, 성공, 실패)를 가지며, 상태별로 다른 UI 변화를 요구합니다. 이는 결국 각 API에 대해 세 가지 상태 변화에 대응하는 로직을 개발해야 함을 의미합니다. 본 글에서는 이 과정에서 겪은 문제와 해결 방법을 공유합니다.
안 좋은 예시: 초기 접근 방법
초기에는 'Type'을 기준으로 상태 변화를 구분하려 시도했습니다. 아래는 초기 코드 예시입니다:
DataStatus Sealed Class:
sealed class DataStatus<T>(val data: T? = null, val message: String? = null) {
class Success<T>(data: T) : DataStatus<T>(data)
class Error<T>(data: T? = null) : DataStatus<T>(data)
class Loading<T>(data: T? = null) : DataStatus<T>(data)
}
- 각 상태를 포괄하는 클래스로 정의했습니다.
ViewModel의 SNS 게시글 관리:
@HiltViewModel
class FeedViewModel @Inject constructor(...) : ViewModel() {
private val _feeds = MutableLiveData<DataStatus<MutableList<Feed>>>()
val feeds: LiveData<DataStatus<MutableList<Feed>>> get() = _feeds
...
}
- MutableList<Feed>를 DataStatus로 감쌌습니다.
API 호출 메서드:
fun fetchFeeds() {
_feeds.value = DataStatus.Loading()
viewModelScope.launch(coroutineExceptionHandler) {
fetchFeedsUseCase
.fetchFeeds()
.onSuccess { _feeds.value = DataStatus.Success(it.toMutableList()) }
.onFailure {
errorObserver.value = it
_feeds.value = DataStatus.Error()
}
}
}
- 여기서 DataStatus.Loading(), DataStatus.Success(...) 등을 설정합니다.
UI 변화 관찰:
feedViewModel.feeds.observe(viewLifecycleOwner) { status ->
when (status) {
is DataStatus.Loading -> {
startShimmer()
}
is DataStatus.Success -> {
binding.feedRefreshview.isRefreshing = false
isLoading = false
status.data?.let { feeds ->
feedRecyclerviewAdapter.notifyFeeds(feeds)
stopShimmer()
binding.feedRecyclerview.visibility = View.VISIBLE
}
}
is DataStatus.Error -> {
binding.feedRefreshview.isRefreshing = false
stopShimmer()
}
}
}
문제 인식
이 접근 방식에서 몇 가지 문제점이 드러났습니다:
- Type과 State 혼동: 'Type'을 사용해 'State'를 구분했으나, 이는 적절하지 않았습니다.
- 상태 변화 관리의 복잡성: 여러 API 호출에 대해 단일 상태 관찰 코드로 모든 상태 변화를 처리하려 했습니다.
- 비동기 작업의 의존성 문제: Feeds가 DataStatus에 지나치게 의존하고 있었습니다.
개선 방향
다음과 같이 개선하였습니다:
DataStatus Enum Class:
enum class DataStatus { SUCCESS, ERROR, LOADING }
- 각 상태를 간결하게 표현하는 열거형으로 변경했습니다.
상태와 데이터의 분리:
private val _feeds = MutableLiveData<List<Feed>>()
val feeds: LiveData<List<Feed>> = _feeds
private val _fetchDataStatus = MutableLiveData<DataStatus>()
val fetchDataStatus: LiveData<DataStatus> = _fetchDataStatus
private val _fetchMoreDataStatus = MutableLiveData<DataStatus>()
val fetchMoreDataStatus: LiveData<DataStatus> = _fetchMoreDataStatus
- 데이터(feeds)와 상태(fetchDataStatus, fetchMoreDataStatus)를 분리하여 관리합니다.
- *현재는 LiveData 가 아닌 StateFlow 를 사용하고 있습니다.
상태 변화의 관리 개선:
fun fetchFeeds() {
_fetchDataStatus.value = DataStatus.LOADING
...
}
- 상태 변화를 각각의 LiveData를 통해 관리하며, UI 업데이트 로직을 분리했습니다.
각 상태별 UI 업데이트:
feedViewModel.feeds.observe(viewLifecycleOwner) { ... }
feedViewModel.fetchDataStatus.observe(viewLifecycleOwner) { ... }
feedViewModel.fetchMoreDataStatus.observe(viewLifecycleOwner) { ... }
- Flow 를 사용하게 되어 collect 로 변경하였습니다.
결론
이 경험은 Type과 State를 구분하고 올바르게 사용하는 것의 중요성을 일깨워줬습니다. 비동기 로직과 UI 처리에 있어서 안정적이고 정확한 접근이 필수적임을 깨닫게 되었습니다.
반응형
'프로그래밍' 카테고리의 다른 글
[영상처리] 히스토그램 명세화 (histogram specification) (0) | 2020.12.23 |
---|---|
[JAVA] 자동완성 설정 (0) | 2020.12.23 |
[C++] 명품 c++ 프로그래밍 실습 11장 7번 (0) | 2020.12.23 |
[C++] 명품 c++ 프로그래밍 실습 7장 1번 (0) | 2020.12.23 |
[C++] 명품 c++ 프로그래밍 실습 6장 6번 (0) | 2020.12.23 |