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

# はじめに

私はこれまで次のようにAndroidアプリ設計に関わる記事を書きました。

- [Kotlin + Architecture Component + Dagger2によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370)
- [RxJava + Flux (+ Kotlin)によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/cbf304891daec87ba5b7)

これら2つ記事の内容を組み合わせた、「Architecture Components + FluxによるAndroidアプリ設計」について考えたので本記事にその設計をまとめました。
結論から言うと**FluxアーキテクチャとArchitecture Componentsの相性はとても良い**と思います。

# Sample

実装のサンプルは以下にあります。

https://github.com/satorufujiwara/android-flux-architecture/tree/master/flux-arch

このリポジトリにはFluxアーキテクチャのDispatcherやStoreからViewへのデータの反映を様々な実装で実現したサンプルがあるので、合わせて参考にしていただければ幸いです([Android アプリ設計パターン入門](https://peaks.cc/archtecture_patterns)執筆時のサンプルの一部として作ったので、[そのサンプルリポジトリ](https://github.com/TechBooster/Archtecture-Patterns-Samples)にも同様の実装があります)。

# 実装について

全体の設計は次のようになります。

![flux-arch.jpg](https://qiita-image-store.s3.amazonaws.com/0/36413/169532e6-7b13-b6e9-5279-886782a9df2a.jpeg)

## Android Architecture Components

Android Architecture Components(AAC)の導入などについては次の記事に書いてあるので、そちらをご覧ください。

- [Kotlin + Architecture Component + Dagger2によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370)

## Flux

Fluxは前出の図のように**データフローを単一方向にする**アーキテクチャです。
Action、Dispatcher、Store、Viewという4つのコンポーネントがあり、それぞれ次のような役割を担っています。

- Action (Action Creator) : 特定の要求（Viewからのユーザ入力など）を処理し、その結果となるデータをStoreへと伝達する
- Dispatcher : ActionをStoreへと伝達する
- Store : Dispatcherから送られたActionおよびそれが含むデータに応じて自身の状態を変更し、保持する
- View : Storeの状態に応じて結果などを画面に表示する

### Store/Dispatcherの実装

今回の実装の肝は**Architecture ComponentsのViewModelをFluxアーキテクチャのStoreとみなす**ところです。
それをより明確にするため、次のようなクラスを用意しました。

```kotlin
abstract class Store : ViewModel()
```

これを継承して画面ごとにStoreを用意します。
以下はメインとなる画面の`MainStore`というクラスです。

```kotlin:MainStore.kt
class MainStore @Inject constructor(private val dispatcher: MainDispatcher) : Store() {

  val repos: LiveData<List<Repo>> = dispatcher.onRefreshRepo
      .map { it.data }
      .observeOn(AndroidSchedulers.mainThread())
      .toLiveData()

  val repoReadmeUrl: LiveData<String> = dispatcher.onShowRepoReadme
      .map { it.data }
      .observeOn(AndroidSchedulers.mainThread())
      .toLiveData()
}
```

MainDispatcherは次のようになっており、流すデータごとにDispatcherが存在します。

```kotlin:MainDispatcher.kt
class MainDispatcher : Dispatcher() {

  private val dispatcherRefreshRepo: FlowableProcessor<MainAction.RefreshRepo>
      = BehaviorProcessor.create<MainAction.RefreshRepo>().toSerialized()
  val onRefreshRepo: Flowable<MainAction.RefreshRepo> = dispatcherRefreshRepo
  private val dispatcherShowRepoReadme: FlowableProcessor<MainAction.ShowRepoReadme>
      = BehaviorProcessor.create<MainAction.ShowRepoReadme>().toSerialized()
  val onShowRepoReadme: Flowable<MainAction.ShowRepoReadme> = dispatcherShowRepoReadme

  fun dispatch(action: MainAction.RefreshRepo) {
    dispatcherRefreshRepo.onNext(action)
  }

  fun dispatch(action: MainAction.ShowRepoReadme) {
    dispatcherShowRepoReadme.onNext(action)
  }

}
```

ここでは`BehaviorProcessor`を用いていますが、「[RxJava + Flux (+ Kotlin)によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/cbf304891daec87ba5b7)」の記事内で書いたような様々なRxJava2のクラスをDispatcherとして使うことが出来るかと思います(以前の記事ではRxJava1のクラスを用いているのでその点は注意してください)。
また、Architecture Componentsの[Roomを使ってデータベースからRxjava2](https://medium.com/google-developers/room-rxjava-acb0cd4f3757)のクラスでデータを流せば、これもDispatcherとして用いることができそうとも考えています。

`MainStore`クラス内の`toLiveData()`は拡張関数で、次のように定義されています。
`android.arch.lifecycle:reactivestreams`ライブラリ内の関数`LiveDataReactiveStreams.fromPublisher()`を呼び出しています。

```kotlin:toLiveData()
fun <T> Publisher<T>.toLiveData() = LiveDataReactiveStreams.fromPublisher(this)
```

### Action/ActionCreatorの実装

次にActionは以下のようなインタフェースを用意しました。

```kotlin:Action.kt
interface Action<out T> {
  val data: T
}
```

このインタフェースを実装したseald classを以下のように定義します。

```kotlin:MainAction.kt
sealed class MainAction<out T> : Action<T> {
  class RefreshRepo(override val data: List<Repo>) : MainAction<List<Repo>>()
  class ShowRepoReadme(override val data: String) : MainAction<String>()
}
```

特定の画面(ここでは`MainActivity`)でのActionの種類の一覧性を高めるためにseald classを用いていますが、data classでも良いかもしれません。
そしてActionCreatorの実装は次のようになります。

```kotlin:MainActionCreator.kt
@PerActivityScope
class MainActionCreator @Inject constructor(
    private val dispatcher: MainDispatcher,
    private val repository: GitHubRepository
) {

  fun fetchRepo(repoOwner: String)
      = repository.fetchUserRepos(repoOwner)
      .subscribeOn(Schedulers.io())
      .subscribeBy(
          onSuccess = {
            dispatcher.dispatch(MainAction.RefreshRepo(it))
          },
          onError = {
            Timber.e(it)
          })

  fun fetchReadme(repoOwner: String, repoName: String)
      = repository.fetchReadme(repoOwner, repoName)
      .subscribeOn(Schedulers.io())
      .subscribeBy(
          onSuccess = {
            dispatcher.dispatch(MainAction.ShowRepoReadme(it.html_url))
          },
          onError = {
            Timber.e(it)
          }
      )

  fun showRepoDetailDialog(activity: FragmentActivity, repoOwner: String, repoName: String) {
    RepoDetailDialogFragment.newInstance(repoOwner, repoName)
        .show(activity.supportFragmentManager, "MainAction.RepoDetailDialog")
  }
}
```

`GitHubRepository`の関数を呼び出し、その結果を`MainDispatcher`へと流しています。

### MainActivityの実装

以上のように`MainStore`、`MainDispatcher`、`MainAction`、`MainActionCreator`を用意すると、Viewコンポーネントである`MainActivity`の実装は次のようになります。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {

  @Inject lateinit var androidInjector: DispatchingAndroidInjector<Fragment>
  @Inject lateinit var storeProvider: StoreProvider
  @Inject lateinit var actionCreator: MainActionCreator
  private val store by lazy { storeProvider.get(this, MainStore::class) }
  private val binding by lazy { DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity) }
  private val adapter = MainAdapter()
  private val ownerName = "satorufujiwara"

  override fun supportFragmentInjector() = androidInjector

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding.recyclerView.adapter = adapter
    binding.recyclerView.layoutManager = LinearLayoutManager(this)
    adapter.onItemClicked = {
      actionCreator.showRepoDetailDialog(this, ownerName, it.name)
    }

    store.repos.observe(this) {
      it ?: return@observe
      adapter.run {
        items.clear()
        items.addAll(it)
        notifyDataSetChanged()
      }
    }
    actionCreator.fetchRepo(ownerName)
  }
}
```

画面が表示されると` actionCreator.fetchRepo(ownerName)`が呼び出され、その結果`LiveData`である`store.repos`が更新され、`observe`しているラムダが読み出され、`adapter`を使って`RecyclerView`の表示が更新されます。
`StoreProvider`については次節にて説明します。

## Dagger2

Dagger2を使った依存性注入についても下記の記事から大きくは変わっていません。

- [Kotlin + Architecture Component + Dagger2によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370)

今回も[dagger.android](https://google.github.io/dagger//android.html)を使っています。その導入方法や`@ContributesAndroidInjector`アノテーションの利用方法については上記の記事をご覧ください。

### StoreProviderの実装

Architecture ComponentsのViewModelをFluxアーキテクチャのStoreとみなしているので、Architecture Componentsの`ViewModelProvider.Factory`を継承したインタフェース`StoreProvider`を次のように定義します。

```kotlin:StoreProvider.kt
interface StoreProvider : ViewModelProvider.Factory {

  fun <T : ViewModel> get(activity: FragmentActivity, storeClass: KClass<T>): T

  fun <T : ViewModel> get(fragment: Fragment, storeClass: KClass<T>): T

}
```
このインタフェースを実装したクラスを次のように定義します。

```kotlin:ViewModelFactory.kt
class ViewModelFactory @Inject
constructor(private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>)
  : StoreProvider {

  @Suppress("UNCHECKED_CAST")
  override fun <T : ViewModel> create(modelClass: Class<T>): T {
    var creator: Provider<ViewModel>? = creators[modelClass]
    if (creator == null) {
      for ((key, value) in creators) {
        if (modelClass.isAssignableFrom(key)) {
          creator = value
          break
        }
      }
    }
    if (creator == null) throw IllegalArgumentException("unknown model class " + modelClass)
    try {
      return creator.get() as T
    } catch (e: Exception) {
      throw RuntimeException(e)
    }
  }

  override fun <T : ViewModel> get(activity: FragmentActivity, storeClass: KClass<T>) =
      ViewModelProviders.of(activity, this).get(storeClass.java)

  override fun <T : ViewModel> get(fragment: Fragment, storeClass: KClass<T>) =
      ViewModelProviders.of(fragment, this).get(storeClass.java)
}
```

`StoreProvider`インタフェースを継承しているので`get`関数が増えていますが、このクラスも
「[Kotlin + Architecture Component + Dagger2によるAndroidアプリ設計](https://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370)」の`ViewModelFactory`クラスとほぼ同一です。

### MainModule

`@ContributesAndroidInjector`アノテーションを利用して、Injectに必要な定義を書きます。

```kotlin:UiModule.kt
@Module
internal abstract class UiModule {

  @Binds
  abstract fun bindViewModelFactory(factory: ViewModelFactory): StoreProvider

  @PerActivityScope
  @ContributesAndroidInjector(modules = [MainModule::class, MainDispatcherModule::class])
  internal abstract fun contributeMainActivity(): MainActivity

}
```

DaggerのMutliBindingによる定義(`@Binds`や`@ContributesAndroidInjector`)と`@Provides`による定義が同クラスに共存出来ないので、以下のように分けてあります。

```kotlin:MainModule.kt
@Module
internal abstract class MainModule {

  @Binds
  @IntoMap
  @ViewModelKey(MainStore::class)
  abstract fun bindMainStore(viewModel: MainStore): ViewModel

  @ContributesAndroidInjector
  abstract fun contributeRepoDetailDialogFragment(): RepoDetailDialogFragment
}

@Module
internal class MainDispatcherModule {

  @PerActivityScope
  @Provides
  fun provideMainDispatcher() = MainDispatcher()

}
```

以上のように定義することで`MainActivity`内で、`StoreProvider`および`MainStore`のインスタンスを次のように取得することができます。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {

  @Inject lateinit var storeProvider: StoreProvider
  private val store by lazy { storeProvider.get(this, MainStore::class) }

  //略
}
```

## その他のライブラリ

Architecture Component、Dagger2以外に以下のようなライブラリを用いています。

 * DataBinding - https://developer.android.com/topic/libraries/data-binding/index.html
 * OkHttp - http://square.github.io/okhttp
 * Retrofit - http://square.github.io/retrofit
 * Moshi - https://medium.com/square-corner-blog/kotlins-a-great-language-for-json-fcd6ef99256b
 * Glide - https://github.com/bumptech/glide
 * RxJava - https://github.com/ReactiveX/RxJava
 * RxAndroid - https://github.com/ReactiveX/RxAndroid
 * RxKotlin - https://github.com/ReactiveX/RxKotlin
 * Timber - http://github.com/JakeWharton/timber

# まとめ

- Architecture Componentsの`ViewModel`をFluxアーキテクチャのStoreとしてつかう
- DispatcherにはRxJava2の`Flowable`を用い、Store内で`LiveData`へと変換
- Viewから先は`LiveData`を用いる
- **FluxアーキテクチャとArchitecture Componentsの相性はとても良い**