直感的に理解する RxJava その1: 基本的な構成

ReactiveX

最近 Android 開発者の間では RxJava が話題です. ただ最初はとっつきにくいのが難点です. 関数型リアクティブプログラミングは命令型プログラミングの感覚から近づくと難しいかもしれませんが, いったん理解すると素晴らしいです.

ここでは RxJava の特色を説明します. 4回分の記事はあなたを入り口に案内するためのもので, すべてを説明はできませんが RxJava やそれがどのように動くかに興味をもっていただければと思います.

基本的な使い方

リアクティブコードは Observable と Subscriber で構成されています. Observable はアイテムを発し, Subscriber はそれらのアイテムを受取ります.

アイテムの発し方には, パターンがあります, Observable は, 0から複数個のアイテムを発します. そして, すべてが成功して完了するか, エラーになると終了します. Observable が Subscriber.onNext() を繰り返し, Subscriber.onComplete() か Subscriber.onError() で終了します.

これは, 標準的な Observer パターンによく似ていますが, ひとつ大きな違いがあります. Observable は, subscribe されないかぎりアイテムを発しません. 言い換えれば, だれかにリッスンされていなければ, アイテムは発せられません.

Hello, world!

サンプルでどのように動くかみてみましょう. 最初は, 基本的な Observable をつくります.


Observable<String> myObservable = Observable.create(
    new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> sub) {
            sub.onNext("Hello, world!");
            sub.onCompleted();
        }
    }
);

この Observable は "Hello, world!" を発して終了します.

このデータを受けとる Subscriber を作ります.


Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

これがやっていることは, Observable によって発された文字列を表示するだけです.

Observable と Subscriber ができたので, それらを subscribe() を使ってフックします.


myObservable.subscribe(mySubscriber);

subscribe されたとき Observable は Subscriber の onNext() と onComplete() メソッドをコールします. その結果, Subscriber は "Hello, world!" を出力して終了します.

コードの簡略化

"Hello, world!" と出力させるだけでたくさんのボイラープレートコードがあります. これは何が起きているかすべてみることができるように冗長にしています. RxJavaではコードをシンプルにするためにたくさんのショートカットがあります.

最初に Observable を簡単にしてみましょう. RxJava は一般的な処理に対しての Observable の作成メソッドをいくつか持っています. この場合では Observable.just() を使って上のコードとまったく同じように ひとつのアイテムを発して完了するように書き換えることができます.


Observable<String> myObservable =
    Observable.just("Hello, world!");

次は, Subscriber です. まずは, onCompleted() や onError() を無視して, 代わりに onNext() のふるまいを定義するクラスを使います.


Action1<String> onNextAction = new Action1<String>() {
    @Override
    public void call(String s) {
        System.out.println(s);
    }
};

Action は Subscriber の一部を定義することができます. Observable.subscribe() は, 3つまでの Actionパラメータ onNext(), onError(), and onComplete() を渡すことができ, Subscriber は以下のように書き換えることができます.


myObservable.subscribe(onNextAction, onErrorAction, onCompleteAction);

onError() と OnComplete() は無視するので必要なのは, 一つ目のパラメータだけです.


myObservable.subscribe(onNextAction);

ここで, メソッドチェインを使ってこれらを書き換えてみます.


Observable.just("Hello, world!")
    .subscribe(new Action1<String>() {
        @Override
        public void call(String s) {
              System.out.println(s);
        }
    });

最後に Java8 ラムダ式を使って Action1 のコードの冗長部分を取り除きます.


Observable.just("Hello, world!")
    .subscribe(s -> System.out.println(s));

Android であれば retrolambda の利用を強くお勧めします. 冗長なあなたのコードをおどろくほど短くしてくれます.

変換処理

少し書き換えてみましょう.

署名を "Hello, world!" に加える場合を考えてみます. まず考えられるのは Observable を変更することです.


Observable.just("Hello, world! -Dan")
    .subscribe(s -> System.out.println(s));

Observable をコントロールできる場合であればいいですが, 他人が作ったライブラリを使うことも考えられます. また 複数の場所で Observable を利用する場合は何回も書き加えることになります.

代わりに Subscriber を変更したらどうでしょう.


Observable.just("Hello, world!")
    .subscribe(s -> System.out.println(s + " -Dan"));

これはまだ満足できるものではありません. Subscriber はメインスレッドで実行されるかもしれないのでできるだけ軽量にしておくべきです. また, 概念的なレベルで見ると Subscriber は「反応するもの」で「変換するもの」ではありません.

これらの中間で "Hello, world!" を変換できれば良いのではないか?

Operator の紹介

ここでは, Operator を使ってどのように変換処理を行うかを紹介します. Operator は, 元の Observable から 最終の Subscriber までの間に, 発されたアイテムを操作します. RxJava にはかなり多くの Operator がありますが, 最初は少しだけ使ってみましょう.

この場合は, 発せられたアイテムを map() で変換することができます:


Observable.just("Hello, world!")
    .map(new Func1<String, String>() {
        @Override
        public String call(String s) {
            return s + " -Dan";
        }
    })
    .subscribe(s -> System.out.println(s));

再度, ラムダ式を使って簡単にします.


Observable.just("Hello, world!")
    .map(s -> s + " -Dan")
    .subscribe(s -> System.out.println(s));

かなりクールではないですか?

map() は基本的なアイテム変換の Operator です. 最終の Subscriber が使える形にまとめたり, データを完全に適合させるために, 多くの map() をチェインすることができます.

さらに map() を

map() には面白い特徴があります. 元 Observable と同じタイプのアイテムを発する必要はありません.

Subscriber がオリジナルのテキストを出力する代わりに テキストのハッシュを出力したいならば以下のようになります.


Observable.just("Hello, world!")
    .map(new Func1<String, Integer>() {
        @Override
        public Integer call(String s) {
            return s.hashCode();
        }
    })
    .subscribe(i -> System.out.println(Integer.toString(i)));

おもしろいですね.
String から始まったのに Subscriber は Integer を受け取ります. このコードをラムダ式で短くしましょう.


Observable.just("Hello, world!")
    .map(s -> s.hashCode())
    .subscribe(i -> System.out.println(Integer.toString(i)));

最初に言ったように, Subscriber はできるだけ短くしたいので, もうひとつ map() を使って String に戻しましょう.


Observable.just("Hello, world!")
    .map(s -> s.hashCode())
    .map(i -> Integer.toString(i))
    .subscribe(s -> System.out.println(s));

元のコードの件に戻ってみましょう. いくつかの変換のステップを追加するだけです。同様に署名変換を追加することができます。


Observable.just("Hello, world!")
    .map(s -> s + " -Dan")
    .map(s -> s.hashCode())
    .map(i -> Integer.toString(i))
    .subscribe(s -> System.out.println(s));

だから何?

ここであなたは思ったかもしれません.

「簡単なコードなのにいろいろやりすぎ」

確かにそうです.

これらは簡単なコードの例でしたが, 大事な2つの考え方が含まれています.

1:「Observable と Subscriber でいろんなことができる」

想像力を働かせてみましょう. いろんなことができます.

Observable は データベースクエリーとなることができます. Subscriber は 結果を受け取り画面に表示します. また, Observable は 画面のクリックにもなることもでき, Subscriber ではそれに反応できます. あるいは, Observable はインターネットから取得したバイトストリームになり, Subscriber ではそれをディスクに書き込むことができます.

どんな処理もできる一般的なフレームワークなのです.

2:「Observable と Subscriber はそれらの間にある変換処理から独立している」

最初の Observable と最後の Subscriber の間の map() 処理に集中して取り組む事ができます.
高度に組み合わせが可能で, データ操作しやすく, 入出力データが正しく Operator で操作できるようにいくらでもメソッドをチェインすることができます.

これら 2つのキーとなる考え方を組み合わせることでたくさんの潜在能力を引き出すことができます. ここでは, map() ひとつだけを利用しましたがこれだけでは限界があります. その2では RxJava で利用できる 多くの Operator を利用してみます.

[原文] Grokking RxJava, Part 1: The Basics

直感的に理解する RxJava その2: Operator

直感的に理解する RxJava その3: リアクティブであることのメリット

直感的に理解する RxJava その4: Reactive Android


Observable 複数データソース に優先順位をつける

通信を使うとき, 必要になるだろうよくあるパターンだと思います.

通信状況や不要なリクエストやキャッシュを使うかどうか.

どこからデータを取得してくるか.

- メモリ
- ストレージ
- ネットワーク

それぞれのソース別に Observable を作ったら, 優先順位をつけたら連結する.

ReactiveX_-_Concat_operator

ReactiveX - Concat operator

条件をつけて, 最初の一つだけ利用する.

ReactiveX_-_First_operator

ReactiveX - First operator

そんなサンプルコードがあります.


// Create our sequence for querying best available data
Observable<Data> source = Observable.concat(
    sources.memory(),
    sources.disk(),
    sources.network()
  )
  .first(data -> data != null && data.isUpToDate());

// "Request" latest data once a second
Observable.interval(1, TimeUnit.SECONDS)
  .flatMap(__ -> source)
  .subscribe(data -> System.out.println("Received: " + data.value));

rxjava-multiple-sources-sample/Sample.java


public Observable<GfyItem> getGfyItem(final String gifUrl, final String gfyName) {

  // Use the first source that returns a valid name
  return Observable.concat(

    // We already have the name
    Observable.just(gfyName),

    // We check for a pre-converted gif (for the gfyname)
    getPreExistingGfyName(gifUrl),

    // We need to convert the gif (then retrieve the gfyname)
    convertGifToGfyName(gifUrl)
  )
  .first(result -> !TextUtils.isEmpty(result))
  .flatMap(this::getMetadata)
  .map(GfyMetadata::getGfyItem);
}

android-gfycat/GfycatService.java

非常にわかりやすいサンプルや記事で勉強になりますっ.

Loading data from multiple sources with RxJava


Android Studio と JDKバージョン

👉 AndroidStudio 利用する Java (JDK) の選択・設定の方法 

この表示.

System Health
Running on a JDK8 version affected by drag and drop issues (1.8.0_60 through 1.8.0_76). See IDEA-146691 for details, and consider using a JDK outside the problematic range.

IDEとしてのAndroid Studio が動く JDK と
Android Studio がアプリビルド時に利用するJDK は異なる.

Mac OSX JDK Selection - Android Tools Project Site

コンソールから

~ $ java -version
java version "1.8.0_72"
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)

この場合, デスクトップ上のアイコンから Android Studio を起動すると
JDK8 でそれが起動する.

Android Studio を JDK8 で稼働させる場合, 既知のバグがありそれを通知するダイアログが表示される.

Welcome_to_Android_Studio

公式のアナウンスにもあるように JDK7 で稼働させる場合は,

~ $ export STUDIO_JDK=/Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk
~ $ open /Applications/Android\ Studio.app

この場合は, ダイアログは通知されない.

一方, 開発中のアプリビルド時に利用するJDKの指定は, Android Studio 設定画面から変更する.

choose-jdk

まとめ

Android Studio 自体が利用する JDK は環境変数 STUDIO_JDK で切替えるとよい.

👉 AndroidStudio 利用する Java (JDK) の選択・設定の方法 


Swift が Android 上で動き始めてよく分かる Kotlin の素晴らしさ

Swift がAndroid上で動き始めており,

_RFC__Port_to_Android_by_modocache_·_Pull_Request__1442_·_apple_swift

[RFC] Port to Android by modocache · Pull Request #1442 · apple/swift

Hacker News でも少し話題になっていたので眺めておりました.

Swift_Ported_to_Android___Hacker_News

Swift Ported to Android | Hacker News

「Swift が Android上で動くこと」について話題がされているかと思いきや Kotlin と比較され, Kotlin のAndroid上で動かすことの良さがはっきりと分かるスレになっておりました.

Surprised noone mentions Kotlin. It's quite Swift-like, backed by JetBrains (Android Studio is based on their IntelliJ Idea), and 1.0 has only just been released. It has full interoperability with Java.

Kotlin に誰も言及していないのが驚き. Kotlin は Swift によくにており, JetBrains (AndroidStidioのベースとなるIntelliJ Idea の開発元)がバックアップしており, 1.0 がリリースされたばかりのもので Java と完全に相互連携ができる.

I love Kotlin, but being able to develop libraries in one language and use them in both Android and iOS is huge. I have been using J2Objc for this until now, and while it's a great tool, it forces me to use Java, which I don't love. I would prefer being able to use Kotlin on iOS, but using Swift for Android development is a great boon.

私はKotlinが好きだが, ひとつの言語でAndroidとiOSで開発ライブラリを利用可能にすることは大変だ. いままで J2Ojc を使っておりこれは素晴らしいツールでJavaを使うことを強いられるが私は好きではない. それより Kotlin を iOS上で利用可能にするほうがいい. Android開発にSwiftを使うのは 非常にありがたいのだが...

Kotlin emits bytecode. It is entirely interoperable with Java. All the Java APIs of the platform are accessible in Kotlin.
Swift on the other end can only target the NDK, which limits it to a very specific niche on Android.

Kotlin はバイトコードを吐く, これは Java と完全に相互連携できること. すべてのJavaAPI はKotlin で利用できる.
一方 Swift は Android上では NDKのみに限定され非常に狭い.

What helps a lot is that the kotlin team has written many helper methods allowing a better flow between the android API and kotlin code : while you don't need it in order to get interop, it allows to more easily write idiomatic kotlin code while interacting with Android.

Kotlin チームの書いたたくさんのヘルパーメソッドは, Android API と Kotlin 間の流れをより良くしており, Swift ユーザがそれを利用しないことと対照的に, Androidと連携しながらより簡単に慣用的な Kotlin コードを書くことができる.

The main problem with Swift on Android is that AFAIK it is going to be limited to NDK.
There is certainly a niche where it can be useful, but for most developers, it makes it a no go.
Kotlin on the other end is indeed a very good stand-in replacement for java on Android.

私の知る限りSwift を Android上で動かすことの大きな問題は NDKに限定されることだ. このことは確実に便利さを狭めておりそれが進行を妨げている. 一方, Kotlin は確かに Android 上での Java の代替となる.

No one cares if you don't use Swift. Go with Kotlin. It's probably a great choice for you. It seems like a great language and I hope it gains traction.

Swift は使わなくて良い. Kotlin でいこう. あなたにとって素晴らしい選択となる. 偉大な言語で勢いを増すことを願っている.

If all you're doing is Android programming Kotlin is probably a better choice at this time. If you're a Java programmer, Kotlin is probably a better choice. If you're already a Swift programmer...
Also, let's see how both languages gain traction in the next 3-5 years.

Androidプログラミングのみを行っていたり, Javaプログラマーであるなら Kotlin はおそらくより良い選択です. もしあなたがすでにSwiftプログラマーであるなら...
次の3-5年でそれぞれがどのように勢いを増すかを見ておきましょう.

しかし,「ことりん」て名前. かわいいよなあ.

あの「Hacker News」で ベストなストーリーを見つける方法


まずは「Google Codelabs」から試してみよう?

バージョンやライブラリの依存など環境の違いでわかりづらくなってきているのが StackOverflow.

ある意味公式のGoogleのサイトから眺め始めるのが筋か.

ある程度まとまってきています.

Google Codelabs

Google_Codelabs

Android なら「BY TECHNOLOGY」から「Android」を選択.

Google_Codelabs 2

Google_Codelabs 3

たとえば, テスト関連ならこのへんか.

- Automated Performance Testing Codelab
- Unit and UI Testing in Android Studio
- Android Testing Codelab

Android_Testing_Codelab

Automated_Performance_Testing_Codelab

Unit_and_UI_Testing_in_Android_Studio 2

イベントやカンファレンスなどのサンプルコードをベースに各リファレンス参照しながらお試しできる構成になっておりますね.