코딩하는 일용직 노동자

안드로이드 MVVM 패턴으로 작업해보기 (4) 본문

안드로이드

안드로이드 MVVM 패턴으로 작업해보기 (4)

bacass 2020. 5. 1. 14:53

#1 API 호출 및 파싱.
우선 api 호출 후 결과를 저장할 모델 클래스를 만듭니다.
포스트맨 에서 얻어낸 결과 json 을 이용해 안드로이드 스튜디오에서 DTO generator 플러그인을 이용해 편하게 만들었습니다.

json 결과를 선택하고 Generate... 선택
DTO from JSON 선택
Generate 를 선택

hits 를 MutableList 형태로 받도록 수정해줬습니다.

#2 API 호출 콜백.
MVVM구조에서 ViewModel은 자신을 사용하는 View를 몰라야 합니다.
안드로이드 개발문서에서도 ViewModel에 View references에 대한 참조가 없어야 한다고 강조합니다.

안드로이드 개발문서 ViewModel 의 경고.
Activity 는 ViewModel을 observing 한다.

때문에 ViewModel에서 View로 이벤트나 결과를 전달해야 할 경우에는 View가 ViewModel을 observe하다가 상태가 변하면 그에 맞는 처리를 하게 했습니다.

api 호출 후 결과를 리턴시키기 위해 ViewModel 에 SingleLiveEvent 를 하나 만들어뒀습니다.

class HomeViewModel(private val repository: NetworkRepository) : BaseViewModel() {
    sealed class SearchResult {
        object Success : SearchResult()
        class Fail(val errorMsg: String) : SearchResult()
        object NetworkError : SearchResult()
    }

    val searchResultEvent = SingleLiveEvent<SearchResult>()
    
    ...
}

SingleLiveEvent는 MutableLiveData를 상속받아 만든 클래스입니다.
원본은 인터넷에 검색해보면 있는데, 이것을 아주 살짝 고친 형태로 사용했습니다.
원본은 여기에 있습니다. (SingleLiveEvent에 관한 좀더 자세한 포스팅은 이곳(https://bacassf.tistory.com/49)을 참고해주세요)
우선 간략하게 말하자면, LiveData를 View에서 observe하면 신호가 두번 전달되는 경우가 있게되는데 이것을 막기위해 사용하는 것입니다.
화면쪽에서는 이것을 observe 해서 결과가 정확히 오는지 테스트해봤습니다.

viewModel.searchResultEvent.observe(viewLifecycleOwner, Observer {
    when (it) {
        is HomeViewModel.SearchResult.Success -> {
            Toast.makeText(context, "데이타 수신", Toast.LENGTH_SHORT).show()
        }
        is HomeViewModel.SearchResult.Fail -> {
            Toast.makeText(context, "데이타 없음", Toast.LENGTH_SHORT).show()
        }
        is HomeViewModel.SearchResult.NetworkError -> {

        }
    }
})


#3 결과확인

var imageListData: MutableList<ImageHits>? = mutableListOf()

repository.searchImage(params).run {
    when (this) {
        is NetworkResult.Success -> {
            imageListData.value?.total = this.response?.total!!
            imageListData.value?.totalHits = this.response?.totalHits

            if (this.response?.hits!!.isEmpty()) {
                searchResultEvent.sendEvent(SearchResult.Fail(""))
            } else {
                imageListData.value?.hits?.addAll(this.response?.hits)

                searchResultEvent.sendEvent(SearchResult.Success)
            }
        }
        is NetworkResult.Error -> {
            searchResultEvent.sendEvent(SearchResult.NetworkError)
            return@launch
        }
    }
}

ViewModel에서 api의 결과를 받으면 searchResultEvent.sendEvent(SearchResult.Success) 형태로 View쪽에 신호를 주도록 했습니다.
View에서는 searchResultEvent를 observe하고 있으니 이 신호를 받아서 처리하게 됩니다.

API 호출 및 수신 테스트

결과를 확인해보니 제대로 동작합니다.
이제 리스트 화면을 만들어 보겠습니다.