관리 메뉴

나만을 위한 블로그

[Android] 반응형 프로그래밍(Reactive Programming)이란? RxKotlin + MVVM + Hilt 예시 본문

Android

[Android] 반응형 프로그래밍(Reactive Programming)이란? RxKotlin + MVVM + Hilt 예시

참깨빵위에참깨빵 2023. 7. 26. 00:06
728x90
반응형

지금까지 객체지향 프로그래밍, 함수형 프로그래밍에 대해 포스팅했다.

 

https://onlyfor-me-blog.tistory.com/367

 

객체 지향 프로그래밍이란?

위키백과에서 말하는 객체 지향 프로그래밍은 아래와 같다. https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D 객체 지향 프로그래밍 - 위키백과, 우리 모두의

onlyfor-me-blog.tistory.com

 

https://onlyfor-me-blog.tistory.com/431

 

함수형 프로그래밍이란?

Rxjava, RxAndroid를 공부하는 중인데 둘을 공부하다 보면 함수형 프로그래밍이란 말이 매우 자주 보이고, Rx를 공부하려면 함수형 프로그래밍에 대한 이해가 필요해 보여 따로 정리해둔다. https://ko.w

onlyfor-me-blog.tistory.com

 

이외에 다른 프로그래밍 패러다임 중 하나인 반응형 프로그래밍에 대해 다룬 적은 없어서 이번에 포스팅하려고 한다.

위키백과에서 말하는 반응형 프로그래밍은 아래와 같다.

 

https://en.wikipedia.org/wiki/Reactive_programming

 

Reactive programming - Wikipedia

From Wikipedia, the free encyclopedia Programming paradigm based on asynchronous data streams In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm, it's possi

en.wikipedia.org

반응형 프로그래밍은 데이터 스트림 및 전파와 관련된 선언적 프로그래밍 패러다임이다. 이 패러다임을 쓰면 정적(배열 등) 또는 동적(이벤트 emitters 등) 데이터 스트림을 쉽게 표현할 수 있으며 연결된 실행 모델 안에 추론된 종속성이 존재한다는 걸 전달할 수 있어서, 변경된 데이터 흐름(flow)의 자동 전파가 용이하다
예를 들어 명령형 프로그래밍 설정에서 a := b + c 표현식이 평가되는 순간 a에 b + c의 결과가 할당되고 나중에 b, c 값이 a값에 영향을 주지 않고 변경될 수 있음을 의미한다. 반면 반응형 프로그래밍에선 b 또는 c 값이 바뀔 때마다 a값이 자동으로 업데이트되며 프로그램은 현재 할당된 a값을 결정하기 위해 a := b + c문을 명시적으로 재실행할 필요가 없다...(중략)

< 전파 알고리즘 변경(Change propagation algorithms) >

데이터 전파에 대한 가장 일반적인 접근 방식은 아래와 같다

- Pull : 값 소비자는 관찰된 소스에서 값을 정기적으로 쿼리하고 관련 값을 쓸 수 있을 때마다 반응한다는 점에서 능동적이라 할 수 있다. 이벤트 또는 값 변경을 정기적으로 확인하는 이런 방식을 일반적으로 폴링(polling)이라 한다
- Push : 값 소비자는 값을 쓸 수 있게 될 때마다 소스에서 값을 받는다. 이 값은 독립적이다. 여기엔 필요한 모든 정보가 포함돼 있으며 소비자가 추가 정보를 쿼리할 필요가 없다
- Push-Pull : 소비자는 변경에 대한 간단한 설명인 변경 알림(change notification)을 받는다. 그러나 알림엔 필요한 모든 정보가 포함돼 있지 않으므로 소비자는 알림을 받은 후 더 많은 정보(특정 값)를 소스에 쿼리해야 한다. 이것이 Pull 부분이다. 이 방법은 소비자가 잠재적으로 관심을 가질 수 있는 다량의 데이터가 있을 때 일반적으로 사용된다. 따라서 처리량, 대기시간을 줄이기 위해 가벼운 알림만 전송된다. 더 많은 정보가 필요한 소비자는 특정 정보를 요청할 것이다. 이 접근 방식에는 알림이 전송된 후 추가 정보에 대한 많은 요청으로 소스가 압도당할 수 있다는 단점도 있다...(중략)

 

위키백과의 말대로라면 반응형 프로그래밍은 데이터의 흐름(스트림), 전파를 중점으로 프로그래밍하는 패러다임이라 볼 수 있다. 그 외의 자세한 내용은 위키백과에서 확인하면 될 것 같다.

다른 곳에선 어떻게 설명하는지 확인했다.

 

https://www.techtarget.com/searchapparchitecture/definition/reactive-programming

 

What is reactive programming? What you need to know

Reactive programming describes a design paradigm that relies on asynchronous programming logic to handle real-time updates to otherwise static content.

www.techtarget.com

반응형 프로그래밍은 프로그래밍 로직에 의존해서 정적 컨텐츠에 대한 실시간 업데이트를 처리하는 디자인 패러다임을 설명한다. 사용자가 쿼리할 때마다 컨텐츠에 대한 데이터 업데이트를 처리하기 위한 효율적인 수단(자동화된 데이터 스트림 사용)을 제공한다. 반응형 프로그래밍에 쓰이는 데이터 스트림은 지속적으로 or 거의 연속적으로 생성되는 일관되고 응집력 있는 디지털 신호 모음이다. 이런 데이터 스트림은 트리거에 대한 반응으로 모션 센서, 온도 게이지 또는 제품 재고 DB 같은 소스에서 전송된다. 트리거는 아래 중 하나일 수 있다

- 이벤트 : 소프트웨어 생성 경고, 키 입력, IoT 시스템의 신호 등
- 호출(call) : workflow의 일부로 루틴을 호출하는 함수
- 메시지 : 시스템이 작업 상태, 오류, 실패, 기타 조건에 대한 정보와 같이 사용자 or 시스템 운영자에 재전송하는 정보 단위

반응형 프로그래밍은 유저의 입력을 요청하는 대신 이벤트에 응답하는 소프트웨어를 생성한다. 이벤트는 그저 어떤 일이 발생했다는 신호다. 이벤트는 '실시간' 신호라는 게 일반적으로 받아들여지고 있다. 즉 이벤트가 신호를 보내는 조건과 동시에 생성되고 실시간으로 처리돼야 한다는 뜻이다. 이런 이벤트는 여러 처리 요소를 통해 흐르거나 도중에 중지 및 처리, 분기되어 병렬 처리 활동을 생성할 수 있는 '스트림'으로 가장 잘 시각화된다. 이 처리는 대부분의 경우 시간에 민감하다
반응형 프로그래밍과 이것이 다루는 반응형 시스템은 옵저버, 핸들러 기능의 조합으로 구성된다. 옵저버는 중요 조건이나 변경사항을 인식하고 발생했음을 알리는 메시지를 생성하고, 핸들러는 이런 메시지를 적절하게 처리한다

비즈니스 애플리케이션에서 반응형 프로그래밍의 초기 애플리케이션은 주로 네트워크, 서버 또는 소프트웨어 상태 모니터링, 재고 수준 같은 DB 상태 신호 등에 국한됐다. 이것은 IoT, 스마트 빌딩 및 도시, 퍼블릭 클라우드 컴퓨팅의 출현으로 변화하고 있다...(중략)...반응형 프로그래밍은 옵저버, 핸들러를 구축하고 필요에 따라 스트림을 스레딩하는 것이다...(중략)

 

https://www.baeldung.com/cs/reactive-programming

 

반응형 프로그래밍은 비동기 이벤트 처리 및 데이터 스트림의 아이디어를 기반으로 하는 선언적 프로그래밍 패러다임이다. 반응형 프로그래밍은 GUI 프로그래밍, 웹 프로그래밍 등 여러 영역에서 쓰인다...(중략)...앞에서 말했듯 반응형 프로그래밍은 비동기 이벤트 처리란 아이디어를 기반으로 한다. 비동기 처리는 이벤트 처리가 다른 이벤트 처리를 차단하지 않는 걸 의미한다. 첫 GUI 프로그램에서 UI는 버튼 클릭 같은 일부 사용자 작업 후에만 업데이트됐다. 앱의 핵심은 소위 이벤트 루프였다. 무한 루프에서 유저 입력 처리 및 UI 업데이트를 담당했다. 그러나 프로그램이 입력을 동기적으로 대기(=차단) 중이었기 때문에 아무 일도 일어나지 않았다. 오지 않는 이벤트를 동기적으로 기다리면 전체 프로그램이 중단되고 GUI가 응답하지 않게 된다. 해결책은 GUI 업데이트에서 이벤트 처리를 분리하는 것이다. 분리는 이벤트 대기열과 병렬 이벤트 처리를 도입해 수행된다. 이 접근법은 생산자 및 소비자 패턴으로 알려져 있다. 유저 입력을 처리하는 쓰레드는 이벤트를 대기열에 넣고 소비자 쓰레드는 대기열의 헤드에서 이벤트를 가져와 처리한다...(중략)...이런 생산자/소비자 연결은 반응형 스트림으로 이어진다. 

 

이외에도 리액티브 선언문이라는 것도 있다. 2014년에 쓰여졌다.

 

https://www.reactivemanifesto.org/ko

 

리액티브 선언문

탄력성(Resilient): 시스템이 장애 에 직면하더라도 응답성을 유지 하는 것을 탄력성이 있다고 합니다. 탄력성은 고가용성 시스템, 미션 크리티컬 시스템에만 적용되지 않습니다. 탄력성이 없는 시

www.reactivemanifesto.org

 

이 선언문을 보면 반응형 시스템이 갖춰야 할 성질을 4가지로 요약하고 있다.

 

1. 응답성 : 시스템은 가능한 즉각적으로 응답해야 한다. 응답성은 편의성, 유용성의 기초가 되지만 문제를 신속하게 탐지하고 효과적으로 대처할 수 있는 걸 의미한다. 응답성 있는 시스템은 신속하고 일관성 있는 응답 시간을 제공하고 신뢰할 수 있는 상한선을 설정해 일관된 서비스 품질을 제공한다.

 

2. 탄력성 : 시스템이 장애를 겪어도 응답성을 유지하는 걸 탄력성이 있다고 한다. 탄력성 없는 시스템은 장애가 발생하면 응답성을 잃어버린다. 탄력성은 복제, 봉쇄, 격리, 위임에 의해 실현된다. 복제와 격리, 위임은 생각과 다른 뜻일 수 있으니 선언문을 확인해 보는 게 좋다.

 

3. 유연성 : 시스템 작업량이 변하더라도 응답성을 유지하는 걸 유연성이라 한다. 반응형 시스템은 입력 속도의 변화에 따라 입력에 할당된 자원을 증감시키면서 변화에 대응한다. 이것은 시스템에서 경쟁 지점이나 중앙 집중적인 병목 현상을 방지할 수 있게 설계해서 구성 요소를 샤딩하거나 복제해 입력을 분산시키는 걸 의미한다.

 

4. 메시지 구동 : 반응형 시스템은 비동기 메시지 전달에 의존해서 구성요소 사이에서 느슨한 결합, 격리, 위치 투명성을 보장하는 경계를 형성한다. 이 경계는 장애를 메시지로 지정하는 수단을 제공한다. 명시적 메시지 전달은 시스템에 메시지 큐를 생성하고 모니터링하며 필요 시 배압을 적용해서 유연성을 부여한다.

 

 

큰 시스템은 더 작은 규모의 시스템들로 구성돼 있기 때문에 구성요소의 반응적 특성에 의존한다. 즉 반응형 시스템은 설계 원칙을 적용하고 이 특성을 모든 규모에 적용해서 그 구성요소를 합성할 수 있게 하는 걸 의미한다.

 

반응형 프로그래밍의 예시는 뭐가 있을까? SNS 앱에서 다른 유저가 게시글의 좋아요를 누르면 난 좋아요를 누르지 않았어도 숫자가 저절로 +1 된다거나, 검색할 때 내가 친 검색어에 따라 추천 검색어가 자동완성되는 것 등이 있겠다.

이걸 안드로이드 관점에서 본다면 데이터 바인딩으로 Model과 View가 실시간으로 서로 업데이트를 해야 하는지 여부와 뭘로 업데이트해야 하는지를 공유하고 업데이트하는 것이라고 볼 수 있다.

 

그럼 반응형 프로그래밍은 어떻게 구사할 수 있는가? 2023년 현재 사용되고 있는 반응형 프로그래밍을 위한 라이브러리는 대표적으로 2014년에 소개된 RxJava가 있다. 이외에도 Rx를 공통 접두어로 갖는 RxKotlin, RxJS, RxSwift, RxPython 등 여러 자매품 라이브러리들이 존재한다.

Rx가 아니라도 iOS 진영의 combine, 안드로이드 진영의 Coroutine Flow가 반응형 프로그래밍에 사용할 수 있는 라이브러리다. 여기선 안드로이드에 초점을 맞춰 RxKotlin을 위주로 확인한다. RxJava는 예전에 몇 가지 포스팅해둔 글이 있으니 그 부분을 참고하거나, 구글에 널려 있는 많은 자료들을 확인하면 되겠다.

 

아래는 간단한 RxKotlin의 예시다.

 

import io.reactivex.rxjava3.kotlin.toObservable

fun main() {
    val list = listOf("apple", "banana", "cat")

    // 데이터 스트림 생성
    list.toObservable()
        // 중간 연산자로 각 아이템에 함수를 적용해 조건에 맞는 아이템만 필터링하는 등 데이터 조작
        .map { s -> s.length }
        .filter { length -> length >= 5 }
        // 결과 구독
        .subscribe(
            { length -> println("수신 : $length") },  // onNext
            { error -> println("에러 : $error") },    // onError
            { println("완료!") }                      // onComplete
        )
}

// 수신 : 5
// 수신 : 6
// 완료!

 

toObservable()을 써서 문자열 리스트를 Observable로 바꾼다. Observable은 데이터 스트림을 의미하며 이걸 통해 map, filter 등의 연산자를 사용할 수 있기 때문에 아주 중요한 요소다.

필요한 만큼 데이터를 조작했다면 subscribe()를 통해 결과를 받아볼(=구독) 수 있다. RxKotlin에선 subscribe() 안에 onNext, onError, onComplete 콜백을 정의할 수 있는데 이름 있는 아규먼트 형태로 작성이 불가능해서 순서에 맞게 써야 한다.

 

그럼 안드로이드에선 어떻게 반응형 프로그래밍을 구현할 수 있을까? 코드로는 어떻게 구현할 수 있는지 확인하기 위해 Github Api, hilt, Repository 패턴을 적용한 MVVM 패턴을 기반으로 하는 간단한 예시를 준비했다. Coroutine Flow를 사용한 예시는 이전에 작성했기 때문에 링크로 대신한다.

 

https://onlyfor-me-blog.tistory.com/478

 

[Android] Coroutine Flow란? MVVM + Flow + Retrofit 예제

최근 안드로이드 진영에서 비동기 처리에 자주 사용했던 라이브러리인 RxJava가 걷어내지고 그 자리를 코루틴의 flow라는 것이 대신한다고 들었다. 그래서 구글에서 Compose를 비롯해 여러 방식으로

onlyfor-me-blog.tistory.com

 

아래 코드들은 어디까지나 예시 코드기 때문에 실제로 사용하려면 리팩토링을 꼭 해야 한다.

먼저 의존성부터 추가한다.

 

implementation "io.reactivex.rxjava3:rxkotlin:3.0.1"
implementation "io.reactivex.rxjava3:rxandroid:3.0.1"
implementation "com.squareup.retrofit2:adapter-rxjava3:2.9.0"

 

위 3가지 의존성은 안드로이드에서 레트로핏, RxKotlin을 같이 쓴다면 필수적이다. 23.07.25 기준으로는 노란 줄로 경고가 표시되지 않지만, 이후에 이 포스팅을 본다면 버전업 여부를 확인하고 다른 버전을 써도 된다. 그리고 로그 라이브러리로 Timber를 쓰기 때문에 쓰고 있지 않다면 이 라이브러리의 의존성도 별도로 추가해 주거나 Log 클래스를 사용하도록 바꾼다.

data class는 아래처럼 정의한다.

 

data class Repository(
    val id: Int,
    val name: String,
    val description: String?
)

 

그리고 레트로핏을 쓸 것이기 때문에 인터페이스도 정의해 준다.

 

import io.reactivex.rxjava3.core.Observable
import retrofit2.http.GET
import retrofit2.http.Path

interface RxGithubApi {
    @GET("users/{user}/repos")
    fun getRepositories(
        @Path("user") user: String
    ): Observable<List<Repository>>
}

 

hilt를 적용한 프로젝트에서 위와 같은 레트로핏 함수가 작성된 인터페이스를 사용하려면 싱글톤한 provide 류 함수를 작성해야 한다. 대충 작성해 준다. 여기부터 나오는 클래스, 액티비티에선 hilt 어노테이션을 꼭 작성해야 하니 주의한다.

 

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object GithubNetworkModule {
    @Provides
    @Singleton
    fun provideGithubApi(): RxGithubApi = Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
        .build()
        .create(RxGithubApi::class.java)
}

 

 

이 object class는 내가 어디에서 호출할 필요는 없다. 그냥 어딘가에 있는 것만으로 제 역할을 하기 때문에 적당한 곳에 만들어서 놔두면 된다.

이후 인터페이스의 함수를 사용하는 Repository를 만든다.

 

import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class RxGithubRepository @Inject constructor(
    private val githubApi: RxGithubApi
) {
    fun getRepositories(user: String): Observable<List<Repository>> =
        githubApi.getRepositories(user)
}

 

별다른 연산자 없이 정직하게 인터페이스의 함수만 매개변수를 넘겨 호출할 뿐이다. 원한다면 여기서 추가적인 조작을 할 수 있다.

이제 Repository를 사용하는 뷰모델을 만들어 준다.

 

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject

@HiltViewModel
class RxGithubViewModel @Inject constructor(
    private val repository: RxGithubRepository
) : ViewModel() {

    private val disposable = CompositeDisposable()

    fun getRepositories(user: String): Observable<List<Repository>> =
        repository.getRepositories(user)
            .subscribeOn(Schedulers.io())
            .doOnSubscribe { disposable.add(it) }

    override fun onCleared() {
        super.onCleared()
        // 뷰모델이 사라질 때 모든 disposable을 제거 -> 메모리 누수 방지
        disposable.clear()
    }
}

 

RxJava나 RxKotlin을 쓴다면 disposable을 써서 Observable의 구독이 필요없어졌을 때 해당 Observable을 없애주는 게 낫다. 그렇지 않으면 메모리 누수가 발생할 수 있기 때문인데, 자세한 내용은 이전에 작성한 아래 포스팅을 참고하거나 구글링해서 확인해 본다.

 

https://onlyfor-me-blog.tistory.com/415

 

[Rxjava] Disposable이란? CompositeDisposable이란?

Rxjava는 데이터를 발행하는 생산자, 발행된 데이터를 받아 처리하는 소비자로 나눠진 형태로 구성된 Reactive Streams를 바탕으로 하는 라이브러리다. 또한 옵저버 패턴을 확장해서 관찰 대상 객체의

onlyfor-me-blog.tistory.com

 

여기선 ViewModelScope가 종료된 후(=액티비티의 onDestroy() 호출 후)에 호출되는 onCleared()를 오버라이딩해서, 이 함수가 호출될 때 CompositeDisposable에 담긴 Observable들을 모두 제거하고 있다.

onCleared()는 간단히 말하면 뷰모델의 리소스를 정리하고 옵저버를 해제하는 등의 작업을 수행하는 로직을 박아두기 좋은 곳이다. configuration change로 액티비티 / 프래그먼트가 재생성되는 경우에는 뷰모델이 파괴되지 않아서 호출되지 않고, 액티비티 / 프래그먼트가 onDestroy() 상태가 되면 이것과 연결된 뷰모델이 제거되면서 onCleared()가 호출된다.

 

이렇게 뷰모델을 구성했다면 다음은 액티비티다. 물론 데이터 바인딩을 적용했다. BaseActivity의 전체 코드도 같이 올린다. XML 파일에 데이터 바인딩 설정을 하는 걸 잊지 말자.

 

abstract class BaseActivity<T : ViewDataBinding>(
    @LayoutRes private val layoutId: Int
) : AppCompatActivity() {
    protected val binding: T by lazy(LazyThreadSafetyMode.NONE) {
        DataBindingUtil.setContentView(this, layoutId)
    }

    init {
        addOnContextAvailableListener {
            binding.notifyChange()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.lifecycleOwner = this
    }

    override fun onDestroy() {
        binding.unbind()
        super.onDestroy()
    }

    protected inline fun bind(block: T.() -> Unit) = binding.apply(block)
}
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.activity.viewModels
import com.example.kotlinprac.BaseActivity
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityRxKotlinTestBinding
import dagger.hilt.android.AndroidEntryPoint
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import timber.log.Timber

@AndroidEntryPoint
class RxKotlinTestActivity :
    BaseActivity<ActivityRxKotlinTestBinding>(R.layout.activity_rx_kotlin_test) {

    private val viewModel: RxGithubViewModel by viewModels()

    @SuppressLint("CheckResult")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.getRepositories("깃허브 유저 이름 입력")
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { repositories ->
                    Timber.e("수신 : ${repositories.size} 개의 repository 확인")
                    repositories.forEach {
                        Timber.e("레포지토리 이름 : ${it.name}")
                    }
                },
                { error -> Timber.e("에러 : $error") },
                { Timber.e("## 완료") }
            )
    }
}

 

getRepositories()의 매개변수로 깃허브 유저 이름을 꼭 입력해줘야 한다. 안 그러면 에러만 날 뿐이다.

이름도 넣었다면 앱을 실행시켜 본다.아마 아래와 비슷한 형태의 로그가 표시될 것이다.

 

 

Github API는 기본적으로 페이지 별로 최대 30개의 아이템을 반환하는 제한이 있기 때문에, "per_page" 쿼리 파라미터를 추가해서 최대 100개까지 개수 조정을 하지 않는 이상 본인의 레포지토리 개수가 30개 이상일 경우 API 호출로 받는 레포지토리의 개수는 30개가 최대다.

레포지토리 이름을 모두 출력하고 완료 출력까지 정상적으로 작동한 걸 로그로 확인할 수 있다. 대략 이런 식으로 안드로이드에서 레트로핏과 RxKotlin을 같이 사용할 수 있다.

 

마지막으로 사소한 리팩토링만 진행하고 포스팅을 끝낸다. 지금은 액티비티에서 로그로 깃허브 레포지토리의 이름들을 표시하도록 구현했다. 이 로직은 뷰에 위치하는 것보다 뷰모델이나 Repository로 빼고 뷰는 표시하는 것만 담당하게 하는 것이 바람직하다.

여기선 Repository에 getRepositoryNames()라는 함수를 추가한다.

 

@Singleton
class RxGithubRepository @Inject constructor(
    private val githubApi: RxGithubApi
) {
    fun getRepositories(user: String): Observable<List<Repository>> =
        githubApi.getRepositories(user)

    fun getRepositoryNames(user: String): Observable<List<String>> =
        githubApi.getRepositories(user)
            .map { repositories -> repositories.map { it.name } }
}

 

기존 함수는 데이터 클래스 Repository의 리스트를 반환했다면 밑의 함수는 레포지토리의 이름만 담긴 리스트를 반환하는 함수다.

이제 뷰모델에서 새로 만든 함수를 추가한다. 구현 내용은 특별하게 바뀌는 것 없이 똑같다. 함수명과 리턴타입만 신경쓰면 된다.

 

@HiltViewModel
class RxGithubViewModel @Inject constructor(
    private val repository: RxGithubRepository
) : ViewModel() {

    private val disposable = CompositeDisposable()

    fun getRepositories(user: String): Observable<List<Repository>> =
        repository.getRepositories(user)
            .subscribeOn(Schedulers.io())
            .doOnSubscribe { disposable.add(it) }
    
    fun getRepositoryNames(user: String): Observable<List<String>> =
        repository.getRepositoryNames(user)
            .subscribeOn(Schedulers.io())
            .doOnSubscribe{ disposable.add(it) }

    override fun onCleared() {
        super.onCleared()
        // 뷰모델이 사라질 때 모든 disposable을 제거 -> 메모리 누수 방지
        disposable.clear()
    }
}

 

그리고 액티비티의 기존 함수를 지우고 새로 만든 함수를 사용하게 수정한다.

 

@AndroidEntryPoint
class RxKotlinTestActivity :
    BaseActivity<ActivityRxKotlinTestBinding>(R.layout.activity_rx_kotlin_test) {

    private val viewModel: RxGithubViewModel by viewModels()

    @SuppressLint("CheckResult")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.getRepositoryNames("깃허브 유저 이름 입력")
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { repoNames -> Timber.e("수신 : $repoNames") },
                { error -> Timber.e("에러 : $error") },
                { Timber.e("## 레포 이름만 가져오기 완료") }
            )
    }
}

 

이후 앱을 실행해서 로그캣을 보면 아래처럼 표시될 것이다.

 

 

리턴타입을 리스트로 설정했기 때문에 액티비티에서 레포지토리 이름들이 담긴 리스트를 받았다. 이제 이 리스트를 리사이클러뷰에 표시하거나 텍스트뷰에 담아서 해시태그 형태로 표시하는 등 다양한 처리가 가능해졌다.

이걸 응용하면 어떤 기준으로 상위 n개의 레포지토리를 가져온다던가 하는 비즈니스 로직도 간단하게 구현할 수 있다.

반응형
Comments