RxAndroid로 리액티브 앱 만들기 #1

  1. RxAndroid로 리액티브 앱 만들기 #1
  2. RxAndroid로 리액티브 앱 만들기 #2
  3. RxAndroid로 리액티브 앱 만들기 #3
  4. RxAndroid로 리액티브 앱 만들기 #4

이 시리즈는 마이크로소프트웨어에 기고된 내용이 일부 수정되어 포함되어 있습니다.


애플리케이션 개발이 쉽다는 말은 점차 옛말이 되어가고 있습니다. 유즈케이스가 다양해진 만큼 입력 방식도 다양해졌습니다. 가공이 필요한 데이터는 다양한 방식으로 비동기적으로 전달됩니다. 데이터는 사용자에게 즉시 전달 가능한 것과 적절히 프로세싱을 거쳐야 하는 것으로 나누어집니다. 복잡한 요구사항을 만족하기 위해 서버와 클라이언트도 복잡해졌습니다. 오늘날 서버와 클라이언트 코드는 복잡한 제어흐름, 콜백, 상태변수, 이벤트 버스, 대기열, 다양한 디자인 패턴 요소들로 가득합니다. 여러 산발적인 이슈를 관리할 적절한 도구가 절실한 시점입니다. 이런 상황을 개선해줄 도구는 무엇이 있을까요?

새로운 희망

여러 이슈를 처리해줄 적절한 도구는 닷넷 진영에서 등장했습니다. 마이크로소프트는 옵저버 패턴과 LINQ 스타일 문법을 확장하여 비동기처리와 이벤트 기반 프로그래밍을 할 수 있다는 것을 발견하고 연구진은 이를 정립하여 반응형 확장(Rx, Reactive Extensions)을 공개하였습니다.

반응성 확장은 곧 여러 기술 기반 회사들의 호응을 얻었다. 넷플릭스(Netflix)는 Rx를 자바(RxJava) 환경에 옮겼고, 사운드클라우드(SoundColud)의 마티아스 캐플러(Matthias Käppler)는 RxJava를 안드로이드까지 (RxAndroid) 확장합니다.

여러 기술 업체가 Rx를 미는 분위기 속에 안드로이드 오픈소스의 락스타 제이크 와튼(Jake Wharton)은 본인의 트위터에 다음의 문장을 남겨 논란을 일으켰습니다. 반응형 프로그래밍을 한다면 안드로이드 플랫폼 내의 많은 요소들과 여러 라이브러리들이 더 이상 필요가 없다는 이야기입니다.

“Using RxJava to replace loaders and internal lib to replace fragments/activities. All that’s left is views, android.animation.*, and bliss.” by Jake Wharton

물론 세상에는 은탄환은 없고 반응형 프로그래밍 역시 장단점은 있습니다. 이 말은 RxAndroid(RxJava)가 그가 다른 대안을 알아보고 싶지 않을 정도로 강력하고 유연하다는 의미로 받아들일 수 있을 것입니다.

Hello RxAndroid

안드로이드에서 리액티브 프로그래밍을 사용하기 위해서는 먼저 Gradle 환경 설정을 해야합니다. 안드로이드 스튜디오에서 프로젝트를 만들고 app 디렉토리의 build.gradle 파일을 열어 봅시다.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "io.realm.test"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'io.reactivex:rxandroid:1.1.0'
}

그래들 설정에서 가장 중요한 부분은 의존성입니다.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'io.reactivex:rxandroid:1.1.0'
}

안드로이드도 자바 환경이기 때문에 rxjava를 포함하지 않는 것에 의아할 수 있습니다. RxAndorid는 RxJava에 대한 의존성을 가지고 있고 RxAndroid를 의존성에 포함하면 안드로이드 개발 환경에 문제가 없습니다. 혹시 새로운 버전을 적용하길 원한다면 명시적으로 지정할 수 있습니다.

이제 첫 번째 리액티브 안드로이드 앱 코드를 만들어 봅시다.

package io.realm.simpleobservable;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.TextView;

import rx.Observable;
import rx.Subscriber;

public class MainActivity extends ActionBarActivity {
    private static final String TAG = MainActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Observable<String> simpleObservable =
                Observable.create(new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(Subscriber<? super String> subscriber) {
                        subscriber.onNext("Hello RxAndroid !!");
                        subscriber.onCompleted();
                    }
                });

        simpleObservable
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {
                        Log.d(TAG, "complete!");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "error: " + e.getMessage());
                    }

                    @Override
                    public void onNext(String text) {
                        ((TextView) findViewById(R.id.textView)).setText(text);
                    }
                });
    }
}

Observable과 Subscriber를 주목하세요. 데이터의 강을 만드는 옵저버블(Observable)과 강에서 데이터를 하나씩 건지는 서브스크라이버(Subscriber)가 리액티브 프로그래밍의 가장 핵심적인 요소입니다.

옵저버블은 데이터를 제공하는 생산자로 세가지 유형의 행동을 합니다.

  1. onNext - 새로운 데이터를 전달한다.
  2. onCompleted - 스트림의 종료.
  3. onError - 에러 신호를 전달한다

이제 다시 옵저버블 코드를 살펴봅시다.

Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello RxAndroid !!");
        subscriber.onCompleted();
    }
});

옵저버블은 Hello RxAndroid !!란 데이터를 전달하고(onNext) 이내 끝났다는 신호(onCompleted)를 전달합니다. 옵저버블의 세가지 행동들이 만드는 스트림을 도식해 보면 다음과 같습니다.

상단의 흐름은 세번 데이터를 전달받고(onNext) 정상 종료(onCompleted)인 경우이고 하단의 흐름은 두번 데이터를 전달받고(onNext) 에러가 발생(onError)한 경우입니다.

서브스크라이버는 옵저버블이 만드는 스트림에 응대하여 처리할 수 있게 대칭적으로 인터페이스가 구성되어 있습니다.

simpleObservable
    .subscribe(new Subscriber<String>() {
        @Override
        public void onCompleted() {
            Log.d(TAG, "complete!");
        }

        @Override
        public void onError(Throwable e) {
            Log.e(TAG, "error: " + e.getMessage());
        }

        @Override
        public void onNext(String text) {
            ((TextView) findViewById(R.id.textView)).setText(text);
        }
    });

Subscriber는 3가지 메서드를 오버라이드하도록 구성되어 있고 개별 메서드의 역할은 옵저버블의 해당 메서드와 동일합니다. 스트림의 데이터의 유형별로 대칭되는 서브스크라이버의 인터페이스가 대응하는 것입니다. 위의 구성은 정상적으로 데이터가 왔을 때 텍스트 뷰의 항목을 수정하고 종료나 에러가 발생할 때 로그를 남깁니다.

편의를 위한 간단한 서브스크라이버

서브스크라이버를 구성할 때 항상 onCompleted, onError, onNext를 다루는 것은 불편합니다. RxJava는 편의를 위해 사용하지 않는 구성이 누락된 인터페이스를 제공합니다.

simpleObservable
    .subscribe(new Action1<String>() {
        @Override
        public void call(String text) {
            ((TextView) findViewById(R.id.textView)).setText(text);
        }
    });

위의 서브스크라이버는 onNext의 경우만 다루고 있습니다. onNext, onError를 다루는 것과 onNext, onError, onCompleted를 모두 다루는 구성도 준비되어 있습니다.

simpleObservable
    .subscribe(new Action1<String>() {
        @Override
        public void call(String text) {
            ((TextView) findViewById(R.id.textView)).setText(text);
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {

        }
    }, new Action0() {
        @Override
        public void call() {

        }
    });
simpleObservable
    .subscribe(new Action1<String>() {
        @Override
        public void call(String text) {
            ((TextView) findViewById(R.id.textView)).setText(text);
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {

        }
    });

본격적인 리액티브의 세상은 다음 편에서 다루겠습니다.