Takuji->find;

株式会社はてなでアプリケーションエンジニアやってます、技術的な記事を書いているつもり

100人を超えるコミュニティーを1年半くらい運営して得たDiscordサーバー運営術

2022年10月に約1年半運営してきたウマ娘のサークルを解散した。

サークルを解散するまでの1年半で学んだことを2022年も終わるのでここに供養したいと思う。

いわゆるオンゲーのギルドも会社組織みたいなもんだと思うので、仕事にも役立つかもしれない。

考え方的なこと

細かく言及するようなことでもないので軽く羅列

  • 自分や周りの人が当たり前のようにできても、それができない人はいる
  • 守れない人の多いルールはそもそもルールが破綻してることの方が多いから考え直す
  • 何でも一人で抱えすぎない
    • 協力的な人を見つけて巻き込む
  • 運営メンバーの考えはちゃんとアウトプットする
  • 多様性に配慮する

テキストコミュニケーション

古くはIRC、今はSlackやDiscordで仕事でもプライベートでもコミュニケーションを取る人間なのでテキストチャットでのコミュニケーションに関する知見はそれなりにあると思うがそれでも学ぶことは多かったと思う。

Webページのスクショや拾いモノ画像は貼るな、URLを貼れ

これスマートフォンネイティブの人がやりがちだと思っているんだけど、スクショは下記のような点で圧倒的にURL+埋め込みの情報表示に劣るので本当にやめた方がいい。

  • ソースとしての信頼性が低い
  • 更新に追従しない
  • テキストのコピーができない
    • これは一部の情報において致命的

特に上の2つは軽視してる人が多いんだけど、デマが広がったり情報が誤解されたまま解釈されるとかあるので本当に良くない。

ウマ娘愛好会では基本的にWebページのスクショを貼るのを禁止していて、必ずソースのURLを貼るようにお願いしていた。

これはTwitterから拾った画像です、とかYouTubeのスクショとかも同様。どれも該当するパーマリンクが存在するんだからそれを貼ってくれ〜〜〜〜〜〜〜

Bot導入

積極的に導入せよ、いなくて困ることはあってもいて困ることはそんなにない。

あなたがNode.jsの書けるソフトウェアエンジニアなら自分でBot書くのもあり。

ウマ娘愛好会は最終的にサークル加入からメンバー管理、通話の読み上げ、ファン数の管理まで全てBotで行われるようになった。

適切なロール運用

最低限運営メンバーとそれ以外、くらいのロールはあった方がいい。

運営メンバー以外のロールはeveryoneでもよさそう。

everyoneの権限をデフォルトから削るのは、メンバーにかなりの不便を強いるのでやめるのが吉。

ちなみにウマ娘愛好会ではeveryoneにはデフォルトに+でイベント管理や絵文字管理の権限をつけていた(メンバーでも使いやすくなるように)

最多の頃で120人以上メンバーがいたけど、悪用されたことはない。

適切なテキストチャンネルの分割

話す人間が多くなってくるとテキストチャンネルの分割は必須になってくる。アクティブで喋る人が4-5人くらいでもう分割を考えた方がいいと思っている。

ゲームのDiscordサーバーなんかだと分け方は分かりやすくて、コンテンツとか目的で分けると吉。

その他だと以下のようなチャンネルが作られると良いと思う。

  • イベント系
    • イベントの情報を話すチャンネル、規模によってはイベントごとに作ってもいいだろうし、イベントだけでまとめててもいいと思う
  • 攻略系
    • 攻略情報を貼るようなチャンネル、コンテンツによって必要なことが違うゲームはできる限りコンテンツごとに分けた方がいい。例えばウマ娘だとチャンミと競技場で必要な育成方法が全然違う。
  • ガチャ報告系、愚痴系
    • 一般的に聞きたくない、見たくない人が一定層いるものを隔離するチャンネル。人が増えてきたらあった方がいいし、人が増えてなくてもガチャ結果系は隔離した方が平和を保てると思う。
  • ゲーム外の雑談チャンネル
    • ゲームがきっかけで集まった人でもゲーム以外の雑談したくなると思う。ウマ娘愛好会では出張時に現地の人にウマい飯店を聞くという行為が流行っている印象。
    • 競馬の話題のチャンネルではG1の開催日は普段話さない人も含めて盛り上がっている。(自分は見ているだけだけど)
    • 別ゲーを一緒にやるためのチャンネルもある、一時期Apexとか麻雀とかがめっちゃ流行っていた(Apexは今はウマ娘プレーヤーの集まるサーバーに移行したけど)
  • 運営メンバーとのコミュニケーションチャンネル
    • これは重要、ちゃんとメンバーとコミュニケーションしよう。一方的なやり方だと誰もついてこない。
    • お知らせのチャンネルは運営メンバーだけ書き込めるようにしておきましょう

ボイスチャンネル

ボイスチャンネルの有無はサーバーの方向性によりそう。個人的にはあってほしい。

聞き専の人の参加ハードル下げるために読み上げ系botは必須。音楽再生Botとかはお好みで。

あらかじめ想定されるチャンネルを作っておくのもいいけど、それ以外に雑多に使えるように一時的なボイスチャンネルを作れるBotを導入したりすると便利。

サークルDiscordサーバーの今

解散して3ヶ月が経とうとしているウマ娘愛好会のDiscordサーバーだが、未だに毎日誰かが喋っている状態。ここから一緒に他のゲームをやったり、相変わらずウマ娘の話や競馬の話をしたりで盛り上がっている。

跡地としてはまあ成功したんじゃないか。

最後に

まとまらなかった。今後もうこんな規模のDiscordサーバーを運営する機会はたぶん来ないが、いい勉強になった。

ウマ娘のサークルグループDiscord Botを開発・運用している話

ウマ娘にはオンゲによくあるギルドみたいな「サークル」という機能があって、サークルごとに1ヶ月で育成したウマ娘のファン数合計で競うランキング機能があったり、キャラ強化用のアイテムをサークルメンバーに寄付してショップでアイテム交換できるポイントをもらったり、みたいなことができる。

umamusume.jp

自分が所属しているサークルグループ「ウマ娘愛好会グループ」では創設サークルの「シン・ウマ娘愛好会」や昨年12月からグループに加わったランキング上位サークルの「西京ファーム」を始め3つのサークル最大90人が一つのグループとして活動していて、Discordサーバーを共有している。

3つあるサークルは枠としては別モノだが、毎月サークル間で移籍することができたり、運営は3サークル合同で行ってたりしていて加入手続きやメンバー管理、募集枠の確定などあらゆるプロセスが複雑になりがち。 それを解決するために自前でDiscordのbotとWebUIを開発して運用している。

この記事ではそのシステムの概要について書き連ねることにする。

それぞれの細かい内容は別途記事にする。

続きを読む

シンボリックリンクを含むディレクトリを再帰的にコピーしたい場合はcp -Rを使うとよい

CUIでよく使うコマンドに cp があるが、ディレクトリーごとコピーしたい場合は cp -r src dest みたいな感じで -r をよく使っている。

しかしこれだとシンボリックリンクをはっていた時に実体がコピーされてしまってリンクが切れる。

そこで -r の代わりに -R を使うとシンボリックリンクのままコピーされてリンクを保つことができる。

linuxjm.osdn.jp

既存パラダイムとKotlin Coroutinesの共存

この記事は DroidKaigi 2020, day 1 17:00-17:40で発表される予定だった内容を書き起こしたものです。

droidkaigi.jp

他にも発表予定だった資料のAGENDAを1項目ごとに記事にして公開する予定です。

  • 既存パラダイムとの共存 (この記事)
  • ライブラリーと組み合わせる
  • Activity/Fragmentで利用する
  • ViewModelで利用する
  • LiveDataとの使い分け
  • 例外処理
  • キャンセル可能な作りにする
  • テスト

はじめに

Kotlin Coroutinesが正式リリースされて2年ちょっとが過ぎ、もはや私たちのAndroidアプリ開発には欠かせないものとなりました。

コルーチンはRxJavaのようなストリームやコールバック方式と比べて非同期的な処理を直感的に記述でき、これまでRxJavaを使っていたようなユースケースをある程度置き換えられるようになっています。 ​ ​ 私はこの2年と少しでKotlin Coroutinesを複数のアプリに導入し既存のパラダイムの置き換えを進めてきました。

Kotlin Coroutinesの導入を行ったことでAndroidアプリ開発が楽になりましたが、大変になったこともありました。

恐らくKotlin Coroutinesが難しく感じて導入をためらっている方もいるでしょう。 ​

このシリーズでは、既存のAndroidアプリにKotlin Coroutinesを導入したことによってAndroidアプリ開発がどう変わったか、

Kotlin Coroutinesの導入を行ったことで困ったことをどう解決したかなどを紹介します。

皆様のKotlin Coroutinesの導入の参考になれば幸いです。

また、Kotlin Coroutinesに関してまだ何も知らないという方はこのシリーズを読む前に以下の記事か、Kotlin Coroutinesの公式ドキュメントを一読されることをおすすめします。

blog.takuji31.jp

既存パラダイムとの共存

既存アプリにKotlin Coroutinesを導入するにあたっておそらく最初の壁となるのが既存のパラダイムへの影響でしょう。 Kotlin Coroutinesで書くと既存のRxJavaやイベントハンドラーのようなものをコルーチンに置き換えたくなるはずです。

既存のパラダイムを全部置き換えるのは工数がない、ライブラリーやフレームワークのコードは簡単には変えられない、といった問題で現実的ではなく実際は一部を置き換えるということになるでしょう。

  • 特定の機能だけKotlin Coroutinesで書く
  • 特定のレイヤー(ViewModel/Repository/Data層など)だけKotlin Coroutinesで書く

どちらの方法で進めても途中で必ず既存のパラダイムとKotlin Coroutinesの仕組みとの変換が必要になってきます。 この記事ではRxJavaやコールバック、イベントリスナーといったような仕組みとKotlin Coroutinesを繋ぎ込む方法を紹介します。

RxJavaとKotlin Coroutines

RxJavaはおそらく現代のAndroidアプリ開発Jetpackの次くらいに一番よく使われているライブラリーではないでしょうか。

リアクティブプログラミングによってAndroidアプリ開発者は複雑な非同期処理を行う際のコールバック地獄から解放されました。

一方でコールバック地獄の次はOperator地獄、実行するスレッドを切り替えるための仕組みであるSchedulerが分かりにくい、Operatorをめちゃくちゃ組み合わせたら書いた本人以外何やってるのか分からないコードが完成する、などと人類には早すぎた感も否めないように思えます。

RxJavaとKotlin Coroutinesは kotlinx-coroutines-rx2kotlinx-coroutines-reactive を使うことで相互運用が可能です。

RxJavaをコルーチン内で使う

RxJavaの各種ストリームをコルーチン内で使うための拡張関数が kotlinx-coroutines-rx2kotlinx-coroutines-reactive (Reactive Streamsの Publisher 実装である Flowable はこちら) には用意されています。

以下のようなRepositoryのコードがあるとします。

interface UserRepository {
  fun getCurrentUserId(): Maybe<UserId>
  fun getUser(userId: UserId): Single<User>
  fun userStatus(): Observable<UserStatus>
  fun userStatusFlowable(): Flowable<UserStatus>
}

このRepositoryのメソッドはそれぞれコルーチン内で以下のように使えます。

val repository : UserRepository

scope.launch {
  // get curent user id
  val userIdOrNull: UserId? = repository.getCurrentUserId().await()

  userIdOrNull?.let {
    // get user
    val user: User = repository.getUser(it).await()
  }

  // await first user status
  val userStatus: UserStatus = repository.userStatus().awaitFirst()

  // also ok
  val userStatus: UserStatus = repository.userStatusFlowable().awaitFirst()
}

もちろんそれぞれのストリームがsubscribeした時に同期的に処理をする作られている場合はsubscribeしたその場で処理が同期的に行われてしまうので、通信やディスクI/Oを伴う処理が含まれている場合は以下のような対策が必要です。

  • withContext {} を利用してメインスレッド上で実行されないようにする
  • RxJavaのSchedulerを使って処理を非同期に行う

また、これ以外にも ObservableFlowableFlow に変換する拡張関数も用意されています。 複数流れてくる値をコルーチンで処理したい場合にはこちらを使います。

interface UserRepository {
  fun userStatusFlowable(): Flowable<UserStatus>

  fun userStatusFlow(): Flow<UserStatus>
    = userStatusFlowable().asFlow()
}

コルーチンを使ってRxJavaのストリームを作る

RepositoryをKotlin Coroutinesを使って書くようにしたはいいがViewModelがまだRxJavaで〜とかUseCaseで他のRxJavaのストリームと組み合わせないといけなくて、みたいなことがあった場合にはSuspending functionやFlowを使ってRxJavaのストリームを作ることもできます。

interface UserRepository {
  suspend fun getCurrentUserId(): UserId?
  suspend fun getUser(userId: UserId): User
  fun userStatus(): Flow<UserStatus>
}

rxXXXX {} という関数が用意されているので、この関数のラムダ式内でSuspending functionやFlowを使うと簡単にRxJavaのストリームを作ることができます。

val repository: UserRepository
val currentUserId: Maybe<UserId>
  = rxMaybe { repository.getCurrentUserId() }

val userId: UserId
val user: Single<user>
  = rxSingle { repository.getUser(userId) }

val userStatusFlowable: Flowable<UserStatus>
  = rxFlowable { producer ->
    repository.userStatus().collect { producer.send(it) }
  }

また、 Flow を簡単に ObservableFlowable にも変換できます。

val userStatusFlowable: Flowable<UserStatus>
  = repository.userStatus().asFlowable()

val userStatusObservable: Observable<UserStatus>
  = repository.userStatus().asObservable()

その他

ここで紹介した以外にも kotlinx-coroutines-rx2 にはいくつかの関数が用意されてますので、詳しくは公式ドキュメントをチェックしてください。

kotlin.github.io

コールバックとKotlin Coroutines

ここでいうコールバックとは以下のようなものです。

  • 何かしらの非同期な処理を行って、成功時に結果をメソッドやラムダ式に返す
    • 値を返すのは1回だけ
  • 例外が起きた時も例外をメソッドやラムダ式に渡す
    • 成功時と同じメソッドでもいいし別のメソッドでもいい
  • 途中でキャンセル可能でもよい

コールバックをSuspending functionに変換するには以下の2つの関数が使えます。

  • suspendCoroutine
  • suspendCancelableCoroutine

2つの違いは名前通りキャンセル可能かどうかなので、必要に応じて選択してください。

これらの関数はKotlin Coroutinesの低レベルAPIContinuation を受け取ってこのインスタンスに値を渡すことで再開できる関数を作るといったものです。

例えば以下のようなHTTPクライアントがあるとします(実装は省きますが名前で雰囲気を掴んでいただく感じで)

interface HttpClient {
  fun myUser(callback: HttpClient.Callback<User>): Request

  interface Callback<T> {
    fun onSuccess(res: T)
    fun onFailure(e: HttpException)
  }

  interface Request {
    fun cancel()
  }
}

これをSuspending functionに変換するとこうなります。

suspend fun HttpClient.myUser() : User
  = suspendCoroutine { cont ->
    myUser(object: HttpClient.Callback<User> {
      fun onSuccess(res: User) {
        cont.resume(res)
      }

      fun onFailure(e: HttpException) {
        cont.resumeWithException(e)
      }
    })
  }

あとはコルーチン内で普通に他のSuspending functionと同じように使えます。

scope.launch {
  val httpClient: HttpClient

  val user: User = httpClient.myUser()
}

イベントリスナーとKotlin Coroutines

ここでいうイベントリスナーとは以下のようなものです。

  • 何らかのイベントが起きるとメソッドやラムダ式が呼ばれる
  • Listenerを登録すると延々と値が流れてくる
  • 不要になったら明示的に登録解除する必要があったりなかったり

複数の値が流れてくるのでFlowが使えます。

ただしFlowを作るための一番簡単な関数である flow {} は同じコルーチン内からしか値を送ることができません。こういったコルーチンのスコープ外から複数の値を送るFlowを作るのには callbackFlow {} を使います。

似たような関数に channelFlow {} がありますが、こちらはFlowが閉じられるのを待つ awaitClose {} を呼び出したかどうかのチェックがありません。

val View.onClick: Flow<View> = callbackFlow {
  setOnClickListener { view ->
    offer(view)
  }

  awaitClose {
    setOnClickListener(null)
  }
}

この関数で生成されたFlowをcollectすればクリックしたイベントがコルーチンに送られます。

scope.launch {
  val onClick = view.onClick

  onClick.collect {
    println("Collect")
  }
}

ただしこの方法だと Flow がコールドストリームであることに影響して、 collect {} を実行するまでリスナーはセットされませんのでご注意ください。

最後に

この記事では既存のパラダイムとKotlin Coroutinesの共存方法について紹介しました。

Kotlin Coroutinesを使いたいが、既存のパラダイムとの組み合わせ方が分からない!という方の助けになれば幸いです。

次回はAndroidアプリでよく使われる既存のライブラリーとKotlin Coroutinesを組み合わせる方法について紹介したいと思います。

なんとなく理解するAndroidでKotlin Coroutinesを使う方法

この記事は社内のエンジニアが集まるScrapboxへ書いたページの転記です。

なんとなくKotlin Coroutinesについて理解が深まると幸いです。

参考URL

Coroutineを使うにあたって重要な要素

Suspending Function

CoroutineDispatcher

  • Coroutineの実行するスレッドを決める仕組み
  • RxでいうSchedulerみたいなもの
  • デフォルトでDsipatchersに3種類のDispatcherが用意されている
    • Main
      • メインのスレッドで実行
      • kotlinx-coroutines-androidを導入するとメインスレッドのHandlerにpostしてくれる
      • Dispatchers.Main.immediateを指定するとメインスレッドからの呼び出しなら即時実行してくれる
        • しない場合は次のイベントループで実行される
      • kotlinx-coroutines-testで別のDispatcherにdelegateできる
    • Default
      • デフォルトのDispatcher、親のCoroutineContextに特に何も設定していないとCoroutineの作成時にこれが使われる
      • JVM/Android上での実装はIOと共有で2以上、CPUのコア数か64のどちらか小さい値以下の共有スレッドプールで実行される
        • つまりスレッドが変わる可能性もあるので、スレッドセーフなデータの扱いを心がけた方が安全
    • IO
      • IO用のDispatcher
      • Defaultとスレッドプールが共有されるが、こちらはSystemのpropertyで数を制限できる
      • Defaultとの違いはAndroid上では名前くらいしかないのでDefault使えばいい気がしてる

CoroutineContext

  • Coroutineの情報を詰め込んでいるオブジェクト
  • 要素がある
    • CoroutineInterceptor
      • ほぼCoroutineDispatcher
    • ExceptionHandler
      • catchされなかった例外のハンドリングの仕組み
      • デフォルトだと何も処理されない
        • Androidの場合はAndroidExceptionPreHandlerが使われてスレッドに設定されているUncaughtExceptionHandlerに処理が以上される
          • →手でハンドリングしないとアプリがエラーで落ちる
      • https://kotlinlang.org/docs/reference/coroutines/exception-handling.html
    • Job
      • 現在のCoroutineのJob
      • 子のCoroutineを作る時はこのJobの子として作成される
      • cancelを呼ぶとキャンセルされる
        • 子のCoroutineも全てキャンセルされる
    • CoroutineName
      • Coroutineに名前をつける仕組み
      • デバッグの時に便利だろうけどあんまり使わない
    • その他色々あるけど実際に使うのはこれくらい
      • Coroutine内部では色々使われている模様

CoroutineScope

  • Coroutineを実行するためのスコープ
  • CoroutineContextをもつ
  • Coroutineを作成するためのエンドポイントになる拡張関数(コルーチンビルダーと呼ばれている)が用意されている
    • async
    • launch
  • スコープ内で生成されたCoroutineはスコープのCoroutineContextを継承する
    • Dispatchers.MainがDispatcherとして設定されていれば作成されたCoroutineはメインスレッド上で実行される
  • CoroutineScopeはネストできる
  • catchしていない例外やキャンセルが起きた時に並行実行されている他の処理がキャンセルされる単位

コルーチンを書く

1.CoroutineScopeを作る、既にあるものを使う

CoroutineScopeを作る

CoroutineScopeを作るには以下のような関数で行える

既にあるものを使う

既にあるものを使うと便利、よく使うCoroutineScopeは以下の通り

  • ViewModel.viewModelScope
    • ViewModelに用意されているCoroutineScope
    • SupervisorJob + Dispatchers.Main
      • 最近は Dispatchers.Main.immediate になった模様
    • ViewModel.onCleared 実行時にキャンセルされる
    • ViewModel内でコルーチンを作りたい時は基本的にこれを使うことになりそう
  • Lifecycle.coroutineScoope / LifecycleOwner.lifecycleScope
    • androidx.lifecycleLifecycle と連動しているCoroutineScope
    • androidx.lifecycle.Lifecycle.Event#ON_DESTROY のイベントを受け取った時にキャンセルされる
      • Activity/Fragmentだと onDestroy
      • Fragment.viewLifecyclerOwner だと Fragment.onDestroyView
    • launchWhenCreated / launchWhenResumed / launchWhenResumed といった便利コルーチンビルダーが生えている
  • GlobalScope
    • https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
    • デフォルトで用意されているやつ
    • 特定のJobに紐付かない
      • キャンセルはコルーチンそのものをキャンセルする必要がある
      • 別のScope内でGlobalScopeを使ってコルーチンを立ち上げてScopeをキャンセルしても立ち上げたコルーチンはキャンセルされない
    • あんまり使うべきじゃない
      • 公式ドキュメントにも使うのを強くおすすめしません、くいらいの温度感で書かれている
      • 例外として特定のSuspending Function内でDispatchers.Unconfined を使って produceなどのoperatorを使う時、に使うがこれもレアケースだと思う

2.Suspending Functionを書く

  • 特に難しいことはなくて suspend キーワードを使った関数を作ればよい
  • 基本的にSuspending Function内は同期的に実行される
    • 中で coroutineScopeasync を使って Deferred を立ち上げたりした場合は別
  • 中の処理をまるまる別のDispatcherで処理したい場合は withContext(defaultDispatcher) { /# do something #/ } みたいな感じで囲ってやるとよい

3. コルーチンビルダーでコルーチンを作る

  • 戻り値を受け取る必要がなければ launch
  • 値を受け取りたければ async
    • ただしawaitはSuspending FunctionなのでSuspending Function内で実行する必要がある
    • launch -> coroutineScope -> async みたいな囲うのが一般的
      • 例外を内部でcatchして値を返すのなら coroutineScope を使わないのもあり
  • 特定のContextで実行したければ withContext
    • 引数にCoroutineContextを受け取る
    • 一般的にCoroutineDispatcherをDispatchers.Defaultに切り替えて実行したい時に使われることが多い
    • NonCancelable を渡すとキャンセルされない
      • あまり使い道はなさそう

コルーチンを書く時に気を付けたいこと

例外処理

  • 例外処理は普通にtry/catchできる
  • ちゃんとcatchしないと親のコルーチンが死ぬ
    • SupervisorJobを使っていると死なない
    • CoroutineScope単位で死ぬので coroutineScope で囲う死なないという話はあるが、それでもcatchしないと普通に例外が吐かれてアプリが死ぬ

キャンセル可能な作りにする

  • コルーチンはキャンセルしてもコード側がキャンセル可能な作りになっていない限り処理が続行される
  • キャンセル可能に作っておくとアクセスされたら困るタイミングでViewにアクセスする、といった状況が避けやすい
  • よく使うキャンセル可能なコルーチンを作る関数
    • delay
      • 処理を中断して特定の時間後に再開することで後続の処理を遅らせる、コルーチンがキャンセルされたら再開されずにキャンセルされる
    • yield
      • その場でコルーチンを中断する、コルーチンがキャンセルされたら再開されずにキャンセルされる
      • Dispatcherが解放されるので、詰まってる別のコルーチンの処理を終わらせてから続きをする、といったことにも使える
  • kotlinx.coroutinesにあるサスペンド関数は全てキャンセル可能になっている

Kotlin Coroutinesをテストする

これは はてなエンジニア Advent Calendar 2019 13日目の記事です。

Kotlin Coroutinesのコルーチンをテストする時に必要なテクニックを紹介します。

コルーチンを同期的に実行したい

コルーチンを使って非同期処理をしているが、テストをする時には同期的に実行してほしいことはよくありますよね。

TestCoroutineScopeTestCoroutineDispatcher を使えば簡単にできます。

また、 runBlockingTest を使えばこれらのクラスが自動的に使われます。

kotlin.github.io

kotlin.github.io

CoroutineDispatcherを置き換える

普段使っているCoroutineDispatcherをテスト時に TestCoroutineDispatcher に置き換えるには下記のような方法があります

Dispatchers.Main

kotlinx-coroutines-test に含まれている Dispatchers.setMain / Dispatchers.resetMain を使いましょう

@Before
fun setUp() {
  Dispatchers.setMain(TestCoroutineDispatcher())
}

@After
fun tearDown() {
  DIspatchers.resetMain()
}

kotlin.github.io

kotlin.github.io

Dispatchers.Default / Dispatchers.IO 他

直接置き換える手段はないので、DIしましょう。

Flowに流れる値をテストする

Flow.collect をコルーチン内で実行すると値を全部受け取るまで処理がそこで止まります。

例えばRoomが返すような終わりのないFlowはキャンセルしない限り collect から先に進めないので、 launch で新しいコルーチンを立ち上げるとよいです。

その時 TestCoroutineScoperunBlockingTest を一緒に使ってやると Flow.collect が同期的に実行されます

@Test
fun test() = runBlockingTest {
  val flow = // create flow
  var actual = null
  val flowCollectJob = launch {
    flow.collect {
      actual = it
    }
  }

  // ここでFlowに値が流れそうなことを何かする

  // yield()でこのコルーチンがDispatcherを一旦手放すことでflow.collectに渡したblockが実行され値が反映される
  assertThat(actual).isEqualTo(expected)

  // 最後にキャンセルしてやらないと完了していないコルーチンがあるので例外が起きる
  flowCollectJob.cancel()
}
 

宣伝

2020年2月に五反田で開催されるDroidKaigi 2020でKotlin Coroutinesについて発表します。Kotlin Coroutinesの使い方に困ってる方は是非見に来てください!

これから始めるKotlin Coroutinesの導入

Kotlin Coroutinesが正式リリースされて1年が過ぎ、もはや私たちのAndroidアプリ開発には欠かせないものとなりました。
コルーチンはRxJavaのようなストリームやコールバック方式と比べて非同期的な処理を直感的に記述でき、これまでRxJavaを使っていたようなユースケースをある程度置き換えられるようになっています。
​
​
私はこの1年と少しでKotlin Coroutinesを複数のアプリに導入し既存のパラダイムの置き換えを進めてきました。
Kotlin Coroutinesの導入を行ったことでAndroidアプリ開発が楽になりましたが、大変になったこともありました。
恐らくKotlin Coroutinesが難しく感じて導入をためらっている方もいるでしょう。
​
このセッションでは、既存のAndroidアプリにKotlin Coroutinesを導入したことによってAndroidアプリ開発がどう変わったか、
Kotlin Coroutinesの導入を行ったことで困ったことをどう解決したかなどを紹介します。
皆様のKotlin Coroutinesの導入の参考になれば幸いです。
​
- RxJavaを使って書かれたコードを置き換える
- RxJavaを使った世界観にKotlin Coroutinesを混ぜる
- Retrofitを用いたHTTP通信をKotlin Coroutinesに対応
- RoomをKotlin Coroutinesで利用する
- Kotlin CoroutinesとView層との組み合わせ
- Kotlin CoroutinesとLiveDataの使い分け
- Kotlin CoroutinesをViewModelで活用する
- Kotlin Coroutinesとテスト
- 例外処理
- キャンセル可能に作る

droidkaigi.jp

明日の担当は id:polamjag です。