RxJava + Flux (+ Kotlin)によるAndroidアプリ設計

# Introduction

Androidにとってアプリ設計は永遠の課題だと思います。
Activityが独特のライフサイクルを持っているのがさらに課題を複雑化します。
設計についてはMVCやMVP、MVVM、Clean-Architecture、DDDなどが最近の主流でしょうか。
上記にあがった設計手法に関しては素晴らしい記事がいくつもあるのでとても参考になると思います。

> AndroidではMVCよりMVPの方がいいかもしれない
> http://konifar.hatenablog.com/entry/2015/04/17/010606
> [ Android ] - これからの「設計」の話をしよう
> https://tech.recruit-mp.co.jp/mobile/android-architecture/
> AndroidオールスターズでClean Architectureについて発表してきた&参考リンク集
> http://tomoima525.hatenablog.com/entry/2015/08/13/190731

設計に絶対の正解はなく、チームやアプリの規模により様々な設計が考えられると思います。

私も比較的大規模なアプリを作るにあたり設計を試行錯誤しながら作っていたのでですが、日々開発していく中で、どうしても解決したい問題を[SQLBrite](https://github.com/square/sqlbrite)というすばらしいライブラリで解決し、隣のチームが[EventBus](https://github.com/greenrobot/EventBus)+[Flux](http://facebook.github.io/flux/)を採用してるのを見ているうちに、**UIレイヤーのみRxJava+Fluxでアプリを設計する**という形に行き着きました。

本記事では備忘録的にその設計の現在の指針をまとめました。また、現在進行系で開発しているので、何か間違っている点やご意見等あればぜひ、コメントや[Twitter](https://twitter.com/satorufujiwara)などでお願いします。

また、肝となるライブラリが違いますが、設計についてはizumin5210さんの考えている設計に非常に近いんではないかな？と思っています。

>Clean Architecture + DDD + Redux + RxJavaをAndroidでやるときにどこまで分割するか問題
>http://izumin.hateblo.jp/entry/2016/01/24/221943

## Kotlin

Kotlinについては[前回の記事](http://qiita.com/satorufujiwara/items/871c5b7b66c7691d82a8)をみていただければと思います。
該当アプリをほぼKotlinで書いているのでサンプルソースなどはKotlinになりますが、設計上Kotlinである必要はありません。
しかし、ラムダやエクステンションなどKotlinで記述したほうが、すっきりと書けると思います。また、RxJavaのライブラリはだいたいKotlin向けにエクステンションが出ているのであわせて使うのをお勧めします。

## RxJava

hydrakecatさんによる以下の記事が参考になると思います。

> RxJava は Subscriber を中心に捉えると理解しやすいんじゃないかという話
> http://hydrakecat.hatenablog.jp/entry/2015/12/22/RxJava_%E3%81%AF_Subscriber_%E3%82%92%E4%B8%AD%E5%BF%83%E3%81%AB%E6%8D%89%E3%81%88%E3%82%8B%E3%81%A8%E7%90%86%E8%A7%A3%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%93%E3%81%98%E3%82%83%E3%81%AA%E3%81%84

上記記事では「Everything is Subscriber.」となっていますが、今回の設計は元々の[「Everything is a stream.」](http://slides.com/robwormald/everything-is-a-stream#/)の考えに則った設計とも言えます。

また`Subject`を多用するので以下の記事も参考になります。

> Rxで知っておくと便利なSubjectたち
> http://qiita.com/hide92795/items/f7205c8171826cc2153b#serializedsubject

## Flux

以下は[公式サイト](http://facebook.github.io/flux/)にあるにデータフロー図です。
「Action->Dispatcher->Store->View」というフローを必ず守ります。

> ![flux-simple-f8-diagram-explained-1300w.png](https://qiita-image-store.s3.amazonaws.com/0/36413/1e02cc99-cf41-bb38-cd3e-a77c9e4ee621.png)
> https://facebook.github.io/flux/docs/overview.html#content

flux、およびそのAndroidへの導入はizumin5210さんによる記事が参考になると思います。

> Droidux: ReduxをAndroidに持ち込んで状態管理から解放されよう！
> http://qiita.com/izumin5210/items/549fd15d97e9fc3b1ef7

上記のサンプルアプリ内でも`PublishSubject`がDispatcherとして使用されていますが、今回の設計はそれらを拡充したものになります。

## どうしても解決したい問題

現在作っているアプリには30個近い`Activity`が存在します。
その中には別の`Activity`で起きたアクションによって、画面内の表示を変えないといけないものがいくつか存在します。例えば、「フォロー」がそれに当たります。「フォロー」ボタンがいくつかの`Activity`に存在していたら、どこかでそれが押された場合に全ての`Activity`で「フォロー済」となってて欲しい、その`Activity`がたとえシステムによってdestroyされていたとしても。これがどうしても解決したい問題でした。

他にもログイン情報や、設定など「状態」を`Activity`を跨いだアプリ全体で管理したいものは必ずといって存在すると思います。

[EventBus](https://github.com/greenrobot/EventBus)、[Otto](https://github.com/square/otto)、[LocalBroadcast](http://developer.android.com/intl/ja/reference/android/support/v4/content/LocalBroadcastManager.html)などは`Activity`のインスタンスが生きていることが前提なので、この問題を解決するには至りませんでした。全ての画面のデータのみをシングルトン的に一箇所に持つという手もありますが、大規模アプリには向かないでしょう。
今回の設計では[SQLBrite](https://github.com/square/sqlbrite)を用いてこれを解決しています。

# RxJava + Flux

前置きが長くなりましたが、今回の設計の話をします。
要点は以下の3つです。

* FluxのDispatcher/StoreとしてRxJavaおよびその他のRxJava関連ライブラリを使う
* Storeからは全て`Observable`として状態が流れてきて、それを必要なViewがそれをsubscribeする。
* Viewは状態を持たず、Storeが状態を持つ。

## 必要なライブラリ

### Dispatcher/Storeとして使うライブラリ
* [RxJava](https://github.com/ReactiveX/RxJava)
* [SQLBrite](https://github.com/square/sqlbrite)
* [rx-preferences](https://github.com/f2prateek/rx-preferences)

### 併用したいライブラリ
* [Dagger2](http://google.github.io/dagger/)
 * DispatcherやStore,Actionのインスタンス管理に
* [RxAndroid](https://github.com/ReactiveX/RxAndroid)
* [RxLifecycle](https://github.com/trello/RxLifecycle)
* [RxBinding](https://github.com/JakeWharton/RxBinding)
 * ユーザのアクションが`Observable`として流れてくるので、全てをStream化することが出来ます。

RxBindingのようにユーザの入力イベントやセンサーなどのイベントを`Observable`に変えるものは積極的に導入した方が良いと思います。

## 最小設計

fluxではDispatcherを1つ用意することになっていますが、そのルールに則らずDispatcherは「アプリ全体で共有されるもの」と、特定の「Activity」内でのみ使われるものにScopeを分けて複数用意することとします。
これらのインスタンスの管理はDagger2を用いて行います。

```
├ data
├ ui
│ ├ main
│ │ ├ MainAction
│ │ ├ MainActivity
│ │ ├ MainComponent
│ │ ├ MainDispatcher
│ │ ├ MainFragment
│ │ ├ MainModule
│ │ ├ MainScope
│ │ └ MainStore
│ ├ AppAction
│ ├ AppDispatcher
│ ├ AppComponent
│ ├ AppModule
│ ├ AppScope
│ └ AppStore
```

## Dispatcher/Storeの種類

dispatchしたいイベントや保存したい状態によって使い分けます。

### PublishSubject

subscribeした後のemitをSubscriberに伝えるSubjectです。
状態を保持する必要のない一時的なEventなどをdispatchするのに向いています。

```kotlin:ErrorDispatcher.kt
val errorEventObservable = SerializedSubject(PublishSubject.create<String>())
```

```kotlin:ErrorStore.kt
fun errorEvents() = errorDispatcher.errorEventObservable
```

```kotlin:MainActivity.kt
class MainActivity : RxAppCompatActivity() {

    @Inject lateinit var mainStore: MainStore

    override fun onResume() {
        super.onResume()
        mainStore.errors()
                .observeOn(AndroidSchedulers.mainThread())
                .bindToLifecycle(this)
                .subscribe { showToast(R.string.error) }
    }
```
`onResume`内でsubscribeすればRxLifecycleによって`onPause`内でunsubcribeされるので、Activityが最前面に表示されている時に特定のActionを行いたい場合などに使えます。

### BehaviorSubject

`PublishSubject`と似ていますが、subscribeした際に直前の値をemitするので状態を保持出来ます。
画面間で連携する必要のない状態や、画面間で連携する必要があってその状態へのアクセスが多いものに向いています。

```kotlin:UserDispatcher.kt
val userObservable = SerializedSubject(BehaviorSubject.create<User>())
```

```kotlin:UserStore.kt
fun user() = userDispatcher.userObservable
```

```kotlin:MainActivity.kt
class MainActivity : RxAppCompatActivity() {

    @Inject lateinit var userStore: UserStore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        userStore.user()
                .observeOn(AndroidSchedulers.mainThread())
                .bindToLifecycle(this)
                .subscribe { userNameTextView.text = it.name }
    }
```

### BriteDatabase

[SQLBrite](https://github.com/square/sqlbrite)はSQLiteのqueryをstreamに変換するライブラリです。subscribeしているqueryがある場合に、同じテーブルに変更があると再度同一queryをSQLiteに適応し、結果がSubscriberへemitされます。

>```Java:BriteDatabase.java
  public long insert(@NonNull String table, @NonNull ContentValues values,
      @ConflictAlgorithm int conflictAlgorithm) {
    SQLiteDatabase db = getWriteableDatabase();
    if (logging) {
      log("INSERT\n  table: %s\n  values: %s\n  conflictAlgorithm: %s", table, values,
          conflictString(conflictAlgorithm));
    }
    long rowId = db.insertWithOnConflict(table, null, values, conflictAlgorithm);
    if (logging) log("INSERT id: %s", rowId);
    if (rowId != -1) {
      // Only send a table trigger if the insert was successful.
      sendTableTrigger(Collections.singleton(table));
    }
    return rowId;
  }
```
>https://github.com/square/sqlbrite/blob/0.6.2/sqlbrite/src/main/java/com/squareup/sqlbrite/BriteDatabase.java#L385

上記はinsert時の`BriteDatabase`内のコードですが、`sendTableTrigger`によってそのテーブルに対しての変更が他のSubscriberに伝わります。これをDispatcherとして使います。

```kotlin:MainDispatcher.kt
class MainDispatcher(val db: BriteDatabase) {
private val emptyObservable = SerializedSubject(PublishSubject.create<String>())

    fun repoObservable(screenId: String) =
            db.createQuery(TABLE_NAME, "SELECT * FROM $TABLE_NAME WHERE $SCREEN_ID=?", screenId)
                    .mapToList { Repo(it.getStringByName(REPO_ID), it.getStringByName(REPO_NAME)) }
                    .mergeWith(emptyObservable.filter { it == screenId }.map { emptyList<Repo>() })

    fun dispatch(screenId: String, list: List<Repo>) = db.newTransaction().run {
        var rows = 0
        try {
            db.delete(TABLE_NAME, "$SCREEN_ID=?", screenId)
            list.filterNotNull().map { repo ->
                ContentValues().apply {
                    put(SCREEN_ID, screenId)
                    put(REPO_ID, repo.id)
                    put(REPO_NAME, repo.name)
                }
            }.forEach {
                db.insert(TABLE_NAME, it)
                rows++
            }
            markSuccessful()
        } finally {
            end()
        }
        if (list.size == 0) emptyObservable.onNext(screenId)
        rows
    }
}
```

`BriteDatabase::createQuery`で欲しいデータのSELECT文を書いておけば同テーブルへの変更をsubscribeすることが出来ます。`mapToList`を使えばCursorを欲しいオブジェクトへmap出来ます。

上記の例ではdispatchする時にdeleteしてinsert、すなわちreplaceをしています。0件から0件になった場合のみ、SQLBriteは内部でdispatchしません。この挙動が他のDispatcherと違うので、その場合のみ別のSubject(上記の例では`emptyObservable`)から値を送ることにしています。

テーブルは同じActivityの別インスタンスでも共有しているので、インスタンスごとに`screenId`を割り振ってそれらを区別するようにしています。

```kotlin:MainActivity.kt
private var screenId: String by Delegates.notNull()

override fun onSaveInstanceState(outState: Bundle?) {
    super.onSaveInstanceState(outState)
    outState?.putString(KEY_SCREEN_ID, screenId)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    screenId = savedInstanceState?.getString(KEY_SCREEN_ID) ?: hashCode().toString()
}
```

SQLを書く煩雑さはありますが、前述のフォローのように他のStoreに変更を伝えたい場合はテーブルを変更しておけば、それがStoreを通じてViewに反映されます。画面間でデータを共有したいがサイズが大きいもの、リストなどで順番が意味を持つものを保持するのに向いているDispatcherです。

### RxSharedPreferences

[rx-preferences](https://github.com/f2prateek/rx-preferences)もSQLBriteと同様にSharedPreferencesの特定のkeyに対する変更を他のSubscriberにemitします。これをDispatcherとして使います。

```kotlin:UserDispatcher.kt
class UserDispatcher(val context: Context) {

    fun user() = context.getRxPrefs("user").getString("id").asObservable()

    fun dispatchUser(id: String) = context.getRxPrefs("user").getString("id").set(id)
}

fun Context.getRxPrefs(name: String): RxSharedPreferences = RxSharedPreferences.create(
        getSharedPreferences(BuildConfig.APPLICATION_ID + '.' + name, Context.MODE_PRIVATE))
```

画面間でデータを共有したいが順番は関係なく、かつ常に保持しておく必要のないものに向いているDispatcherです。

# RxBindingとの併用

Storeからはstreamとしてデータが流れてきますので、RxBindingと組み合わせることによってRxJavaの恩恵を受けることができます。

```kotlin
view.globalLayouts()
    .withLatestFrom(store.data()) { view, data -> data }
    .first()
    .bindToLifecycle(this)
    .subscribe { // Bind data to views. }
```
上の例では[`ViewTreeObserver.OnGlobalLayoutListener`](http://developer.android.com/intl/ja/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html)を用いてViewの高さなどが決まってからStoreのデータをViewに反映させています。

```kotlin
drawerStore.clickMenuEvents()
           .observeOn(AndroidSchedulers.mainThread())
           .doOnNext { activity.drawerLayout.closeDrawers() }
           .zipWith(drawerLayout.drawerOpen(Gravity.LEFT).skip(1).filter { !it }) { intent, drawer -> intent }
           .bindToLifecycle(this)
           .subscribe { startActivity(it) }
```
上の例で`clickMenuEvents`は`Observable<Intent>`を返します。これはStoreから流れてくるDrawerのアイテムが押されたイベントを受けてDrawerを閉じてからstartActivityをする例です。
RxBindingの`DrawerLayout::drawerOpen(Int)`は初期値をemitするので`skip(1)`をつけています。
RxJavaの`zipWith`を使うことでActivityやFragmentのプロパティ(Javaにおけるフィールド)に状態を持つことなく待ち合わせをすることが出来ます。`zipWith`の行を消すだけで待ち合わせのみがなくなる点がポイントです。

# Sample

上記の実装の例は以下にあります。

https://github.com/satorufujiwara/kotlin-android-flux

# Discussion

Fluxを意識しながら設計・実装していますが、複数のDispatcherが存在する点やAction内でデータレイヤーであるRepositoryにアクセスしているあたりがFluxとは違うのかなと考えています。
また、実際にまとめてみるとFluxというよりかはMVVMに近い実装かもしれません。StoreがMVVMのViewModelにあたるような実装ですが、以下のような点がMVVMと違っていると考えています。

* Storeへの変更は必ず「Action->Dispatcher」を経由する
* Viewからのイベントを伝えるActionとViewへ影響があるStoreが分離されている。
* Storeのどの変更を、どの期間、どのタイミングでregisterするかはViewに委ねられている (RxLifecycleが便利)
* Viewで起きるイベントがどのActionを呼ぶかはViewに委ねらている

現状考えているメリットデメリットは以下のような感じです。

## Pros

* Acitonに対するコールバックが無くなる
 * コールバックループを排除出来る
 * 無名クラスがActivity参照することによるメモリリークを意識する必要がない
* ActivityやFragmentが自身のライフサイクルに合わせてStoreをsubscribeすることが出来る
 * Storeはライフサイクルを意識する必要がない 
* SQLiteやSharedPreferencesを変更しておけば画面間の状態連携が可能となる
* 全ての状態変化をStream化出来る
 * RxJava好きな人にはたまらない 

## Cons

* Disaptcherのインスタンスに関して気を配る必要がある
 * Dagger2のScopeごとにインスタンス化されるのでSubjectはインスタンスごとに伝わる
 * DispatcherがSQLBrite/rx-pereferencesである場合はScopeに関係なくアプリ全体に伝わる
* SQLiteの管理に気を配る必要がある
 * ActivityごとのSQLiteではscreenIdで管理する必要がある 
 * アプリの起動時に全て消す
 * テーブル間の同期のSELECT文は都度書く
* テストについては(まだ)考えられていない