Overview

브랜디는 현재 2.0 기반 Android 버전입니다. Main Thread와 Sub Thread 사이의 ANR를 방지하려고 Volley, Otto Bus Library를 사용해서 백엔드 서비스(back-end Service)를 연동하고 있습니다.

이제 3.0 개발로 더 좋은 백엔드 서비스 기능을 만들려고 합니다. (기반 작업은 이미 완료했습니다.) 다만 3년 동안 브랜디 앱을 개발하면서 느꼈던 고통과 피로를 ‘제발’ 줄여보고 싶어서 브랜디 3.0에서는 Retrofit2 와 RxJava, Lambda 표현식을 사용하기로 했습니다.

RxJava(Reactive programming)는 가장 추천하고 싶은 것 중 하나입니다. 우리는 함수형 리액티브(반응형) 프로그램이라는 표현으로 자주 마주치곤 하는데요. 주로 옵저버 패턴(Observer pattern)을 대체하기 위해 사용합니다. 단순히 데이터를 넘기고 마무리하는 건 옵저버 패턴으로도 충분하지만 대부분의 문제는 이벤트들을 묶어서 사용할 때 생깁니다.1) RxJava는 이벤트에 대한 조건 처리나 실패 처리, 리소스 정리에 대비해 사용합니다. 기존 방식의 명령형 리액티브 접근 방식을 사용하면 복잡함이 지속적으로 증가하는 반면, 함수형 리액티브 프로그래밍은 효율을 크게 높일 수 있습니다. 몇 가지 예제와 함께 살펴보겠습니다.

Android에 직접 사용해보기

새로운 프로젝트를 생성한 후, 아래와 같이 build.gradle 파일을 수정해봅시다. (JDK 1.8 설치 필수)

apply plugin: 'com.android.application'

android {
   compileSdkVersion 26
   defaultConfig {
       applicationId "kr.co.brandi.myapplication"
       minSdkVersion 21
       targetSdkVersion 26
       versionCode 1
       versionName "1.0"
       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
       }
   }
  //추가된 부분 1 
	compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
}

dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])

	//추가된 부분2
   implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
   implementation 'io.reactivex.rxjava2:rxjava:2.1.3'
  
   implementation 'com.android.support:appcompat-v7:26.1.0'
   implementation 'com.android.support.constraint:constraint-layout:1.0.2'
   implementation 'com.android.support:design:26.1.0'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.1'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}


이제 람다 표현식과 RxJava를 사용할 준비가 되었습니다.

Flowable.just("Hello World").subscribe(new Consumer<String>() {
   @Override
   public void accept(String s) throws Exception {
       Log.v(tag, s);
   }
});

Flowable.just("Hello World !").subscribe(s -> Log.v(tag, s));


간단한 생성자와 결과를 출력하는 부분입니다. 두 번째 subscribe는 람다 표현식으로 인터페이스를 생성하지 않더라도 첫 부분과 동일하게 결과물을 얻을 수 있습니다.2) 이제 RxJava에서 간단한 필터링으로 간편하게 데이터를 가공하는 능력을 확인해보겠습니다. 아래 코드는 기본적인 List 의 값을 출력하는 부분입니다.

List<Integer> valueList = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

for (int data : valueList) {
   String result = "value " + data;
   Log.v(tag, result);
}

Flowable.fromIterable(valueList)
       .map(new Function<Integer, String>() {
           @Override
           public String apply(Integer data) throws Exception {
               return "value : " + data;
           }
       })
       //.map(data -> "value : " + data)
       .subscribe(data -> Log.v(tag, data));


위의 코드에 조건을 추가해 ’짝수’만 출력하겠습니다.

for (int data : valueList) {
   if ((data % 2) == 0) {
       String result = "value " + data;
       Log.v(tag, result);
   }
}

Flowable.fromIterable(valueList)
       //.filter(data -> {
       //      return (data % 2) == 0;
       //})
       .filter(data -> (data % 2) == 0)
       .map(data -> "value : " + data)
       .subscribe(data -> Log.v(tag, data));


위와 같이 데이터 가공은 순차적으로 진행되고, 여러 함수로 간단하게 처리할 수 있습니다. 원하는 데이터 가공을 위해 filter, map 등의 함수들을 순차적으로 이어 붙일 수도 있습니다.

위에서 보여드린 RxJava는 간단한 예시이기 때문에 RxJava 의 기능을 좀 더 보여드리겠습니다.


String[] data1 = {Shape.HEXAGON, Shape.OCTAGON, Shape.RECTANGLE};
String[] data2 = {Shape.TRIANGLE, Shape.DIAMOND, Shape.PENTAGON};

Flowable<String> source =
       Flowable.combineLatest(
               Flowable.fromArray(data1)
                       .zipWith(Flowable.interval(100L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getId(shape)),
               Flowable.fromArray(data2)
                       .zipWith(Flowable.interval(150L, 200L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getSuffix(shape)),
               (id, suffix) -> id + suffix);

source.subscribe(s -> Log.d(getThreadName(), s));


CombineLatest() 함수를 이용해 두 개의 스트림을 하나로 처리하는 방법을 보여 드렸습니다. 각각의 스트림은 interval 함수를 시간 간격으로 data1과 data2 배열의 개수만큼 반복하여 처리하는 로직입니다. 서로 다른 두 스트림은 마지막 데이터를 가지고 있으며 새로운 데이터가 나올 때마다 하나의 스트림으로 출력됩니다.

01
마블 다이어그램 3)
02
결과



Conclusion

만약 RxJava를 이용하지 않고 두 개의 TimerTask를 이용해서 코딩했다면 결과는 같았을지도 모릅니다. 이제 RxJava를 알기 때문에 다시는 TimerTask를 이용해서 코딩할 일은 없을 겁니다. 알면 알수록 다양한 기능을 갖추고 있는 RxJava! 이제 브랜디 상용화 버전에 사용할 수 있게 다시 개발의 숲으로 들어가겠습니다. 그럼 이만.

1)함수나 네트워크 호출의 비동기 응답
2)Java 8 람다 표현식 자세히 살펴보기, 2018.03.09.
3)RxJava on Android


고재성 과장 | R&D 개발1팀
gojs@brandi.co.kr
브랜디, 오직 예쁜 옷만