[Android] 클린 아키텍처로 ViewModel 개선하기

sundays·2023년 4월 8일
0

cleanArchitecture

목록 보기
3/6

클린 아키텍처로 개선하기 위해서는 모든 객체들을 행동을 기준으로 독립적으로 생성해야 한다는 것입니다. 일단 모든 객체들을 전부 나눠주는 것부터 시작하게 되었습니다. 이전의 ViewModel은 너무 많은 의존성을 가지고 있었습니다. 정말 자잘한 네이밍 규칙에 대한 위반 내용들은 제외하겠습니다.

개선 전 ViewModel

class VideoViewModel @Inject constructor(...) : ViewModel() {
	private val _data: MutableLiveData<NaverMovie> = MutableLiveData()
	...
    init {
    	viewModelScope.launch {
            val response = repository.get2(title!!)
            if (response.isSuccessful) {
                _plotData.value = response.body()?.Data?.get(0)?.Result?.get(0)?.plots?.plot?.get(0)
             }
             
    	viewModelScope.launch {
      		val response = repository.get(title!!, year!!)
      		if (response.isSuccessful) {
         		_data.value = response.body()?.items?.get(0)
  		}
    }
    ...
}

클린 아키텍처 개선

한번에 여러개의 로직을 담당하는 SRP 위반

기존의 코드에서 init에서 flow가 두개가 운영되고 있었는데 이것은 가독성이 떨어지고 단일 책임 원칙에 위반되는 코드이기도 합니다. 이 두가지의 repository에서 받는 리턴 데이터를 단일개로 묶어서 하나의 로직처럼 변경하게 되었습니다. 두개의 데이터를 mapping 하여 사용하기 위해 combine 를 사용하여 데이터를 관리 하게 되었습니다.

init {
	collectFlow()
}
...
private fun collectFlow() {
	combine(kmdb, movieClips) { kmdb, movieClips ->
	   ...
	}.launchIn(viewModelScope)
}

비지니스 로직까지 가지고 있는 책임이 크다

기존의 코드에서는 Usecase에 작성되어야 하는 비지니스 영역의 코드까지 전부 ViewModel에서 담당하였습니다. 클린 아키텍처 에서는 이 비지니스로직의 해당하는 부분을 전부 단일 객체에서 돌려주기위해 UseCase 클래스를 추가로 생성하여 새로 작성하게 됩니다. 그래서 이 UseCase를 호출해서 ViewModel에 사용해주었습니다.

class GetNaverUseCase @Inject constructor(private val repository: MovieRepository) {
	operator fun invoke() : Flow<Resource<NaverMovie>> = flow {
    	try{
        	emit(Resource.Loading())
            val movie = repository.getMovieByNaver(title, year).item[0].toItem()
            emit(Resource.Success(movie))
        } catch(e: HttpException) {
        	emit(Resource.Error("에러"))
        } catch(e: IOException) {
        	emit(Resource.Error("에러"))
        } 
    }
}

로딩화면과 네트워크 오류로 인한 에러 표기 개선

이번 코드에서 가장 중요하다고 생각했던 개선점은 로딩화면 및 네트워크 오류 표기에 대한 부분입니다. 앞서 생성하였던 UseCase의 Try-Catch로 인해 에러를 표기할때 어떻게 표기할 것인가를 ViewModel이 정할 수 있습니다.

	fun getMovieDetail(title: String, year: Int) {
        getNaverUseCase(title, year).onEach { result ->
            when (result) {
                is Resource.Loading -> {
                    _lastMovieData.value = LastMovieState(isLoading = true)
                }
                is Resource.Success -> {
                    _lastMovieData.value = LastMovieState(movie = result.data)
                }
                is Resource.Error -> {
                    _lastMovieData.value =
                        LastMovieState(error = result.message ?: "An unexpected error occured")
                }
            }
        }
    }

Repository를 가져오는 수동 DI가 발생하여 의존성이 크다

현재 존재하는 Repository를 수동으로 의존성을 주입해주고 있는데요 이렇게 되면 나중에 API서버가 변경될때 혹은 다른 repository에 넣게 될때 Viewmodel에 각종산재되어있는 repository를 전부 변경해야 하겠죠? 저는 Hilt를 사용해서 모듈에서 재사용할 수있도록 정의 하였습니다

	@Provides
    @Singleton
    fun provideMovieRepository(api: MovieApi): MovieRepository {
        return MovieRepositoryImpl(api)
    }

결론

이번에 Clean Archtecture를 도입하고 프로젝트 개선이 되어가는 중입니다. 점점 객체지향을 향해가는 것이 즐겁습니다. 해당 프로젝트의 전체 소스는 링크를 확인해주시면 감사하겠습니다.

profile
develop life

0개의 댓글