정상에서 IT를 외치다

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

안드로이드

[Android, MVVM] MVVM 따라하기 - 3 (RxJava 구현)

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

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

 

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

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

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

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

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

 

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

 

서버 통신 Rx로 변경하기

 

이번 시간에는 깃허브 프로젝트에 RxJava를 적용하여 비동기 작업을 효율적으로 처리하는 방법을 알아보겠습니다.

 

 

RxJava

 

안드로이드 개발을 하면서 비동기 처리와 콜백을 다루는 작업을 많이 겪게 됩니다. 이를 도와주는 여러 라이브러리 중에서 이번 시간에는 RxJava에 대해 다뤄볼까 합니다. 

 

 

Retrofit 에서 RxJava 적용하기

 

Retrofit에서는 이미 RxJava형태로 쓸 수 있게 준비되어 있습니다. 통신 결과로 반환하는 데이터를 Rx의 옵서버블 형태로 변환해 줄 수 있습니다. 

 

 

 

1. Rx 통신을 위한 라이브러리 추가

 

현재 RxJava의 최신 버전은 3.x.x입니다. 하지만 Retrofit 에서는 아직 RxJava 3 버전이 호환되지 않습니다. 이에 Rx 2 버전을 사용해 예제를 구현하겠습니다. 혹시 RxJava 3 버전을 사용하고 싶으신 분은 Retrofit 2 버전과 RxJava 3 버전 호환을 도와주는 라이브러리를 사용해주세요.

 

rxJavarxAndroid 최신버전 버전은 링크를 확인해 주세요. 

현 포스팅 시점 최신버전은 RxAndroid 2.1.1 , RxJava 2.2.19 입니다.

 

//retrofit
def

retrofitVersion =

'2.3.0'

implementation

"com.squareup.retrofit2:retrofit:

$retrofitVersion

"

implementation

"com.squareup.retrofit2:converter-gson:

$retrofitVersion

"

//Retrofit 에서 받은 응답을 옵서버블로 변환해주는 라이브러리

implementation

"com.squareup.retrofit2:adapter-rxjava2:

$retrofitVersion

"

...


//rx android
def

rxandoirdVersion =

'2.1.1'
def

rxjavaVersion =

'2.2.19'

implementation

"io.reactivex.rxjava2:rxandroid:

$rxandoirdVersion

"

implementation

"io.reactivex.rxjava2:rxjava:

$rxjavaVersion

"

 

 

2. 데이터 처리부 수정하기

 

응답을 옵서버블 형태로 반환해 줄 수 있도록 Factory를 추가해 줍니다.

private fun getRetrofitBuild() = Retrofit.Builder()
    .baseUrl(baseUrl)
    .client(getOkhttpClient())
    // 받은 응답을 옵서버블 형태로 변환해 줍니다.
    .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
    .addConverterFactory(getGsonConverter())
    .build()

 

 

3. RepoApi, UserApi 반환타입 수정하기

 

서버 통신으로 받아올 데이터는 오직 1개만 있으면 됩니다. 이에 1개의 데이터만 발행하는 옵서버블인 Single을 사용해 줍니다.

 

RepoApi

interface RepoApi {

    @GET("search/repositories")
    fun searchRepository(@Query("q") query: String): Single<RepoSearchResponse>

    @GET("repos//")
    fun getRepository(
        @Path("owner") ownerLogin: String,
        @Path("name") repoName: String
    ): Single<RepoModel>
}

 

 

UserApi

interface UserApi {

    @GET("users/")
    fun getUser(@Path("name") userName: String): Single<UserModel>
}

 

 

4. Repository 구현부 수정하기

 

Disposable

 

디스포저블은 옵서버가 옵서버블을 구독할 때 생성되는 객체로, 옵서버블에서 만드는 이벤트 스트림과 이에 필요한 리소스를 관리합니다. 옵서버블로부터 더 이상 이벤트를 받지 않으려면 디스포저브을 통해 구독 해제를 할 수 있습니다. 구독 시 생성되는 디스포저블 객체를 하나씩 관리할 수도 있지만, CompositeDisposable을 사용하면 여러 개의 디스포저블 객체를 하나의 객체에서 관리할 수 있습니다.

 

RepoRepository

interface RepoRepository {

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

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

 

Repository에서 Disposable을 반환할 수 있게 수정하였습니다.

 

RepoRepositoryImpl

override fun searchRepositories(
    query: String,
    callback: BaseResponse<RepoSearchResponse>
): Disposable {
    return repoApi.searchRepository(query)
        //이 이후에 수행되는 코드는 메인 쓰레드에서 실행됩니다.
        .observeOn(AndroidSchedulers.mainThread())
        //구독할 때 수행할 작업을 구현합니다.
        .doOnSubscribe {
            callback.onLoading()
        }
        //스트림이 종료될 때 수행할 작업을 구현합니다.
        //Single에서 스트림이 종료되는 시점은 성공(Success)과 에러(Error) 입니다.
        .doOnTerminate {
            callback.onLoaded()
        }
        //옵서버블을 구독합니다.
        .subscribe({
            callback.onSuccess(it)
        }) {
            // 에러 처리 작업을 구현합니다.
            if(it is HttpException) {
                callback.onFail(it.message())
            } else {
                callback.onError(it)
            }
        }
}

기존의 콜백 형식에서 Rx를 사용해 하나의 데이터 흐름으로 원하는 로직을 처리할 수 있게 되었습니다.

 

override fun getDetailRepository(
    user: String, repo: String, callback: BaseResponse<RepoDetailModel>
): Disposable {
    return Single.zip(
        repoApi.getRepository(user, repo), userApi.getUser(user),
        BiFunction<RepoModel, UserModel, RepoDetailModel> { repoModel, userModel ->
            RepoDetailModel(
                title = repoModel.fullName,
                repoName = repoModel.name,
                ownerName = userModel.name,
                ownerUrl = userModel.profileImgUrl,
                followers = userModel.followers,
                following = userModel.following,
                description = repoModel.description,
                language = repoModel.language,
                updatedAt = repoModel.updatedAt,
                stars = repoModel.stars
            )
        }
    ).observeOn(AndroidSchedulers.mainThread())
        .doOnSubscribe {
            callback.onLoading()
        }
        .doOnTerminate {
            callback.onLoaded()
        }
        .subscribe({
            callback.onSuccess(it)
        }) {
            if(it is HttpException) {
                callback.onFail(it.message())
            } else {
                callback.onError(it)
            }
        }
}

RxJava에는 많은 장점이 있지만 그 중 다양한 오퍼레이터를 제공한다는 것이 가장 큰 장점입니다. 위 예제는 repoApi와 userApi의 비동기 처리를 zip 오퍼레이터를 사용해 하나의 데이터 흐름으로 합쳐 처리할 수 있게 되었습니다. 기존에  repoApi 콜이 성공하면 userApi 콜을 호출하는 예제에서는 코드양도 방대하고 API 콜의 시작과 끝을 정하기가 어려웠습니다. 하지만 RxJava를 사용하니 위와 같이 깔끔하게 구현할 수 있게 되었습니다.

 

 

 

SearchFragment 적용하기

 

repoRepository

.searchRepositories(query

, object

: BaseResponse<RepoSearchResponse> {
//...

}).

also {
compositeDisposable

.add(

it

)

}

 

기존 repository 호출에서는 아무 값도 반환하지 않았지만 이제는 Disposable을 반환하게 되었습니다. 이를 CompoeiteDisposalbe을 사용해 안드로이드 생명주기에 맞춰 관리해 줍니다.

 

override fun onStop() {
    compositeDisposable.dispose()
    super.onStop()
}

 

만일 데이터가 계속 통신 중일 때 화면이 닫혀버리면 통신 또한 종료해야 합니다. 이에 onStop 에서 CompoeiteDisposalbe에 있는 객체들을 일괄적으로 dispose 해줍니다. 이렇게 함으로써 화면이 종료되었을 떄 불필요한 데이터 호출을 막을 수 있고 행여나 발생할 수 있는 메모리릭 문제나 ANR을 모두 방지할 수 있습니다.

 

리펙토링 - RxJava 구현 깃허브 링크

반응형
Comments