Mobilization hugo visser cover

누구나 할 수 있는 RxJava, 지금 시작하기!

JVM을 위한 리액티브 익스텐션인 RxJava는 최근 몇년간 안드로이드를 개발하는데 인기있는 도구로 급부상했습니다. 여러 신규 혹은 기존 라이브러리가 Rx를 지원하게 됐으며, 시간이 지남에 따라 등장하게 되는 기존 문제에 대한 해결책으로 “반응형” 솔루션을 추가했습니다. RxJava는 훌륭한 기능과 가파른 학습 곡선으로 유명한데, 만약 아직도 RxJava를 사용하고 있지 않는다면 이 글을 꼭 읽어보세요.

이 글은 RxJava가 무엇인지, 그리고 RxJava로 어떻게 한 단계씩 안드로이드 앱의 문제를 풀어갈 수 있는지에 대한 Mobilization 2016 컨퍼런스의 강연을 옮긴 것입니다. 이 글에서는 또한 RxJava를 사용하면서 하기 쉬운 일반적인 실수를 살펴보고 반응형 앱을 관리하기 위한 팁과 요령을 알려드립니다.


소개 (0:00)

저는 안드로이드 Google Developer Expert(GDE)이자 Little Robots의 독립 안드로이드 개발자입니다. 이번 글에서는 RxJava에 대해 말씀드리겠습니다.

RxJava가 어떤 것인지와 어떻게 RxJava로 개발을 시작할지에 대해 알려드리고, RxJava를 시작하기 위한 팁과 함께 제가 만든 실제 앱의 예제도 보여드리겠습니다.

일반적인 코드 (1:26)


// Everything is awesome and simple! (pulling the result)
public Article getArticle() throws Exception {}

// Asynchronous callback (awaiting the result)
public void getArticle(ArticleCallback callback) {}

// Asynchronous execution w/ callback on main thread
public AsyncTask<Void, Void, Article> fetchArticle() {}

어떤 문제를 풀려 하나요? 전형적인 코드라면 동기적으로 작동하고 있겠죠? 어떤 항목을 얻어오기 위해 간단히 getter를 호출하면 모든 일이 끝납니다.

하지만 실제로 이렇게 단순하지는 않습니다. 이 항목은 네트워크에서 가져와야 할테고, 아마 데이터베이스가 있을 겁니다. 아니면 메인 스레드를 블럭하지 않기 위해 동기적인 호출을 피하고 싶을지도 모릅니다. 이런 경우에 콜백 인터페이스와 같은 것에 의존할 수도 있겠지만 생명 주기를 제어하기 어렵고 일반적으로 취소할 수 없다는 문제가 있습니다.

다수의 콜백을 변경하기 어렵기 때문에 여러 가지 작업을 수행하는 경우라면 모든 콜백을 조정해야만 합니다. 오류 처리를 수행할 표준 방법이 없으므로 사용하는 콜백에는 성공이나 에러 핸들링이 적용됩니다. 이 콜백이 반환될 스레드가 무엇인지도 확인해야 합니다.

안드로이드 코드라면 결과를 메인 스레드에서 반환하고 UI에서 보여주고 싶으시겠죠. AsyncTask를 사용해서 해결할 수도 있을 겁니다. 백그라운드 스레드에서 작업을 실행하도록 해주고, 취소도 가능하며, 메인 스레드에서 결과를 받을 수도 있습니다.

rx.Observables와 에러 핸들링 (2:58)


public Observable<Article> article() {}

하지만 여전히 체이닝과 에러 핸들링 문제가 있습니다. AsyncTask로 에러를 핸들링하는 것은 그다지 깔끔하지 않습니다. 한편 이 문제에 RxJava를 적용하면 옵져버블을 만들어서 항목을 반환하게 합니다. 이 코드를 실행하거나 구독하면 동기적이나 비동기적으로 작동할 수 있습니다.

옵저버블은 에러 핸들링 기능을 내장하고 있습니다. 동기적, 비동기적 방식 모두 동작하며, 취소할 수 있고 에러 핸들링도 가능합니다. 세 가지 이벤트 타입이 있는데요. 먼저 onNext() 메서드 안에서 반환되는 값 이벤트는 이 옵저버블에 의해 항목이 만들어질때마다 콜백을 받게 됩니다. 다음은 에러 이벤트로, 발생하면 옵저버블이 종료됩니다. 이 옵저버블로부터 에러를 받는다면 그 다음 이벤트가 도착하지 않을 수도 있습니다.

완료 이벤트라는 것도 있습니다. 에러없이 스트림이 끝나는 경우입니다. 하나 이상의 항목을 받고, 특정 시점에 스트림이 끝난다면, 이 이벤트가 완료 상황을 알려줍니다.

스트림 (5:11)

옵저버블에서 데이터를 가져오기 위해서는 ‘구독’을 해야합니다. 메서드를 호출한 다음 이를 구독하고 구독자에게 제공하면 됩니다. 구독자에는 onNext, onError, onComplete, 세 메서드가 있습니다.

항목이 만들어질때마다 onNext 이벤트를 받습니다. 옵저버블 내에서 뭔가 처리할 때 발생하는 예외와 같이 문제가 생기면 오류가 발생합니다. 예외 없이 잘 완료된다면 그것도 알려줍니다.

구독 (5:46)


Subscription s = article().subscribe(new Subscriber<Article>() {
   @Override
   public void onNext(Article article) {
       // this can be called multiple times
   }
   @Override
   public void onError(Throwable e) {
       // no more events, and automatically unsubscribed
   }
   @Override
   public void onCompleted() {
       // no more events, and automatically unsubscribed
   }
});

이런 개발 뉴스를 더 만나보세요

구독을 하는 방법은 여러 가지가 있는데, 그 중 하나는 구독자를 서브클래싱하는 것입니다. 일반적으로 내부 클래스를 사용합니다.


Subscription s = article().subscribe(new Action1<Article>() {
   @Override
   public void call(Article article) {
   }
}, new Action1<Throwable>() {
   @Override
   public void call(Throwable throwable) {
   }
}, new Action0() {
   @Override
   public void call() {
   }
});

같은 메서드를 위한 내부 클래스를 사용한다면 다음과 같은 구문을 사용할 수도 있습니다.

Lambda 표현식 (6:10)


Subscription s = article().subscribe(
   article -> { ... },
   error -> { ... },
   () -> { ... });

구독 해제 (6:48)

언젠가는 구독을 해제해야하기 때문에 구독을 추적해둬야 합니다.


s.unsubscribe();

스트림 작업을 완료하면 구독을 해제해야 합니다. 예를 들어 프래그먼트나 액티비티를 사용하는 경우 생명주기를 처리하던 구독을 중지한다면 이벤트를 만드는 옵저버블을 취소해야겠죠. 구독을 취소하지 않는다면 메모리 누출이 생길 수 있습니다. 특히 앞서와 같이 내부 클래스를 사용하는 경우라면 콜백을 선언한 프래그먼트나 액티비티에서 가지고 있던 참조를 잘 유지해야 구독을 해제할 수 있습니다.

어디선가 구독을 했다면 구독을 해제해야 합니다. 메모리가 누출되는지 실제로 확인하기는 어렵지만 항상 그렇다고 생각하고 대비하는 것이 좋습니다.

학습 곡선 (8:25)

RxJava는 복잡하고 학습 곡선이 가파르다고 여겨지는데, 이는 구문이 어렵기 때문이 아니라 RxJava를 애플리케이션에 적용하려면 구조적인 변화가 필요하기 때문입니다.

RxJava를 적용하려면 앱에 대한 생각을 전환해야 합니다. 어떤 개발자가 앱에 HTTP 호출 라이브러리를 적용한다면, 그 의도는 직접 작성할 코드를 줄이려는 것이겠죠. 직접 같은 일을 하는 20줄의 코드를 작성할 수도 있지만 멋진 라이브러리를 적용해서 한 줄만 사용할 수 있습니다. 같은 맥락으로 RxJava를 적용하는 것은 이런 작업으로, 단지 방향성이 좀 다를 뿐입니다.

스트림 vs 단일 값 (9:15)

단일 콜백 대신에 이벤트 스트림을 생각해야 합니다. 또한 이 옵저버블이 이벤트를 하나만 발생시킬지, 여러 개를 발생시킬지와, 스트림이 완료될 것인지도 고려해야 합니다.

RxJava의 기능적인 스타일이 있는데, 어떤 타입이 불변일까요? 어디서 스트림의 모든 오퍼레이션을 체인화할까요? 옵저버블 자체는 간단하지만 오퍼레이터를 사용해서 스트림을 조작하는데서 그 강력한 기능이 생겨납니다. 저는 일반적으로 10개 가량의 오퍼레이터를 사용하지만 더 많은 오퍼레이터들이 있고 이를 조합할 방법도 다양합니다.

정말 다양한 조합이 가능하며, 사실 이것이 어떤 오퍼레이터를 어떻게 조합하는 것이 좋은지에 대한 팁이 필요한 이유입니다.

문서 (10:54)

문서도 RxJava를 배우는 것을 어렵게 합니다. 좋은 문서가 많이 있지만, 처음 보는 사람에게는 이해하기 어려울 수 있습니다.

멋진 다이어그램으로 이해를 돕는 사이트가 있는데, 오퍼레이터 동작을 이해하는데 도움이 될 겁니다. 옵저버블에서 오는 이벤트를 이해하기 쉽게 보여줍니다.

flatMap (11:42)

flatMap은 이벤트를 받아서, 내보내는 모든 이벤트에 대해 하나 이상의 이벤트를 생성할 수 있는 새 옵저버블을 만듭니다. 예를 들어 아래 그림의 빨간색 원은 두 개의 다이아몬드로 변형됩니다. 녹색과 파란색에 대해서도 똑같이 적용되지만, 이들이 꼭 순서대로 발생하는 것은 아닙니다.

RxJava-flatMap

쉬운 것부터 시작하기 (12:30)

RxJava를 시작할 때 모든 것을 이해하려고 하면 정말 어려우므로, 쉬운 것부터 시작하기를 권합니다. 모든 것을 반응형으로 바꾸려면 실패할 확률이 높으므로 필요해 보이는 것에부터 적용하세요.

도메인 레이어나 비즈니스 로직부터 적용하기 시작해 보세요. 데이터 전송, REST와 같은 HTTP 호출에도 굉장히 효과가 좋습니다. 많은 분들이 Retrofit을 사용하다가 RxJava를 알게 되는데, 이런 옵저버블을 얻기 쉽고 한번 구독하면 HTTP 호출을 처리해서 결과를 전해 줍니다.

이벤트버스로 RxJava 사용

RxJava는 이벤트를 보낸다는 점에서 이벤트버스와 유사합니다. 하지만 이벤트를 변환하고 관심있는 특정 이벤트를 구독할 수 있는 권한을 추가해서 보다 강력하죠.

항목을 rfidTag 문자열로 변환 (15:28)


public Observable<String> rfIdTags(Observable<Article> observable) {
   // map = convert an object to another object
   return observable.map(article -> article.rfIdTag);
}

이 예제는 RFID 스캐너를 사용해서 상점에서 재고를 파악하는 앱에서 가져왔습니다. 티셔츠일 수도 있고 아닐 수도 있는 이 항목에는 태그가 있습니다. 어떤 항목이 실제로 가게에 있는지 확인해 보고 싶을 때, RxJava를 적용할 수 있을만한 부분은 항목의 옵저버블을 RFID 태그로 다시 변환하는 곳입니다. 태그의 스트림을 알고 싶은 상황이겠죠.

위 예제처럼 항목 옵저버블에 간단한 오퍼레이터를 적용해볼 수 있습니다. 현 단계에서는 어디서 온 것인지는 중요하지 않습니다.

RxJava-convert

이제 항목을 RFID 태그와 문자열로 매핑할 겁니다. 항목을 가지고 이를 맵을 사용해서 문자열로 변환하는 것을 볼 수 있는데요.

아주 간단하지만 예전 것을 래핑해서 새로운 옵저버블로 만드는 것을 파악하는 것이 중요합니다. string.substring을 사용하면 새로운 문자열을 만든다는 점에서 문자열의 작동과 비슷한데, 옵저버블도 마찬가지입니다. 하지만 나중에 이런 오퍼레이션을 체이닝할 것이라는 점을 염두해 주세요. 이것을 구독하면 각 항목마다 콜백을 받게 되지만, 항목 그 자체가 아닌 RFID 태그가 돌아옵니다.


Observable<String> newObservable = observable.map(article -> article.rfIdTag);

동일 항목 수량 세기 (18:06)

고유한 RFID를 갖고 있는 항목들이 같은 항목일지도 모릅니다. 10장의 붉은색 티셔츠가 있는데 모두 다른 RFID가 있는 경우를 생각해 볼까요? 항목 ID로 이들을 그룹화한 후 각 그룹의 항목 수를 계산합니다. 그 다음 항목 수량을 내보내면서 새 객체를 만들려고 합니다.


public Observable<ArticleQuantity> quantity(Observable<Article> observable) {
    return observable.filter(article -> article.isInStock).
    groupBy(this::articleIdFromTag).
    // convert each group to a new observable
    flatMap(group ->
        // each item in the group is converted to ArticleQuantity
        group.map(articleInfo -> new ArticleQuantity(group.getKey(), 1)).
        // then these items are reduced to a single item per group
        reduce((q1, q2) ->
            new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity))
    );
}

일단 재고가 없는 모든 항목을 필터링합니다. 이 필터 오퍼레이터는 새 옵저버블을 만들고 연결한 후 재고가 없는 모든 항목을 필터링합니다.

RxJava-notation

다음으로 항목의 RFID 태그를 사용해서 모든 항목을 그룹화합니다. 위 노테이션은 기사를 가져와서 기사 ID를 반환하는 메서드를 호출한다는 의미입니다.

groupBy (19:45)

RxJava-count-group

groupBy 함수는 이제 새 값을 내보내게 되는데 정확하게는 옵저버블을 위한 새 옵저버블입니다. 위 오퍼레이션을 적용하고 나면 GroupObservable<String, Article>을 가지는 옵저버블을 사용할 수 있습니다. 문자열을 키로 삼아 파악하려는 항목이 보이고 수량이 나옵니다.

RxJava-groupby

이제 그룹을 만들었으니 GroupObservableflatMap을 적용합니다. 각 그룹을 실제로 모두 처리할겁니다. 그룹을 매핑해서 새 객체로 만듭니다.

항목을 가져와서 원하는대로 그룹화하고 거의 최종 형태의 객체, 즉 수량으로 만들었습니다. 항목 ID를 가지고 각각의 수량을 알아냈지만 아직 고유하지는 않습니다.

Reduce (22:09)

이제 그룹 내의 모든 항목을 더하기 위해 reduce라는 오퍼레이터를 사용합니다. 이 오퍼레이터는 두 개의 객체를 받아서 한 개의 객체를 생성합니다.

RxJava-count-group-2

같은 그룹에 있으므로 q1, q2의 ID는 같을테니 받아온 ArticleQuantity에서 첫 번째(q1)의 ArticleID를 가져오고, 그 다음 이 둘의 수량을 더할 겁니다.

모든 작업이 끝나면 그룹마다 고유하게 ArticleQuantity를 내보내는 옵저버블이 됩니다. 즉, 그룹에 각각 몇 개가 있는지 알 수 있죠. 이처럼 데이터를 처리하는 것만으로 강력한 기능을 만들 수 있습니다.

이제 보다 많은 기능을 만들어야 하는 상황으로 넘어가보죠. 예측 재고라는 새로운 객체를 만들어볼 겁니다.


Observable<ExpectedStock> expectedStock(Observable<Article> articles) {
    Observable<Article> observable = articles.publish().autoConnect(2);

    return Observable.combineLatest(rfIdTags(observable).toList(),
        quantity(observable).toList(), (rfidTags, quantities) ->
        new ExpectedStock(rfidTags, quantities));
}

앞서 만든 함수를 결합하면 간단히 우리가 본 모든 RFID 태그를 결합할 수 있습니다. 이를 리스트로 변환하고, 수량에 대해서도 동일하게 적용할 수 있습니다.

여기서 팁을 드리자면 원본 옵저버블을 가져와서 연결 가능한 옵저버블로 만든 후, 두 개의 구독자를 만든 이후에만 값을 내보낼거라고 알려줄 수 있다는 것입니다.

연결 가능한 옵저버블은 기본적으로 하나의 옵저버블을 여러 구독자에게 동시에 보내는 브로드캐스트 기능을 합니다.


ConnectableObservable<Article> connectable = observable.publish();

Subscription s1 = connectable.subscribe();
Subscription s2 = connectable.subscribe();

connectable.connect();

두 개의 구독자가 만들어지는 즉시 값이 생산되기 시작합니다. 아래 코드가 동작하는 원리죠.


ConnectableObservable<Article> connectable = observable.publish().autoConnect(2);

Subscription s1 = connectable.subscribe();
Subscription s2 = connectable.subscribe();

// happens automatically now
// connectable.connect();

RFID 태그로 두 구독자가 있는걸 알 수 있고 목록으로 변환할 수 있는 수량도 있습니다. 이렇게 옵저버블이 네트워크 호출을 유발할 때 효과적으로 사용할 수 있습니다. 두 개의 네트워크 호출이 일어나지 않고 한 개만 발생해서 효율적입니다. RxJava를 적용하기에 데이터 프로세싱이 아주 좋다는 것을 알 수 있죠.

스케줄러 (26:28)

RxJava는 스케줄러라는 강력한 개념을 가지고 있습니다. 이를 이용해서 어떤 트랙 상황이 발생하고 있는지를 제어할 수 있습니다. 특히 안드로이드는 실제로 차단하기 어렵기 때문에 이 기능이 유용하게 쓰입니다.

Schedulers.io(), Schedulers.computation()이 있고 executer에서 사용자가 만들 수도 있습니다. 안드로이드를 위한 RxAndroid 프로젝트에서는 메인 스레드에서 작업을 수행하는 스커줄러를 제공합니다.

subscribeOnobserveOn 이라는 두 오퍼레이터는 스케줄러를 적용하는 방법을 제어하는 오퍼레이터입니다.

subscribeOn

subscribeOn에 대해 설명하는 몇몇 글이 좀 혼란스럽긴 하지만, 핵심은 기본적으로 한 번만 적용할 수 있다는 점입니다. 옵저버블에 연쇄적인 오퍼레이터가 있다면 subscribeOn를 단 한 번만 사용할 수 있습니다. 그 이유는 subscribeOn은 구독이 일어나는 스레드에 대한 subscribeOn 컨트롤을 제공하기 때문입니다. 구독을 호출하는 순간 새 스레드가 생성되고 구독에 필요한 모든 작업을 생성합니다. 이를 여러번 적용해서 여러 개의 스레드가 생겨도 결국 첫번째 subscribeOn이 끝나는 곳에서 끝나기 때문에 실제로 효과가 없습니다.

observeOn

한편 observeOn은 이벤트가 어디서 발생하는지 제어합니다. 일반적으로 메인 스레드에 사용해서 결과를 얻으면 다시 메인 스레드에서 얻게 됩니다. 그런 다음 뷰를 안전하게 업데이트할 수 있습니다.


observable.map(...).subscribeOn(io()).observeOn(mainThread()).flatMap(...);

이 예제에서는 map이 IO 스레드에서 동작하고 map의 결과가 돌아오면 메인 스레드로 교체해서 flatMap 등의 다음 오퍼레이터로 이어갑니다. 일반적으로 이를 마지막 오퍼레이터로 사용하고 구독을 적용합니다.

여기서 주의할 점은 어떤 오페러이터는 자신의 스케줄러를 대신 적용한다는 점입니다. 이 점을 알지 못하면 가끔 이런 모든 작업을 올바르게 수행했더라도 스케줄러를 적용할 때 갑자기 잘못된 스레드에서 뷰를 가져오는 결과를 보고 놀랄 수 있습니다. 예를 들어 timer()debounce() 등의 오퍼레이터는 적용할 스케줄러를 지정하지 않으면 다른 스케줄러를 사용할 수 있으니 주의해야 합니다.

이 정도가 스케줄러와 옵저버블, 오퍼레이터로 RxJava를 시작하기 위해 알아둬야할 기본 요소입니다.

흔한 실수 (30:06)

최근까지 코드 리뷰를 하면서 다음과 같은 실수들을 종종 발견하곤 했습니다.

구독 (30:16)


public class MyActivity extends Activity {
    private CompositeSubscription mSubscriptions = new CompositeSubscription();
    @Override
    protected void onStart() {
        super.onStart();
        mSubscriptions.add(createObservable().subscribe(...));
    }
    @Override
    protected void onStop() {
        super.onStop();
        // use clear(), not unsubscribe()
        mSubscriptions.clear();
    }
}

구독한 레퍼런스를 유지하지 않는다면 메모리가 누출될 수 있으므로 이를 방지하기 위한 팁을 알려드리겠습니다. CompositeSubscription()이라는 객체는 모든 구독을 보관할 수 있는 백업용 구독입니다.

구독을 추가하고 끝나면 clear()를 호출해서 모든 구독을 취소하고 모든 오퍼레이션을 취소할 수 있습니다. unsubscribe() 메서드도 있지만 사용하지 않길 권합니다. 이를 사용하면 액티비티가 재활성화될때 다시 구독을 추가하려는 의도된 동작이 일어나도 즉시 구독 취소를 해버립니다.

에러 핸들링 (31:38)


// Oops, no error handling!
createObservable().subscribe(value -> {  })

// errors are handled here
createObservable().subscribe(value -> {  }, error -> {  })

에러 핸들러를 구독하고 제공하는지 항상 확인해야 합니다. 그렇지 않으면 특히 스케줄러를 적용할 때 스택 트레이스에 아무것도 없을 수 있습니다. 물론 RxJava 절에서 뭔가 잘못됐다고 알려주긴 하지만 어디서 발생했는지 찾을 방법이 없습니다. 항상 에러 콜백을 사용하고, 에러가 발생한다면 에러를 로그로 남겨서 예상치 못한 오류를 기록해야 합니다.

제가 만든 RxLint를 사용하면 에러 핸들러가 빠진 곳이 어디인지 표시해 줍니다.


public void fetchValueFromTheNetwork() {
    createObservable().subscribeOn(Schedulers.io()).
        observeOn(AndroidSchedulers.mainThread()).
        subscribe(value -> {
            EventBus.getDefault().post(new ValueEvent(value));
        }, error -> {
            EventBus.getDefault().post(new ValueErrorEvent(error));
        });
}


public Observable<String> fetchValueFromTheNetwork() {
    return createObservable().subscribeOn(Schedulers.io()).
                              observeOn(AndroidSchedulers.mainThread());
}

mSubscriptions.add(fetchValueFromTheNetwork().subscribe(value -> {  },
                                                        error -> {  }));

AsyncTask처럼 구현하고 싶어하는 사람들도 있습니다. 위 두 가지 예제 중에 전자처럼 만드는 경우인데, 옵저버블을 만들고 구독한 뒤 여러 오퍼레이터를 적용합니다. 그러면 구독 중에 이상한 일들이 벌어지게 되므로, 또 EventBus로 가서 이벤트를 포스트하게 되죠. 만약 이런 코드를 만들고자 한다면 그냥 아래처럼 AsyncTask를 쓰세요.

후자처럼 옵저버블을 만들고 구독하면 됩니다. 이런 방식으로 RxJava를 적용하는 것이 좋습니다.

스트림 끊어버리기 (34:08)


createObservable().map(value -> {
// this is not the place to subscribe or call
// other functions.
createAnotherObservable().subscribe(  );
return value;
});

또다른 흔한 실수는 “스트림 끊어버리기”입니다. 예를 들어 map 오퍼레이터가 있고, 매핑해서 들어오는 값으로 뭔가를 하고 싶은 경우에 새 옵저버블을 또 만들고 구독을 해버립니다. 이런 경우 온갖 문제가 발생하게 되죠.

이 대신 얻은 값을 기반으로 새 옵저버블을 만들 수 있게 하는 flatMap을 사용하세요. 그 다음 flatMap에서 만든 것을 반환하면 됩니다.

(35:33)


// this gets boring and cumbersome
retrieveArticle().observeOn(Schedulers.io()).subscribeOn(AndroidSchedulers.mainThread()).subscribe(article -> {});

체이닝 스타일로 만들어낸 옵저버블을 분해해보세요. 주의를 충분히 기울이지 않으면 체인이 심하게 늘어나고 엄청난 수의 오퍼레이터를 만들어낼 수 있습니다.


/**
* Retrieve a single article. Will return the result on the main thread
* @return the observable
*/
public Observable<Article> retrieveArticle() {
    return articleHttpCall().
        subscribeOn(Schedulers.io()).
        observeOn(AndroidSchedulers.mainThread());
}
// does the right thing
retrieveArticle().subscribe(article -> {  },  );

또 다른 팁은 의미있는 곳에서 스케줄러를 적용하라는 겁니다.

위와 같은 API을 사용하는 항목 검색 앱에서 항상 이런 작업이 일어난다면 API에서 스케줄러를 설정하는 것이 좋습니다. 다만 올바른 스레드에서 반환되는 것을 한 번 확인한 후에, 앱의 다른 부분에서도 잘 동작하고 있는지 확인하고 싶은 경우라면 전체 subscribeOn, observeOn 구현을 매번 해볼 필요는 없습니다. 이렇게 해야 코드의 가독성을 높일 수 있고 나중에 쉽게 스케줄러를 변경할 수 있습니다.

결론 (37:36)

제가 생각하는 RxJava는 강력하지만 어떤 것을 다르게 할 수 있는 방법이라고 생각합니다. 많은 프로젝트들이 사용하는만큼 좋긴 하지만, 처음 적용하는 분이라면 단계별로 천천히 적용하는 것을 추천합니다. 실제로 풀어야할 것을 단계별로 해결해내기 전에는 섣불리 전체 리액티브 방식을 적용하지 마세요.

콜백이나 AsyncTask만으로도 충분한 경우가 아직 많습니다. 모든 AsyncTask를 다 없애고 RxJava를 완벽히 적용할 수 있을거라고 생각하지 마세요. 조금씩 천천히 배워나가면서 문서를 확인해보고 어떤 오퍼레이션 조합이 적당한지 파악하시는게 좋습니다.

Q&A (39:05)

Q: RxJava를 사용했을 때와 안했을 때 복잡성과 코드 줄 수 등을 비교해본 프로젝트가 있나요?

Hugo: 명확하게 비교해보지는 않았지만, 사실 RxJava는 양날의 검과 같다고 생각합니다. 간결하고 강력하고 작성할 수 있지만 어떤 사람들에게는 이해하기 어렵기 때문에 비용이 더 생겨날 수도 있습니다. RxJava는 특히 데이터 처리 등의 일을 할 때 더 강력하게 작용합니다. 만약 제대로 된 방식으로 디자인하고 난 이후에는 코드양이 확실히 줄어들 겁니다.

팀으로 일한다면 팀원 전체가 같은 수준에서 이해해야 합니다. 지나치게 RxJava를 신봉하는 사람이 있는 팀이라면 좋지 않겠죠. 실제로 필요한 것보다 너무 많이 RxJava를 사용하는 경우가 많습니다.

Q: 가독성을 높이기 위해 커멘트를 사용하나요? 아니면 다른 방법이 있을까요?

Hugo: 어떤 코드의 가독성을 높이기 위해서라면 앞서 말한대로 옵저버블을 조합하려고 노력합니다. 개인적으로는 옵저버블 근처에 콜백을 놓는 것을 좋아합니다. 내부 클래스도 마찬가지로 옵저버블 근처에 놓죠.

제가 드릴 수 있는 최고의 팁이라면 변화를 짧게 유지하고 다양한 옵저버블을 조합하라는 것입니다. 각각의 옵저버블이 하는 일에 대해 파악하기 쉽죠. flatMap으로 만든 옵저버블이 있다면 해당 옵저버블을 생성하기 위한 새 함수를 만들었겠죠? 이렇게 만들면 별도의 단계를 더 잘 테스트할 수 있습니다.

Q: 맨처음 옵저버블에 대해 설명하면서 옵저버블이 오류를 받으면 그것을 처리한 다음 옵저버블을 완료할 수 있다고 했는데요. Rx 초보자로써 에러를 처리하고 사용자에게 피드백도 주지만 동시에 구독도 유지하고 싶습니다. 어떻게 하면 될까요?

Hugo: 옵저버블에서 오류가 발생해도 이벤트를 계속 만들고 싶다면 몇 가지 방법이 있습니다. 먼저 onErrorResumeNext를 사용하면 됩니다. 에러를 만들고 onErrorResumeNextretry로 연결하는 겁니다. 그 다음 에러를 처리하는데, 에러는 구독자에게는 보고되지 않으며, 특정 오류에 대해서만 다시 시도하고 다른 옵저버블이 구독자에게 전달하도록 조정할 수 있습니다.

Q: 필터와 일치하는 결과에 작업을 수행하고, 다른 아이템에는 다른 작업들을 수행하고 싶은 경우 RxJava를 사용하는 시나리오는 어떻게 되나요?

Hugo: 저라면 AutoConnect처럼 할 겁니다. 옵저버블을 가지고 특정 경우에 필터를 걸고, 다른 경우에는 그대로 놔둡니다. 이렇게 하면 어떻게 처리하고 싶은지에 따라 이를 조합하거나 같은 일을 하는 두 개의 구독자를 만들 수 있습니다. 스트림에 필터를 건다면 필터를 걸게 될 뿐, 같은 시점에서 필터를 걸고 다시 필터를 풀 수는 없습니다. 결국 같은 스트림을 두 개의 스트림에 브로드캐스팅해야 합니다.

다음: Realm Mobile Platform으로 실시간 협업 기능과 확장이 가능한 리액티브 앱을 만들어 보세요.

General link arrow white

컨텐츠에 대하여

2016년 10월에 진행한 Mobilization 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

Hugo Visser

Hugo는 기업용, 데스크탑용 및 모바일 소프트웨어 제품을 개발해온 소프트웨어 엔지니어입니다. 안드로이드가 소개된 이후, 안드로이드 개발만 해 왔으며, 2009년 첫 앱을 출시했습니다. 또한 개발자가 더 나은 소프트웨어를 개발할 수 있도록 여러 오픈 소스 라이브러리와 도구를 출시했습니다. 안드로이드 Google Developer Expert(GDE)로, 네덜란드 안드로이드 사용자 그룹의 공동 주최자입니다. 그와 그의 회사인 Little Robots는 가능한 더 나은 방식으로 안드로이드를 사용하는 방법에 대해 주력하고 있습니다.

4 design patterns for a RESTless mobile integration »

close