대메뉴 바로가기 본문 바로가기

데이터 기술 자료

데이터 기술 자료 상세보기
제목 RxAndroid로 반응형 앱 개발하기 : 안녕하세요. 반응형 프로그래밍
등록일 조회수 4778
첨부파일  

RxAndroid로 반응형 앱 개발하기

안녕하세요. 반응형 프로그래밍



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



여러 이슈를 처리해 줄 적절한 도구는 닷넷 진영에서 등장했다. 마이크로소프트는 옵저버 패턴과 LINQ 스타일 문법을 확장해 비동기처리와 이벤트 기반 프로그래밍을 할 수 있다는 걸 발견한다. 연구진은 이를 반응형 확장(Rx, Reactive Extensions)으로 공개했다 (https://msdn.microsoft.com/en-us/data/gg577609).

반응형 확장은 곧 여러 기술 기반 회사들의 호응을 얻었다. 넷플릭스(Netflix)는 Rx를 자바(RxJava) 환경에 옮겼고, 사운드클라우드(SoundColud)는 RxJava를 안드로이드(RxAndroid)까지 확장시켰다. 깃허브(Github)는 Rx가 아이폰과 맥(RxCocoa)에서 작동하도록 만들었다. 여러 기술 업체가 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(출처 : twitter.com/jakewharton/status/385898996884971520).

반응형 프로그래밍을 한다면 안드로이드 플랫폼 내의 많은 요소들과 여러 라이브러리들이 더 이상 필요 없다는 이야기였다. 물론 세상에 모든 걸 해결해 주는 만능 ‘은탄환’이란 있을 수 없다. 반응형 프로그래밍 역시 장단점이 존재한다. 제크 와튼의 말은 RxAndroid(RxJava)가 다른 대안을 알아보고 싶지 않을 만큼 강력하고 유연하다는 의미로 받아들일 수 있다.



Hello RxAndroid

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



<리스트 1> app/build.gradle 설정파일 apply plugin: 'com.android.application' android { compileSdkVersion 21 buildToolsVersion "21.1.2" defaultConfig { applicationId "rx.android.gdg.kr.simpleobservable" minSdkVersion 11 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' compile 'io.reactivex:rxandroid:0.24.0' }



그래들 설정에서는 의존성 부분이 매우 중요하다. 두 가지 외부 의존성이 있는 것을 볼 수 있다. appcompat-v7와 rxandroid이다. 안드로이드도 자바 환경이기 때문에 RxJava를 포함하지 않는 것에 의아할 수 있다. RxAndorid는 RxJava에 의존성을 가지고 있다. RxAndroid를 의존성에 포함하면 안드로이드 개발을 재개할 수 있다. 첫 번째 반응형 안드로이드 앱 코드를 만들어보자.



<리스트 2> 의존성 설정 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' compile 'io.reactivex:rxandroid:0.24.0' }



<리스트 3> 반응형 Hello World package rx.android.gdg.kr.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); } }); } }



<리스트 3>의 코드에서 Observable과 Subscriber를 주목하자. 데이터의 강을 만드는 옵저버블(Observable)과 그것을 하나씩 소화하는 서브스크라이버(Subscriber)가 반응형 프로그래밍의 가장 핵심적인 요소이다. 옵저버블은 데이터를 만드는 생산자로 다음의 세 가지 행동을 한다. 이제 다시 옵저버블 코드를 살펴보자.

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



<리스트 4> Hello World 옵저버블 Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.onNext("Hello RxAndroid !!"); subscriber.onCompleted(); } });



<리스트 4>의 옵저버블은 “Hello RxAndroid !!”란 데이터를 전달하고(onNext) 이내 끝났다는 신호(onCompleted)를 전달한다. 옵저버블의 세 가지 행동들이 만드는 스트림을 도식해 보면 <그림 1>과 같은 모양이 된다.





<그림 1> 상단의 흐름은 3번 데이터를 전달받고(onNext) 정상 종료(onCompleted)인 경우이고, 하단의 흐름은 2번 데이터를 전달받고(onNext) 에러가 발생(onError)한 경우다. 서브스크라이버는 옵저버블이 만드는 스트림에 응대해 처리할 수 있게 대칭적으로 인터페이스가 구성돼 있다.



<리스트 5> 서브스크라이버의 예 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); } });



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



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

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



<리스트 6> 단출한 서브스크라이버 simpleObservable .subscribe(new Action1< String>() { @Override public void call(String text) { ((TextView) findViewById(R.id.textView)).setText(text); } });



<리스트 6>의 서브스크라이버는 onNext의 경우만 다루고 있다. onNext, onError를 다루는 것과 onNext, onError, onCompleted를 모두 다루는 구성이 준비돼 있다.



<리스트 7> 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() { } });



<리스트 8> onNext, onError를 다루는 구성 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) { } });



데이터 가공 Map

map은 한 데이터를 다른 데이터로 바꾸는 오퍼레이터이다. 원본의 데이터는 변경하지 않고 새로운 스트림을 만들어 낸다. <그림 2>는 스트림의 데이터를 각각 10씩 곱하는 예이다.



이렇게 map을 사용하려면 인자 하나를 받아 값을 10배 곱해 반환하는 메소rcebox> <리스트 9> 값을 10배 씩 곱하는 map .map(new Func1< Integer, Integer>() { @Override public Integer call(Integer value) { return value * 10; } })



RxAndroid를 사용해 앱을 개발할 때 미리 준비된 다양한 RxJava 오퍼레이터를 사용할 수 있다(reactivex.io/documen tation/operators.html). 반응형 프로그래밍에서 오퍼레이터는 기존에 작성된 메소드를 재사용해 전체 값을 변경할 수 있는 도구를 제공한다. 값 재사용이 가능한 함수를 이용해 가공한다는 점은 함수형 프로그래밍과 일맥상통하는 부분이 있다. 문자열을 대문자로 바꾸는 것도 쉽게 구현할 수 있다.



<리스트 10> 문자열을 대문자로 바꾸는 map simpleObservable .map(new Func1< String, String>() { @Override public String call(String text) { return text.toUpperCase(); } }) .subscribe(new Action1< String>() { @Override public void call(String text) { ((TextView) findViewById(R.id.textView)).setText(text); } });



<리스트 10>은 옵저버블의 스트림을 대문자로 변환하는(사실은 새로운 스트림을 만드는) map을 호출하고, 그것을 서브스크라이버에 연결한다. map을 이용할 때 같은 타입으로 변경해야만 할 수 있는 건 아니다.



<리스트 11> String 스트림을 Integer 스트림으로 변환하는 map simpleObservable .map(new Func1< String, Integer>() { @Override public Integer call(String text) { return text.length(); } }) .subscribe(new Action1< Integer>() { @Override public void call(Integer length) { ((TextView) findViewById(R.id.textView)).setText("length: " + length); } });



단일 데이터, 컬렉션 옵저버블 생성을 위한 유틸리티

simpleObservable은 문자열 하나만 전달하는 간단한 옵저버블인데, 옵저버블을 쉽게 만들 수 있도록 도와주는 유틸리티 메소드가 있다.



<리스트 12> 단일 데이터로 옵저버블을 생성하는 유틸리티 Observable.just Observable simpleObservable = Observable.just("Hello RxAndroid");



연속적인 데이터를 처리하기 위한 유틸리티 Observable.from도 있다. 이 유틸리티를 이용하면 배열이나 컬렉션을 보다 간편하게 처리할 수 있다.



번잡한 문법을 람다로 간단히

기존의 자바 문법에서 RxAndroid(RxJava) 프로그래밍을 할 때 로직과 상관 없는 요소들이 많이 발견된다. 어노테이션, 클래스 선언, 메소드 선언 등의 요소들이 코드에서 반복된다. 이런 반복들은 자바 8에서 도입된 람다를 사용하면 손쉽게 제거할 수 있으며, 그를 통해 간결하고 가독성이 좋은 코드를 만들 수 있다.



<리스트 13> 람다로 간략화한 코드 Observable< String> simpleObservable = Observable.just("Hello Lambda!!"); simpleObservable .map(text -> text.length()) .subscribe( length -> ((TextView) findViewById(R.id.textView)).setText("length: " + length));



이로써 자바 코드가 매우 간결해진 것을 볼 수 있다. 람다 문법이 생소한 독자를 위해 코드 단위로 비교하며 설명한다.



<리스트 14> 람다를 적용하기 전 map 코드 .map(new Func1< String, Integer>() { @Override public Integer call(String text) { return text.length(); } })



람다는 단일 메소드를 가지고 있는 경우에만 간략하게 할 수 있다. 여기서 메소드 인자 부분 이전의 번잡한 내용을 지워보자. 인자의 타입을 추론할 수 있다면, 이 또한 생략할 수 있다.



<리스트 15> 익명 객체 부분을 제거한 람다 map 코드 .map((String text) -> { return text.length(); })



<리스트 16> 익명 객체 부분과 타입을 제거한 람다 map 코드 .map((text) -> { return text.length(); })



인자가 하나인 경우에는 괄호를 생략할 수 있다. 또 람다 블록 내에 문장이 하나이고 리턴값을 가진다면 블록, 세미콜론, 리턴 키워드를 지울 수 있다.



<리스트 17> 블록을 생략한 람다 map 코드 .map(text -> text.length())



람다의 단 한 가지 단점은 자바 8이상에서만 쓸 수 있다는 점이다. 안드로이드는 자바 8을 지원하지 못하며, 람다를 쓰기 위해서는 이전 버전에서 람다를 쓸 수 있게 하는 포팅 라이브러리를 사용해야 한다.



람다 포팅 중 안드로이드에 적합한 라이브러리는 레트로람다(Retrolambda) 라이브러리이다(github.com/orfjackal/retrolambda). 레트로람다 라이브러리는 속도 상의 약점이 없으며 기술 선도 기업들이 2013년부터 안드로이드에 테스트한 라이브러리다. 레트로람다 라이브러리를 사용하기 위해서는 그래들 코드의 설정 수정이 필요하다.



<리스트 18> 레트로람다를 적용한 그래들 파일의 예 buildscript { repositories { jcenter() } dependencies { classpath 'me.tatarka:gradle-retrolambda:2.5.0' } } apply plugin: 'com.android.application' apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 21 buildToolsVersion "21.1.2" defaultConfig { applicationId "rx.android.gdg.kr.java8lambda" minSdkVersion 11 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' compile 'io.reactivex:rxandroid:0.24.0' }



자바 8과 옛 버전을 함께 쓰고자 한다면 Gradle Retrolambda Plugin 프로젝트(github.com/evant/gradle-retrolambda)를 참조하자.



반응형 프로그래밍 도입으로 사용자 경험 향상

이번 글에서는 반응형 프로그래밍의 가장 기초적인 개념 세 가지로 옵저버블, 서브스크라이버, 오퍼레이터을 소개했다. 또 그 개념을 바탕으로 데이터 스트림을 간단히 연결하고 변형하는 방법을 살펴봤다. 다음 시간에는 다양한 옵저버블과 오퍼레이터 등을 합성해 사용자 인터페이스와 상호작용하는 방법을 알아볼 것이며, 사용자 인터페이스 코드도 구조화해 볼 것이다. 이 글에서 사용된 코드 예제는 깃허브 저장소(github.com/GDG-Korea/HelloRx)에서 볼 수 있으니 참고하자.



출처 : 마이크로소프트웨어 4월호

제공 : 데이터전문가 지식포털 DBguide.net