정상에서 IT를 외치다

[Android, MVVM] MVVM 따라하기 - 2 (Model 구현) 본문

안드로이드

[Android, MVVM] MVVM 따라하기 - 2 (Model 구현)

Black-Jin 2020. 6. 9. 02:29
반응형

포스팅 순서는 아래와 같습니다.

 

1. 아무런 기술 적용 없이 요구사항에 맞춰 프로젝트를 구현합니다. 

2. Repository 패턴을 적용하여 Model을 구현합니다.

3. Rx를 적용하여 구현합니다.

4. Databinding을 사용해 MVVM을 구현합니다.

5. AAC의 ViewModel을 사용해 MVVM을 구현합니다.

 

순차적으로 코드를 리펙토링 하면서 어떤 점에서 코드의 유지보수가 좋아졌는지 보겠습니다.

 

Model 구현하기

 

 

출처

 

 

위 그림은 구글 가이드에 있는 MVVM 아키텍처 입니다. 이번 포스팅에 알아볼 것은 빨간 네모로 표시된 Model 부분입니다. Model은 데이터를 구현하는 부분으로 위 그림에서는 Repository 패턴을 적용해 구현되었습니다.

 

 

 

리포지터리 패턴이란?

 

 

먼저 리포지터리에 대한 제타위키 내용을 보겠습니다.

 

 

 

애플리케이션과 데이터 소스 사이의 중개 레이어 역활을 합니다. 이를 구글 아키텍처 가이드와 비교해서 보면 local과 remote의 두가지 데이터 소스의 중개 레이어 역활을 하게 됩니다. Repository를 사용하는 부분에서는 local에서 데이터를 가져왔는지 remote에서 데이터를 가져왔는지 몰라야 합니다.

 

 

Local

 

로컬 영역으로 디바이스 내부 DB를 가리킵니다. 여기에는 room과 sharedPreference와 같은 DB가 해당됩니다.

 

 

Remote

 

서버 영역으로 현 예제에서는  retrofit를 사용해 구현하였습니다.

 

 

리포티저리에서는 로컬과 서버 중 어느 부분을 사용해 통신할 지를 결정합니다. 이렇게 함으로써 View에서는 내가 하고 있는 통신이 로컬인지 서버인지를 모르게 합니다.  또한 통신과 관련된 비즈니스 로직을 처리하여 View는 자신의 로직에만 집중할 수 있게 됩니다.

 

View는 오로지 가공이 된 데이터를 받아 처리해야 하며 통신과 관련된 그 어떤 것도 알아서는 안됩니다.

 

 

 

구현방법

 

BaseResponse

interface BaseResponse<T> {

    fun onSuccess(data: T)

    fun onFail(description: String)

    fun onError(throwable: Throwable)

    fun onLoading()

    fun onLoaded()
}

 

API 통신 결과에 의한 성공 및 실패를 BaseResponse를 사용해 처리해줍니다. 이제 View는 성공, 실패, 에러, 로딩중, 로딩완료의 정보만을 알게 됩니다.

 

 

RepoRepository

interface RepoRepository {

    fun searchRepositories(query: String, callback: BaseResponse<RepoSearchResponse>)

    fun getDetailRepository(user: String, repo: String, callback: BaseResponse<RepoDetailModel>)
}

 

RepoRepositoryImpl

class RepoRepositoryImpl(
private val repoApi: RepoApi,
private val userApi: UserApi
) : RepoRepository {

override fun searchRepositories(query: String, callback: BaseResponse<RepoSearchResponse>) {
callback.onLoading()

repoApi.searchRepository(query)
.enqueue(object : Callback<RepoSearchResponse> {
override fun onResponse(
call: Call<RepoSearchResponse>,
response: Response<RepoSearchResponse>
) {
val body = response.body()
if (response.isSuccessful && null != body) {
callback.onSuccess(body)
} else {
callback.onFail(response.message())
}

callback.onLoaded()
}

override fun onFailure(call: Call<RepoSearchResponse>, t: Throwable) {
callback.onError(t)
callback.onLoaded()
}
})
}

override fun getDetailRepository(
user: String, repo: String, callback: BaseResponse<RepoDetailModel>
) {
//...
}
}

 

리포지터리 구현체 입니다. BaseResponse을 사용해 성공, 실패, 에러, 로딩중, 로딩완료에 관한 로직을 모델에서 전부 처리할 수 있게 작업했습니다. 

 

 

SearchFragment

private fun searchRepository(query: String) {
    if (query.isEmpty()) {
        requireContext().toast(requireContext().getString(R.string.please_write_repository_name))
    } else {
        repoRepository.searchRepositories(query, object : BaseResponse<RepoSearchResponse> {
            override fun onSuccess(data: RepoSearchResponse) {
                with(repoAdapter) {
                    setItems(data.items.map { it.mapToPresentation(requireContext()) })
                }

                if (0 == data.totalCount) {
                    showError(getString(R.string.no_search_result))
                }
            }

            override fun onFail(description: String) {
                showError(description)
            }

            override fun onError(throwable: Throwable) {
                showError(throwable.message)
            }

            override fun onLoading() {
                hideKeyboard()
                clearResults()
                hideError()
                showProgress()
            }

            override fun onLoaded() {
                hideProgress()
            }

        })
    }
}

 

모델에서 통신에 관한 비즈니스 로직을 처리해 주었기 때문에 뷰에서는 상태에 따른 화면만 보여주면 됩니다. 이제 뷰와 모델의 역활이 완전히 분리되었습니다. 뷰완 관련된 로직은 뷰에서 처리하면 되고 통신과 관련된 로직은 모델에서 처리할 수 있게 되었습니다. 

 

 

리펙토링 - 리포티저리 패턴 깃허브 링크

반응형
Comments