2020年1月に出版したKotlin本とFlowの活用ついて

本だしたぞ!

みんなのKotlin 現場で役立つ最新ノウハウ!

KotlinはJetBrains社が開発したプログラミング言語で,Androidアプリ開発やサーバサイドアプリケーション開発など,さまざまな現場で次第に定着してきました。本書では,Kotlinによる開発を成功させるために現場で必要となる基本から実践までの活用ノウハウを,開発の最前線でKotlinを活用しているエンジニアがわかりやすく解説します。CoroutineやKotlin …

2020年1月にみんなのKotlin 現場で役立つ最新ノウハウを出版しました。もう4ヶ月も前なのでいまさらなのだが。
CyberAgent時代の同僚やYahoo! Japanの友人たちと、日々の仕事の中で得た知識を詰め込んだ1冊です。

第1章 Kotlinの始め方
第2章 Androidアプリケーション開発におけるKotlin活用ノウハウ
第3章 Kotlinによるサーバサイドアプリケーション開発
第4章 実践 Kotlin開発 最新情報

私は第4章の中のCoroutinesのセクションを担当しました。後半は_a_akiraのKotlin Multi Platform Project。

担当セクションの前半はCoroutine BuilderやCoroutine Scopeやスレッドなど、知っておかないと後々痛い目にあいそうな基礎について解説し、後半ではChannelやCorotuineによる並列処理/逐次処理、コールバック関数のCoroutine化、コルーチン内のスレッド切り替え、エラーハンドリングといった実践的な内容についても触れていいる。
本を書いたとはいえ、脳の記憶容量は限られているので、ちょくちょく忘れることがある。そんな時に「あ〜コルーチンわかんね〜自著読んで思い出すか〜。」と言うと割とウケをとれるので思わぬ儲けもの。

最近登場したFlowについても触れているが、紙面の都合上、Channelとの違い程度にしか触れられなかった。 よって、今回は、最近私がどのようにFlowをつかっているかを紹介してみる。

Flowのユースケース

Reactive Programming

Coroutineの登場により、私は非同期処理のためにRxを使うことはなくなった。
しかし、リアクティブなUIを実装するにあたってはやはりRxが欲しいのだが、最近はこれをFlowで補っている。
便利なのが、LDRAlighieri/CorbindというRxBindingのFlow実装のようなライブラリ。

combine(
    binding.checkBoxOne.checkChanges(),
    binding.checkBocTwo.checkChanges()
) { isOneChecked, isTwoChecked -> isOneChecked && isTwoChecked }
  .onEach { binding.button.isEnabled = it }
  .launchIn(viewLifecycleOwener.lifecycleScope)

こんな感じで、すべてのチェックボックスにチェックがついたらボタンをenabledにするといったようなリアクティブな処理をこれで実装したりしている。
onEachの中でsuspend関数を扱うこともできるので、Flowでイベントが飛んできたらIOスレッドでなんか処理するみたいなこともできる。

コールバック関数をFlowでラップ

コールバック関数を渡すと、そこに対して複数のデータが送られてくるようなインターフェースを提供してるライブラリが結構ある。これらをFlowでラップすることで、Coroutineとの親和性を高め、コールバックをトリガーにした非同期処理を簡単に実装できる。
試しにFusedLocationProviderClient#requestLocationUpdatesをFlowでラップしてみよう。

fun FusedLocationProviderClient.requestLocationUpdates(locationRequest: LocationRequest) =
    channelFlow<LocationResult> {
        val listener = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                offer(locationResult)
            }
        }
        requestLocationUpdates(locationRequest, listener, null)
            .addOnCanceledListener { close() }
            .addOnFailureListener { close(it) }
        awaitClose()
    }
fusedLocationProviderClient,requestLocationUpdates(...)
  .onEach { repository.postCurrentLocation(it) }
  .launchIn(scope)

End

今の所はこんな感じにつかっている。
本当のことを言うともっといろいろな場面で使ったりしている。
例えば、gRPCとかSocket.IOとか、Play Billing Libraryといったライブラリにはコネクションの概念があるわけだが、それらのコネクションがつながっている間だけ使えるAPIってのも存在する。
これを、コネクションがつながってたら実行するという実装からコネクションがつながってなかったらコネクションがつながるまで中断して、つながったら再開して実行みたいな実装にしたいときにFlowを活用している。最適な実装をできているかどうか、まだ確証を持っていないので詳細を紹介するのは控えておく。

Flowではないのだが、Chris Banesが同じようなことをView#awaitLayout()という関数を実装していたので、そちらのリンクを貼っておく。
ViewExtensions.kt