본문 바로가기
프로그래밍

Type? State?

by 엽기토기 2023. 11. 14.
반응형

 

Type과 State의 구분

 

SNS 탭에서 게시글을 API를 통해 불러오는 기능을 구현하는 과정에서, 두 가지 주요 동작에 직면했습니다:

  1. 가장 최신 100개 불러오기 (FetchFeeds)
  2. 특정 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()
        }
    }
}

 

문제 인식

이 접근 방식에서 몇 가지 문제점이 드러났습니다:

  1. Type과 State 혼동: 'Type'을 사용해 'State'를 구분했으나, 이는 적절하지 않았습니다.
  2. 상태 변화 관리의 복잡성: 여러 API 호출에 대해 단일 상태 관찰 코드로 모든 상태 변화를 처리하려 했습니다.
  3. 비동기 작업의 의존성 문제: 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 처리에 있어서 안정적이고 정확한 접근이 필수적임을 깨닫게 되었습니다.

반응형