意味不明な Gradle の Warning や Error を消して激ビルド時間短縮

なんとなく Gradle コンソールを開けてみると死ぬほど書き出されてるエラーやワーニング.


AGPBI: {"kind":"error","text":"(org.apache.commons.beanutils.BeanMap$8) that doesn\u0027t come with an","sources":[{}]}
AGPBI: {"kind":"error","text":"associated EnclosingMethod attribute. This class was probably produced by a","sources":[{}]}
AGPBI: {"kind":"error","text":"compiler that did not target the modern .class file format. The recommended","sources":[{}]}
AGPBI: {"kind":"error","text":"solution is to recompile the class from source, using an up-to-date compiler","sources":[{}]}
AGPBI: {"kind":"error","text":"and without specifying any \"-target\" type options. The consequence of ignoring","sources":[{}]}
AGPBI: {"kind":"error","text":"this warning is that reflective operations on this class will incorrectly","sources":[{}]}
AGPBI: {"kind":"error","text":"indicate that it is *not* an inner class.","sources":[{}]}
AGPBI: {"kind":"error","text":"warning: Ignoring InnerClasses attribute for an anonymous inner class","sources":[{}]}
AGPBI: {"kind":"error","text":"(org.apache.commons.beanutils.BeanMap$9) that doesn\u0027t come with an","sources":[{}]}
AGPBI: {"kind":"error","text":"associated EnclosingMethod attribute. This class was probably produced by a","sources":[{}]}
AGPBI: {"kind":"error","text":"compiler that did not target the modern .class file format. The recommended","sources":[{}]}
AGPBI: {"kind":"error","text":"solution is to recompile the class from source, using an up-to-date compiler","sources":[{}]}
AGPBI: {"kind":"error","text":"and without specifying any \"-target\" type options. The consequence of ignoring","sources":[{}]}
AGPBI: {"kind":"error","text":"this warning is that reflective operations on this class will incorrectly","sources":[{}]}
AGPBI: {"kind":"error","text":"indicate that it is *not* an inner class.","sources":[{}]}
AGPBI: {"kind":"error","text":"warning: Ignoring InnerClasses attribute for an anonymous inner class","sources":[{}]}
AGPBI: {"kind":"error","text":"(org.apache.commons.beanutils.BeanMap$1) that doesn\u0027t come with an","sources":[{}]}
AGPBI: {"kind":"error","text":"associated EnclosingMethod attribute. This class was probably produced by a","sources":[{}]}
AGPBI: {"kind":"error","text":"compiler that did not target the modern .class file format. The recommended","sources":[{}]}
AGPBI: {"kind":"error","text":"solution is to recompile the class from s

いつから出ていたのかわかりません.

Proguard 周りなのでしょうが, 調べても意味がわかりませんでした.

が,

Large number of errors during Gradle build after upgrading to Android Studio 2.0 - Stack Overflow

意味のない warning や error は, .idea フォルダを消すことで取り除くことができます。このフォルダは Android Studio 1.5 が作成したもので 2.0 にインポートされています。消しても自動的に作成されます。

ideadir

一度削除すると再作成されますが, 意味不明なログが吐かれなくなり, ビルド時間も数分から数十秒に激速となりました。

...

などと喜んでいたが, また出てる.

...

Issue Tacker を見てみる.

Issue 215748 - android - AGPBI reports errors instead of warnings - Android Open Source Project - Issue Tracker - Google Project Hosting

まだ生きてる issue なのですが, 一時しのぎとして以下とか.


# https://code.google.com/p/android/issues/detail?id=215748
-keepattributes Exceptions, Signature, InnerClasses, EnclosingMethod
-dontoptimize

一応出なくなりますけれども,

速くなりますけれども,

「don't optimize」て...

またはこんな一時しのぎ法も.

Issue 222989 - android - Android Studio 2.2 : Build successful with 1944 errors - Android Open Source Project - Issue Tracker - Google Project Hosting

It affects me too it is caused by gradle not android studio (but it tries to update the gradle)
Temporary solution:
I went back to gradle 2.1.3 (that I used earlier):

classpath 'com.android.tools.build:gradle:2.1.3' ->this works

If I am using classpath 'com.android.tools.build:gradle:2.2.0' I am also getting tons of warning message and the output is an unaligned file...

「com.android.tools.build:gradle のバージョンを下げる」という方法ですが, うまくダウングレードできないような...

なんなんすかね, まったく.

issues_-_android_-_android_open_source_project_-_issue_tracker_-_google_project_hosting

Issues - android - Android Open Source Project - Issue Tracker - Google Project Hosting

2016-10-05

待てないので以下でとりあえず対応.
AGPBI 関連の多量のエラーを排除する


いまどきの MVP 実装パターンを眺めるべし「Android Architecture Blueprints」

このような, 質問がありました.

こんにちは。

私はアプリの開発を2年ほどやっているものです。
しかし, いつも似たようなコードの繰り返しばかりで少しも進歩していないように思っています。

小さい会社なので, モバイルアプリ開発者は私だけで, だれも私のコードを見ることがないので, 私のコードの間違いを指摘されることはなく, 会社で開発されている他のコードを見ることもありません。

最新技術を利用しすばらしい実装を行っているオープンソースアプリのコードを勉強したいです。

そのようなアプリをどこで見つけたらよいか教えて下さい。

よろしくお願いいたします。

I would like to study some up-to-date open source apps, preferably with material design, do you have any suggestions? : androiddev

このような環境で日々を過ごし, 似たようなことを考えてる開発者は多いと思います。

以下のサイトはいかがでしょうか. よくある ToDo アプリでのサンプルとなっています.

Android Architecture Blueprints [beta] - A collection of samples to discuss and showcase different architectural tools and patterns for Android apps.

ここでフォーカスされているのは, 構造, 設計, テスト, メンテナンスのしやすさですが, リファレンスとして, または, アプリ開発のスタート地点として利用できます.

MVP (基本的な Model-View-Presenter)

googlesamples/android-architecture at todo-mvp

このサンプルはすべての基本となります. 構造を持つフレームワークを使わないシンプルな Model-View-Presenter パターンの実装例です. ローカル/リモートのデータソースである Repository を手作業で Dependency Injection しています. 非同期処理はコールバックを利用しています.

mvp

MVP + Loader

googlesamples/android-architecture at todo-mvp-loaders

Repository から Loader を使ってデータを取得します.

- コールバックなしで Repository 内のデータを非同期で読み込むことができる.
- データソースを監視しており, Repository の内容が変化すると新しい結果として配送できる.
- 画面回転のあと自動的に直近の Loader を再接続できる.

mvp-loaders

MVP + Loader + ContentProvider

googlesamples/android-architecture at todo-mvp-contentproviders

Repository からのデータ取得に ContentProvider を使います.

- 構造化されたデータへのアクセス操作可能.
- 別プロセスで稼働しているコードからデータへ接続できる標準的なインターフェースとなる.

mvp-contentproviders

MVP + DataBinding

googlesamples/android-architecture at todo-databinding

DataBiding ライブラリを利用して, UI要素にデータとアクションをバインドしています. 厳格には Model-View-ViewModel や Model-View-Presenter パターンではありません. ViewModel と Presenter 両方使用しています.

DataBinding ライブラリは, データとUI要素を連携する重複するコードを削減してくれます.

- レイアウトファイルがUI要素へのバインドに利用されている.
- 同時にイベントもアクションハンドラーと結合されている.
- データの監視が可能で必要であるときには自動で更新するようにセットすることができる.

mvp-databinding

MVP + Clean Architecture

googlesamples/android-architecture at todo-mvp-clean

Clean Architecture に基づいており, Presentation と Repository レイヤーの間に Domain レイヤーが存在して, アプリを3つのレイヤーに分けています.

Domain レイヤーでは, すべてのビジネスロジックを収納しており, Presenter に使われる use-case か interactor と命名されたクラスから始まる. これらの use-case は Presentation レイヤーから作ることができるすべての実装可能なアクションを提供します.

mvp-clean

その他

その他, 最近流行のフレームワークや考え方を考慮しての実装サンプルも続々と作成中のようです.

Architecture Blueprints の非同期処理実装にみる Android SDK の方向性

MVP + Dagger2
MVP + RxJava
MVP + Fragmentなし

非常に柔軟性のある使えるサンプルとなると思われます. ぜひご確認あれ.

Architecture Blueprints の非同期処理実装にみる Android SDK の方向性

Hacker News Radio (翻訳) - Google Play の Android アプリ


2分で作れる「About Page」にみる Intent の作り方

Pocket の開発者の方なのかな.

以下のような記述のみで「About Page」が作成できるようです.


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        View aboutPage = new AboutPage(this)
                .isRTL(false)
                .setImage(R.drawable.dummy_image)
                .addGroup("Connect with us")
                .addEmail("[email protected]")
                .addFacebook("the.medy")
                .addTwitter("medyo80")
                .addYoutube("UCdPQtdWIsg7_pi4mrRu46vA")
                .addPlayStore("com.ideashower.readitlater.pro")
                .addInstagram("medyo80")
                .addGitHub("medyo")
                .create();

        setContentView(aboutPage);
    }

medyo/android-about-page: Create an awesome About Page for your Android App in 2 minutes

こんなかんじになります.

20160425-115827_1

下の方の各アイテムをタップすると, 作成されたそれぞれの Intent で startActivity されるわけですが.


mContext.startActivity(element.getIntent());

どんな Intent なのか見てみましょう.

メール


Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{email});

Facebook


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);

if (AboutPageUtils.isAppInstalled(mContext, "com.facebook.katana")){
  intent.setPackage("com.facebook.katana");
  int versionCode = 0;
  try {
    versionCode = mContext.getPackageManager().getPackageInfo("com.facebook.katana", 0).versionCode;
  } catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
  }

  if (versionCode >= 3002850) {
    Uri uri = Uri.parse("fb://facewebmodal/f?href=" + "http://facebook.com/" + id);
    intent.setData(uri);
  } else {
    Uri uri = Uri.parse("fb://page/"+id);
    intent.setData(uri);
  }
}else{
  intent.setData( Uri.parse("http://facebook.com/" + id));
}

Facebookアプリを入れている場合は, そのバージョンのよってセットする Uri を分けているようです.

Twitter


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);

if (AboutPageUtils.isAppInstalled(mContext, "com.twitter.android")){
  intent.setPackage("com.twitter.android");
  intent.setData(Uri.parse(String.format("twitter://user?user_id=%s",id)));
}else{
  intent.setData(Uri.parse(String.format("http://twitter.com/%s",id)));
}

Play Store


Uri uri = Uri.parse("market://details?id=" + id);
Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri);

YouTube


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(String.format("http://youtube.com/channel/%s", id)));

if (AboutPageUtils.isAppInstalled(mContext, "com.google.android.youtube")){
  intent.setPackage("com.google.android.youtube");
}

Instagram


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://instagram.com/_u/"+id));

if (AboutPageUtils.isAppInstalled(mContext, "com.instagram.android")){
  intent.setPackage("com.instagram.android");
}

GitHub


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setData(Uri.parse(String.format("https://github.com/%s", id)));

まとめ

ユーザに, ブラウザで開くか, アプリで開くか, の選択の余地を与えず, インストールしているかの判断の後, パッケージ指定で独自スキームの Uri を渡して投げる, てのが今のやり方か.

それがおまえらのやり方か!!


CoordinatorLayout がわかりづらいのは デフォルト のせい?

よくある画面要素の組み合わせ ActionBar + RecyclerView + FAB.

ActionBar 部分をスクロール時に上にスライドさせて消したいですよね!

まずは CoordinatorLayout で囲みます.

レイアウトはこんなかんじの構成ですね.


<CoordinatorLayout>
  <AppBarLayout>
    <Toolbar />
  </AppBarLayout>
  <RecyclerView />
  <FloatingActionButton />  
</CoordinatorLayout>

ここでは, 連携する挙動の始まりとなる「きっかけ」を RecyclerView のスクロールとしましたが, 以下の View や Layout は, デフォルトで同様に「きっかけ」にすることができます.

HorizontalGridView
NestedScrollView
RecyclerView
SwipeRefreshLayout
VerticalGridView

NestedScrollingChild | Android Developers

よく画面をみてみると, RecyclerView の最上段をみることができないようになってますね!

20160414-190459_1

それは, CoordinatorLayout は FrameLayout だからです.

CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout | Android Developers

でも, とりあえず無視します.

次に, アクションバー部分が「きっかけ」に反応して「ふるまう」するようにします.

AppBarLayout または Toolbar に, 属性「app:layout_behavior」をつけて, 「ふるまう」時の挙動を追加したくなります.

でも, 必要なかったりします.

ふるまいを記述する Behavior は, デフォルトですで に AppBarLayout にアノテーションを利用してセットされているからです.


@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {

Cross Reference: AppBarLayout.java

なので, ふるまい方の種類だけをその子である Toolbar 記述します.


<CoordinatorLayout>
  <AppBarLayout>
    <Toolbar
      app:layout_scrollFlags="scroll|enterAlways" />
  </AppBarLayout>
  <RecyclerView />
  <FLoatingActionButton />  
</CoordinatorLayout>

ここで, 動かしてみます.

20160414-191401_1

アクションバーが消えていくようになりましたね!

最後に, RecyclerView 自体も「ふるまわ」せます.

AppBarLayout のふるまい方と同じふるまい方をするように同じ Behavior をセットします.


<CoordinatorLayout>
  <AppBarLayout>
    <Toolbar
      app:layout_scrollFlags="scroll|enterAlways" />
  </AppBarLayout>
  <RecyclerView
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
  <FLoatingActionButton />  
</CoordinatorLayout>

ここでいきなり現れた文字列 @string/appbar_scrolling_view_behavior は, デフォルトでセットされている


android.support.design.widget.AppBarLayout$ScrollingViewBehavior

のことです.

AppBarLayout にデフォルトで設定されているものと同じですね.

ここで, また動かしてみます.

20160414-195431_1

AppBarLayout の Behavior と同じものを RecyvlerView にもセットしたので, 同じように動きます.

スクロール時にAppBarと同じように開閉スライドすることになるので, RecyclerView との重なり合いがなくなり RecyclerView の最上段も表示されるようになりましたね!

実際の簡略化していないレイアウトを貼っておきます.


<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <android.support.design.widget.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:title="@string/app_name"
        app:layout_scrollFlags="scroll|enterAlways" />
  </android.support.design.widget.AppBarLayout>
  <android.support.v7.widget.RecyclerView
      android:id="@+id/recycler_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior" />
  <android.support.design.widget.FloatingActionButton
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginRight="16dp"
      android:layout_marginBottom="16dp"
      android:layout_gravity="right|bottom"
      android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>

以上のように, レイアウトファイルだけを編集するだけで, インタラクティブな挙動を画面の要素たちに行わせることができるので, 便利な気もしますね!!

→ 少しややこしいので「まとめ」ようとしてつづく (作成中)


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

ReactiveX

その1, その2, その3では, RxJava がどのように機能するかを書きました. Androidアプリの開発では, それをどのように利用するのでしょうか. ここでは Android開発者に向けての実践的なことを書いてみようと思います.

RxAndroid

RxAndroid は Android用の RxJavaエクステンションで, より簡単に利用できるようバインディングなどを含んだものです.

まず最初に, AndroidScheduler です. これは, 既存の Android のもつスレッド処理を考慮したスケジューラを提供します. 別に, UIスレッド処理に関係するコードは必要ありません.
AndroidSchedulers.mainThread() を利用するだけです.


retrofitService.getImage(url)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

もし, あなたが自作の Handler を利用しているのであれば HandlerThreadScheduler を利用して スケジューラとそれをリンクしましょう.

次に, AndroidObservable には, Androidライフサイクル内で有効に利用できる機能があります. それは bindActivity() と bindFragment() です. Observable に対してAndroidSchedulers.mainThread() を使うことで自動的に追加でき, Activity や Fragment が終了した場合にはアイテムを発することを停止することができます.


AndroidObservable.bindActivity(this, retrofitService.getImage(url))
    .subscribeOn(Schedulers.io())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

また, AndroidObservable.fromBroadcast() は便利です. これは, BroadcasrReceiver のように利用することができる Observable を作成できます. 以下が, ネットワーク接続状態が変化したときに通知するコードです.


IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
AndroidObservable.fromBroadcast(context, filter)
    .subscribe(intent -> handleConnectivityChange(intent));

その他に, View にバインディングできる ViewObserbable が2つあります. あるタイミングで View がクリックされたというイベントを取得したい場合には ViewObserbable.clicks(), TexitView が持っているテキストの変更を監視する ViewObservable.text() があります.


ViewObservable.clicks(mCardNameEditText, false)
    .subscribe(view -> handleClick(view));

Retrofit

Android用RESTクライアントの有名なライブラリである Retrofit は RxJava をサポートしています.

通常ではコールバックを追加して非同期処理を実装します.


@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

RxJava を利用していれば, これの代わりに Observable を返すことができます.


@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

これだけで Observable に変換することができます. データを取得するだけでなく, 変換も同時に行います.

また, Retrofit は Observable に対して, 簡単に複数のRESTコールを合成することができます. 以下のサンプルでは, 写真データとそのメタデータを取得したいとします. zip を利用して取得結果を合成します.


Observable.zip(
    service.getUserPhoto(id),
    service.getPhotoMetadata(id),
    (photo, metadata) -> createPhotoWithData(photo, metadata))
    .subscribe(photoWithData -> showPhoto(photoWithData));

これに似たサンプルをその2 (flatMap() を利用) で紹介しました. 複数のREST処理は RxJava + Retrofit でこれだけ簡単に利用できるのです.

今までのコードや処理に時間のかかるコード

Retrofit は適切な Observable を返すことができることは分かりましたが, その他のライブラリを利用している場合はどうなるでしょうか. Observable に変換するコードが必要となるのでしょうか? すべてを変更せずにそのコードをどのようにして利用することができるのでしょうか ?

ほとんどの場合, そのコードから Observable を作成するには Observable.just() とObservable.from() で十分であると思います.


private Object oldMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.just(oldMethod());
}

oldMethod() が短時間の処理であればこれで問題ないでしょう. しかし, 時間のかかる処理であればどうなるでしょう? Observable.just() の前に oldMethod() が実行されスレッドをブロックしてしまいます.

この問題の対応策として, 時間のかかる処理を defer() でラップするという技を私はいつも利用しています.


private Object slowBlockingMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.defer(() -> Observable.just(slowBlockingMethod()));
}

このようにすると Observable が subscribe されるまでは slowBlockingMethod() から結果を返さないようになります.

ライフサイクル

最もややこしい部分です. Activity のライフサイクルに対しての操作はどうなるでしょう? 2つの問題がいつも登場します.

1.「Configuration が変更されるとき(端末の回転時など) に Subscription が継続している」

Retrofit を利用してRESTコールを行い, 外部から取得したデータを ListView に表示するとします. もし, ユーザが画面を回転させたらどうなるでしょうか? そのリクエストを継続したい場合はどうすればよいのでしょうか?

2. Observable が Context を参照したままでいるので メモリーリークが発生する.

この問題は Context を保持している Subscription が引き起こしています. View と連携しているときはわかりやすいですが, なぜか保持しています. 最終的には, たくさんのメモリーを食いつぶしてしまいます.

残念ながら, それぞれの問題には一発で解決できる方法はありませんが, 分かりやすく理解するためのいくつかのガイドラインがあります.

最初の問題は, RxJava のもつキャッシュ機能で解決できます. それを使うことで同じ Observable が重複して動かないように unsubscribe/resubscribe できます. cache() や replay() を使うことで, たとえ unsubscribe してる場合でさえ 遅延なしのリクエストを継続することができます. このことは, Activity が再作成後の新しい Subscription によりレジュームできることを意味しています.


Observable<Photo> request = service.getUserPhoto(id).cache();
Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));

// ...When the Activity is being recreated...
sub.unsubscribe();

// ...Once the Activity is recreated...
request.subscribe(photo -> handleUserPhoto(photo));

注意点としては, 両方のタイミングで同じキャッシュされたリクエストを利用していることです. 遅延のないコールは一度しか行われません. どこにリクエストを保存するかはあなた次第ですが, ライフサイクルを考慮しながら, ライフサイクルの影響を受けないどこかに保存しなければなりません.

二つ目の問題は, ライフサイクルを考慮しながらの Subscription に対しての適切な unsubscribe で解決できます. これの一般的な方法は CompositeSubscription を使うことですべての Subscription を保持し, onDestroy() や onDestroyView() のタイミングに一括で unsubscribe する方法です.


private CompositeSubscription mCompositeSubscription
    = new CompositeSubscription();

private void doSomething() {
    mCompositeSubscription.add(
        AndroidObservable.bindActivity(this, Observable.just("Hello, World!"))
        .subscribe(s -> System.out.println(s)));
}

@Override
protected void onDestroy() {
    super.onDestroy();

    mCompositeSubscription.unsubscribe();
}

このように Activity/Fragment で CompositeSubscription を持つことで, それぞれを追加し, その後, 一括ですべてを unsubscribe することができます.

注意することは, 一度 CompositeSubscription.unsubscribe() するとそれは再利用できません. 再度利用する場合は 新しい CompositeSubscription を作成して利用しなければなりません.

という形でどちらの問題もコード追加で解決できます. これらのボイラープレートなしに問題を解決できる天才がいつか現れて欲しいです.

あとがき

Android についてはこれだけではありません. RxJava はこれからも更新され, Android にもさらに対応されていくでしょう. みなさんがこれらのことをさらに理解しようとするとき, RxAndroid はまだ開発中で, よいサンプルがまだありません. 私がここで提示したサンプルはこれから一年間は興味深いものになると思います.

その間, RxJava がコーディングを簡単にするだけのものだけでなく, もっとおもしろいものを探します. もしあなたがまだ納得していなければ, いつか私を見つけてください, それについてビールを飲みながら話します.

[原文] Grokking RxJava, Part 4: Reactive Android

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

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

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