FluxのDispatcherをRoomのin memoryで実装するのは、冗長なコードが多くなるので良くない

FluxのDispatcherをRoom in memoryで実装すれば最高なのでは?と思い、ちょっと試してみました。

結論から申しますと冗長なコードが多く、良くないと感じました。EventBusや、他のライブラリを使って実装したほうが良いと思います😂

また、オリジナルFluxは「Dispatcherがアプリ内で1つだけ存在する」という原則があったと思いますが、それを破っています。Fluxですらない可能性があります。

Room in memory?

Roomではin memoryでデータベースを作ることが出来ます。正確に言えば、SQLiteの機能をRoomのAPIとして開放しています。

使い方は次のようになります。

Room
  .inMemoryDatabaseBuilder(context, MyDatabase::class.java)
  .build()

in memoryを使う理由としては、

になります。

実装に入っていく

では、実装の説明をしていきます。

まずはActionをRoomのEntityとして定義します。

sealed class AuthorAction

@Entity(tableName = "author1")
data class Author1(
  @PrimaryKey val _id: Long = 0, // always 0
  val name: String,
  val age: Int
) : AuthorAction()

@Entity(tableName = "author2")
data class Author2(
  @PrimaryKey val _id: Long = 0, // always 0
  val name: String,
  val age: Int
) : AuthorAction()

Primary keyは常に一定にして、アクションは0 or 1つしか存在しないようにしておきます。仮にアクションの履歴が欲しいなら、@PrimaryKey(autoGenerate = true)を使っても良いと思います。

次にDaoを定義します。これはFluxでいうところのDispatcherになります。

@Dao
interface AuthorDispatcher {
  @Insert(onConflict = OnConflictStrategy.REPLACE) fun dispatch(author: Author1)
  @Insert(onConflict = OnConflictStrategy.REPLACE) fun dispatch(author: Author2)

  // Storeに相当する
  @Query("select * FROM author1 WHERE _id = 0")
  fun author(): LiveData<Author1?>

  // Storeに相当する
  @Query(
    """
    select author1.name as name1, author2.name as name2
    FROM author1
     INNER JOIN author2
    WHERE author1._id = 0 AND author2._id = 0
    """
  )
  fun mappedAuthor(): LiveData<MappedAuthor?>
}

となります。@Insertでアクションをdispatchメソッドを、@Queryでsubscribeメソッドを実装しています。

これで、FluxのDispatcherに似た何かをRoomで表現することが出来ます!

まとめ/考察

RoomでDispatcher的なのを作る方法と、EventBusなどを使ったアプローチの違いは以下になります。

工夫すれば、もう少しキレイに書けるとは思いますが、ただEventBusや、Coroutineを使ったアプローチには敵わないと思っています。 FluxのDispatcherの代替としては辛いですが、他の用途、例えばRepositoryの実装などには使える余地があると思うので、そういったところで思い出していただけたら幸いです😊

検証に用いたサンプルコードはRoomDispatcherExampleにあります。

Written by
あんどろいどでぃべろっぱぁー🍎