[Android Architecture] 데이터를 처리하는 Model

안드로이드의 아키텍쳐들은 MVC, MVP, MVVM, MVI가 있지만, 이를 이루는 것에 'MV'은 필수 적으로 들어가 있다. M이란 Model, V란 View를 의미한다.

 

View에는 사용자와 바로 인터렉션 할 수 있는 UI를 제공해주는 부분을 말한다. 안드로이드에서는 Acitivity, Fragment등 이라고 할 수 있다.

 

안드로이드를 처음 배우고, 아키텍처를 적용하지 않는다고 하면 이 View단에서 Data관련 로직을 처리하도록 구현하기 쉽다. 안드로이드 개발 공식 문서에서 '책임의 분리'를 강조하며 아키텍처를 사용하여 구현하는 것을 추천하는데, 이렇게 View 단에 View와 상관없는 로직을 추가하는 것은 '책임의 분리'를 적용하지 않는 것이다. 

 

이번 포스팅에서는 안드로이드 아키텍처 스터디를 하며 Model에 대해 공부하고 반영한 내용을 정리해 보려고 한다.

 

Model은 데이터 관련 로직을 처리한다고 하였다. 안드로이드 개발을 하다보면 데이터를 서버에서 가져올 수도, 기기내 저장소에서 가져올 수도 있다. 이를 구분하기 위해 두가지 데이터 소스를 구현해야 한다.

 

  • Remote Data Source
  • Local Data Source

이 데이터 소스안에서는 데이터를 가져오는 구현을 해야 한다. 예를 들어 RemoteDataSource에는 Retrofit2를 사용하여 서버에서 데이터를 가져 오는 로직이 들어가야 한다. LocalDataSource에는 Room에 저장된 데이터를 가져오는 로직이 들어가야 한다.

 

데이터를 가져오는 방법은 각각 다를 수 있으나, RemoteDataSource는 서버에서 데이터를 가져오기 위한 로직, LocalDataSource는 로컬에 데이터를 가져오는 로직을 각각 분리 해야 한다.

 

그러면 이제 언제 서버에서 데이터를 가져와야 하고, 로컬에서 데이터를 가져와야 하는지 구분하는 로직이 필요하다. 이는 Repostitory에서 담당한다. 예를들어 로컬에 저장되어 있는 정보의 시간을 비교하여 서버에서 데이터를 가져와야 하는지 판단하는 로직은 이 Repository에서 처리 한다.

 

Repository에서 판단하여 전달 된 데이터를 View로 전달하게 되는데, 이렇게 구현을 하게 되면 View는 Reposiroy가 전달 해 준 데이터가 local 데이터 인지, remote데이터인지 알 필요가 없이 전달된 데이터를 화면에 뿌려주기만 하면 된다. 

 

 

 

기존 코드에서 Model 관련 부분을 떼어내려고 하는 도중 한 가지 문제가 생겼다.. 아래 코드는 DataResource의 getMovie()인데, 이는 Repository로 데이터를 전달하는 역할을 한다. 그런데 네트워크 통신이 비동기적으로 이루어지기 때문에 저 List값을 어떻게 Repository가 받을 수 있게 해야하는지 고민하였다.

 

override fun getMovies(q: String): List<Movie.Item> {
        remoteService.requestSearchMovie(query = q).enqueue(object : Callback<Movie> {
            override fun onResponse(call: Call<Movie>, response: Response<Movie>) {
                response.body()?.let {
                }
            }

            override fun onFailure(call: Call<Movie>, t: Throwable) {
                TODO("Not yet implemented")
            }
        })
    }

 

이를 해결 하기 위해 매개변수로 함수를 전달 하게 하였다. 비동기적으로 일어나는 서버에서 데이터를 가져오는 작업에 return값은 필요가 없어보여 제거하였다. 

 

interface NaverRemoteDataSource {
    fun getMovies(q : String, success:(List<Movie.Item>) -> Unit,
    error: (Throwable) -> Unit)
}

 

바뀐 구현부는 아래와 같다.

 

override fun getMovies(
        q: String,
        success: (List<Movie.Item>) -> Unit,
        error: (Throwable) -> Unit
    ) {
        remoteService.requestSearchMovie(query = q).enqueue(object : Callback<Movie> {
            override fun onResponse(call: Call<Movie>, response: Response<Movie>) {
                response.body()?.let {
                    success(it.items)
                }
            }
            override fun onFailure(call: Call<Movie>, t: Throwable) {
                error(t)
            }
        })
    }

 

파라미터로 전달 할 때는 아래와 같이 원하는 바를 구현하여 던져주면 된다.

 

btnSearch.setOnClickListener {
            adapter.clearData()
            movieContainer.repository.getMovies(etUrl.text.toString(), {
              adapter.addData(it)
            }, {
                Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
            })
        }

댓글



Designed by JB FACTORY