visible true

技術的なメモを書く

Ktor用のSpannerのスキーマバージョン管理ライブラリ「spanner-kase」を作った話

Ubie Advent Calendar 2019の2日目です。

最近チームで新しいサービスが必要になったのでKtorでやろうか!ってことでKtorでサービスを書き始めています。 データベースはGoogle Cloud Spannerを使おうということになりました。

Java/KotlinのWebアプリケーションにおいて、データベースのマイグレーションライブラリというとFlywayが有名かと思いますが、 残念ながらFlywayはGoogle Cloud Spannerをサポートしていません。

SpannerをサポートするPull Requestは存在するのですが、2018/01/01に作られたもので、Pull Requestを出した方はその後、google-cloud-spanner-jdbcを公式のgoogle-cloud-javaライブラリに追加*1したりしていますが、Flyway側の動きはなさそうです。

Pull Requestをつついたり引き継いだりしようか考えましたが、リリースタイミングをコントロールできない点と、機能的にKtorで使える範囲であれば小さそうということで、自分で作ってしまおうと考えました。

spanner-kase

名前はCloud SpannerのSchemaを入れておく場所ということでspanner-kase(スパナケース)にしました。

https://github.com/ubie-inc/spanner-kase

色々ケアレスミスをしてバージョンはいきなり1.1.3です。

spanner-kaseでできること

spanner-kaseでできることは次の通りです。

使い心地としては概ねFlywayかなと思います。Ktorで使うことだけを想定しているので、Spring Boot等ではうまく動かないかもしれません。

spanner-kaseの使い方

spanner-kaseを使うには次の手順が必要です

  • spanner-kaseをプロジェクトに追加する
  • マイグレーションファイルをresourcesに配置する
  • google-cloud-spannerのクライアントを初期化する
  • SpannerKaseDatabaseClient、MigrationDataScannerを作る
  • SpannerKaseを作ってマイグレーションを実行する

spanner-kaseをプロジェクトに追加する

spanner-kaseは内部でgoogle-cloud-spannerを使っていますが、推移的な依存関係を避けるためにimplementationで宣言しているので、利用時には別途google-cloud-spannerを追加する必要があります。

// build.gradle.kts
implementation("app.ubie.spanner-kase:1.1.3")
implementation("com.google.cloud:google-cloud-spanner:$GOOGLE_CLOUD_SPANNER_VERSION")

マイグレーションファイルをresourcesに配置する

Ktorプロジェクトを作ると最初からresourcesディレクトリがあると思うので、そこにマイグレーションファイルを置いていきます。パスは特に指定はないですが、ここではdb/migrationに配置しています。

.
├── build.gradle.kts
├── src
│   └── ...
├── resources
│   └── db
│       └── migration
│           ├── V1__User.sql
│           ├── V2__Todo.sql
│           └── V3__Permission.sql
...

マイグレーションファイルには命名規則があります。

V[VERSION]__[NAME].sql

VERSIONの範囲はLong*2です。

VERSIONが若い順に順次実行します。すでに実行済みのVERSIONは実行しません。VERSIONは年月日時分秒で書くのがおすすめです。

V20191201142511__User.sql

google-cloud-spannerのクライアントを初期化する

spanner-kaseは内部でgoogle-cloud-spannerを使っているので、まずはSpannerクライアントを作ります。

val options = SpannerOptions.newBuilder().build()
val projectId = options.projectId
val spanner = options.service

// spanner-kaseで使う
val instanceId = InstanceId.of(projectId, YOUR_INSTANCE_ID)
val databaseId = DatabaseId.of(projectId, instanceId.instance, YOUR_DATABSE_ID)
val databaseAdminClient = spanner.databaseAdminClient
val databaseClient = spanner.getDatabaseClient(databaseId)

databaseAdminClientはSpannerのDDL(Data Definition Language)を更新する際に、databaseClientはデータのCRUDを行う際に利用します。

SpannerKaseDatabaseClient、MigrationDataScannerを作る

次にSpannerKaseDatabaseClientMigrationDataScannerを作ります。

SpannerKaseDatabaseClientはspanner-kaseがバージョン管理のために使うテーブルの操作をする他に、マイグレーションファイルのSQLの実行などを行います。

val spannerKaseDatabaseClient = SpannerKaseDatabaseClient(
    instanceId.instance,
    databaseId.database,
    databaseAdminClient,
    databaseClient
)

MigrationDataScannerマイグレーションファイルの収集を受け持ちます。MigrationDataScanner自体はinterfaceなので、任意の実装を利用できます。予めClassLoaderMigrationDataScannerを用意しています。

KtorではApplicationを初期化する際に、environmentのClassLoaderを使うことで、resources内のマイグレーションファイルを利用できます。

@kotlin.jvm.JvmOverloads
fun Application.module() {
    // 省略
    val migrationDataScanner = ClassLoaderMigrationDataScanner(
        environment.classLoader, // io.ktor.application.Application.environment
        "db/migration" // relative path from resources dir
    )
}

SpannerKaseを作ってマイグレーションを実行する

あとはSpannerKaseを初期化して、migratie() を実行するだけです。

val configure = SpannerKase.Configure(
        spannerKaseDatabaseClient,
        migrationDataScanner
    ) 
SpannerKase(configure).migrate()

おわりに

早急に必要になる!と思ってザーッと作ったけど、優先度いくつか入れ替えてまだspanner-kaseを使うサービスはプロダクションでは出ていないので、まだもうちょいアップデートあるかもしれません。Ktor + Spannerは割とレアな気がしますが、もし機会があれば触ってみてください。

*1:FlywayでのサポートPR時に作ったものを公式に提案して取り入れられたっぽい

*2:1 - 9,223,372,036,854,775,807

Android Studio 4.0とJetpack Compose関連の見るとよいところなどのメモ

Youtube

Android Dev Summit '19のセッション! www.youtube.com

Android Studio 4.0 とJetpack Composeのセットアップ

Jetnewsというサンプルアプリを試したり、新規プロジェクトでJetpack Composeで始めたり、 既存のプロジェクトにJetpack Composeど導入する方法について書いてある。

developer.android.com

Jetpack Compose チュートリアル

Jetpack Composeの基本の解説。Composable functionsやレイアウト、スタイル、Themeなどの説明。 developer.android.com

Codelab

↑のドキュメントがCodelabになった感じ。わかりやすい。 codelabs.developers.google.com

Sample project

JetNewsというアプリのサンプル。 ナビゲーションドロワー、画面遷移などJetpack Composeで全て行っている。 データは埋め込みなので通信処理等はない。かなり参考になる。

github.com

f:id:sys1yagi:20191026143540p:plain:w300

Sample project2

自分で作ったもの。Navigation、ViewModelを使う。 Github APIをRetrofitで実際に検索する。 既存プロジェクトとの共存をイメージしているけど、JetNewsがSPAぽい世界観なので迷っていきている。

github.com

f:id:sys1yagi:20191026143505g:plain:w300

おわりに

まだまだ進化中という感じだけどいい感じなのではという気持ち。

Kotlin Fest 2019で「Kotlinコルーチンを理解しよう 2019」を話してきました

Kotlin Fest 2019楽しかったです。 今回は「Kotlinコルーチンを理解しよう 2019」を話してきました。 資料作りの様子、各セクションを作る時になに考えてたか、反省点などまとめます。

f:id:sys1yagi:20190826160412p:plain 前回と今回でロゴの形がちょっと変わってる

資料

コルーチンとはなにか、から実際使ってどうテストするかまでを45分でまとめるのはなかなか難しかったですが、一応網羅的にやれたか!?とは思っています。作る時はヒイヒイ言っててなにも考えてなかったけど改めてみると、これから触っていく場合に最初に読むものとして結構いいかもしれんと思うなどしました。 speakerdeck.com

コルーチンとはなにか、なにがうれしいのか

概念については自分のなかではある程度理解できてたものの、対象コルーチンや非対称コルーチンについて曖昧だったので改めて調べた。 新雑誌「n月刊ラムダノート」の『「コルーチン」とは何だったのか?』の草稿を公開します - まめめもはとても参考になりました、また、Google公式で紹介していた次のエントリも考え方の整頓にとても助かりました。

特にパート3のSingleRunnerの実装はこの辺に転がっているがめちゃんこ役に立つので参考にすると良いと思います。 このほか2004年の論文 Revisiting Coroutinesもまた改めて読んだ。前よりは理解できた。

コルーチンの何が嬉しいかについては昨年末のGoogle Play App Dojoでの登壇(http://sys1yagi.hatenablog.com/entry/2018/12/19/104023)でまとめていたのでそこまで迷いはなかった。

スライドの最初の、コンウェイによる概念の発明、simulaによる実装、様々な言語の様々な実装の話は、 発表では30秒くらいでサラッと流したけど、コンウェイの論文(http://www.melconway.com/Home/pdf/compiler.pdf)を見に行ったり、simulaのコルーチンプログラムを読んだり(http://staff.um.edu.mt/jskl1/talk.html の Chess game control using Two Masters approach.のセクション)、Module-2やSchemeLispに入門してみたり、Pythonのasync/awaitやRubyのFiberを試したりなんかしてめっちゃ時間をかけてしまった。が良かった。対象コルーチン、非対称コルーチン、各言語の実装、継続やスタックがどうとかいう議論など、歴史を感じる事ができた。これのおかげでサラッと話せたんだなと思う。

Kotlinにおけるコルーチンの仕組み

最初は結構細かく書いていた。suspendキーワードの話のあと、CPS(Continuation Passing Style)とステートマシン生成について話し(https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#implementation-details )、さらにsuspend関数がContinuationImplに、suspendラムダがSuspendLambdaに変換される様子をバイトコードを見て話したり、更にsuspendラムダの拡張関数を使ってコルーチンを実行する様子なんかも眺めたかったけど、実際使っていくにあたってはそこまで意識しないことだよなーっていうのと圧倒的に時間オーバーする事がわかって削った。

// suspendラムダの拡張関数。関数型に拡張関数生やせるの面白い
@InternalCoroutinesApi
public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) = runSafely(completion) {
    createCoroutineUnintercepted(completion).intercepted().resumeCancellable(Unit)
}

結果的に利用者にとって一貫性のある内容になったんじゃないかって気がしたので良かった(使う時にCPSが〜とかはあんま考えないので)。

Kotlinコルーチンのきほん

Kotlinコルーチンは関係要素が多すぎて何から説明するかとても大変だった。このセクションは基本的にKotlin公式のドキュメントに沿って話すことにした(https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)。このセクションが一番長くて時間が足らないってことで結構削ってしまった。コルーチンビルダー関数について深ぼったり(特にsuspendラムダとか起動周りとか)、コルーチンスコープのインスタンス作って動かしてみる例があればもっと理解を深められただろうと思う。コルーチンディスパッチャーについても、こう使うと切り替えられますという感じで割り切ったのでちょっと心残りである。コルーチンディスパッチャーは奥が深くてDefaultとIOの違いとか、MainとServiceLoaderに関する話なんかも面白い。実行コンテキストの合成とかもややこしいけど面白い点だ。あともうちょい、このセクションに出る要素でほぼ実用はできるって点はもっと丁寧に説明できたらな〜と思ったりした。

コルーチンスコープと構造化された並行性

自分のなかで構造化された並行性についてはイメージがあったのでわりとスムーズにいった。最初はRoman氏のStructured concurrencyとそこから 参照されているNotes on structured concurrency, or: Go statement considered harmfulに触れたり、構造化プログラミングの非同期版やで〜みたいな話をしようと思ったけど、生まれた背景をそこまで細かく知る必要は無い気がしたので省略した。coroutineScope関数のあたりで、実はwithContext関数もスコープビルダーだよとか、ファストパスでwithContext関数呼び出しがネストしても実行コンテキストが変わらなければそのまま実行するよとか話したかったけど省略した。またSuperVisorについても省略した。viewModelScopeはSuperVisorJobを使ってたりするけど、まぁ通常はほぼ使わないと思うので省略。 分岐と合流の図は自分で書いてみてマジわかりやすいと思ってたので、わかりやすいという感想を見かけてやったねってなった。

コルーチンと設計

ここもまたサラリと書いた形になった。掘り下げようと思えば色々できたのだけど、考え方だけ伝えればいいかってことでだいぶ削った。そういえばスコープのモチベーションとリークの関係についてもっと説明するべきだった。その上でプラットフォームでスコープ提供してて最高って話ができると良かったかなと思う。suspend関数をメインセーフティで実装したり、コルーチンをメインセーフティで起動したりはまぁこんなものかな。メインセーフティでない例をしっかり示せてなかった気がするのでそこはちょっと反省点。

コルーチンのテスト

コルーチンのきほんのセクションと同等くらいの大きさでめっちゃ大変だった。 テスト方法についてもまだまだ固まってない部分もありどうまとめたもんか迷ったりした。 Githubにもぜんぜん実践的なテストコードが転がってなくて結構途方に暮れたが自分のアプリで徹底的にテスト書いて事なきを得た。間接的なコルーチンの呼び出しについて、ディスパッチャーを置き換える具体的な方法の紹介はあったほうがよかったなと思った。また、runBlockingTestやsetMainやRule周りも結構ザーッと書いてるんでもうちょい補助的な情報を示せたはずと思ったりした。準備の中でテストに関しての理解が間違ってた!みたいなことが数回発生して全練り直しを2回くらいして大変だった。TestCoroutineDispatcherとDelayHandler周りの実装は面白いので覗きたかったけどさすがに時間が足らないので諦めた。テスト周りは多分まだまだ書く度に悪戦苦闘することになりそう。

おわりに

今回準備にあたってコルーチンに関する論文をいくつか読んだ(ざくっとだけど)らとても面白かったのと、最近のCoroutines FlowでHoareのCSPモデルが関係する話( Kotlin Flows and Coroutines - Roman Elizarov - Medium)を読んだりして計算機科学をしっかり学んでみたいな〜大学院とかがいいのかな〜とかなんとか思ったりした。 しかしまずは車の免許を取ろうと思っている。そしてキャンプに行きたい。

参加者、スタッフの皆様ありがとうございました!引き続きKotlinを愛でていきましょう!

FlowのchannelFlowを使ってRxBindingを置き換える

FlowのflowViaChannelを使ってRxBindingを置き換える - visible trueKotlin Coroutines 1.2.xでFlowというコールドストリームをサポートするクラスや関数群が登場しました。ってことでflowViaChannel関数について書いたら、Kotlin Coroutines 1.3.0-M1でflowViaChannel関数がdeprecatedになりました。previewなのでそういうこともあるでしょう。

ということで、FlowのflowViaChannelを使ってRxBindingを置き換える 改め、FlowのchannelFlowを使ってRxBindingを置き換える 話をします。

1.3.0-M1でなにが変わったか

Release 1.3.0-M1 · Kotlin/kotlinx.coroutines · GitHubによるとFlowにおけるコンテキスト保存の不変性について見直したとあります。コンテキスト保存の不変性というのは、Flowのブロックをどのコンテキストで実行するかを利用者がコントロールできることを保証するものです。

Flowのコンテキスト保存の不変性

たとえば次のコードはすべて同じスレッドで実行されます。

suspend fun a() {
  println("start: ${Thread.currentThread().id}")
  flow {
    println("emit: ${Thread.currentThread().id}")
    repeat(3) { emit(it) }
  }
    .collect {
      println("collect: ${Thread.currentThread().id}")
    }
}

出力は次のようになります。

start: 11
emit: 11
collect: 11
collect: 11
collect: 11

flowOn関数を使うと、upstreamの実行スレッドを変更できます。

suspend fun b() {
  println("start: ${Thread.currentThread().id}")
  flow {
    println("emit: ${Thread.currentThread().id}")
    repeat(3) { emit(it) }
  }
    .flowOn(Dispatchers.IO) // ここより上流はDispatchers.IOで動作する
    .collect {
      println("collect: ${Thread.currentThread().id}")
    }
}

出力は次のようになります。

start: 11
emit: 14
collect: 11
collect: 11
collect: 11

さて、次の場合はどうでしょうか*1

suspend fun c() {
  println("start: ${Thread.currentThread().id}")
  flow {
    kotlinx.coroutines.withContext(Dispatchers.IO) {
      println("emit: ${Thread.currentThread().id}")
      repeat(3) { emit(it) }
    }
  }
    .collect {
      println("collect: ${Thread.currentThread().id}")
    }
}

flow関数のブロック内で新たなコンテキストを使おうとしています。これだと利用者が例えばDispatchers.Mainで動作させたいと思ってもコントロールできませんよね。このコードを実行すると例外が投げられます。

start: 11
emit: 14

Flow invariant is violated: flow was collected in BlockingEventLoop@71cde863, but emission happened in LimitingDispatcher@73ad1220[dispatcher = DefaultDispatcher]. Please refer to 'flow' documentation or use 'flowOn' instead

このようにflowのブロックの実行コンテキストは、呼び出し元によってコントロールできることを保証するというのが、コンテキスト保存の不変性です。

Flowのコンテキスト保存の不変性の見直し

コンテキスト保存の不変性の見直しについては次のissueが詳しいです。 Flow context preserving property, thread safety and context changing · Issue #1210 · Kotlin/kotlinx.coroutines · GitHub

既存のコンテキスト保存の不変性では対応しきれない(あるいはコストがかかる)ユースケースがでてきたようです。 そのために新たに、異なるコルーチンからFlowCollector*2のemit関数の呼び出しを禁止するようになりました。 またchannelFlow関数を導入し、コンテキスト保存の不変性を保証しつつ、簡単に異なる実行コンテキストを利用できるようになりました。

その流れでflowViaChannel関数が非推奨になりました。非推奨になった明確な理由の説明がパッと見当たらなかったのですが、flowViaChannel関数のブロックがCoroutineScope.(channel: SendChannel<T>) -> Unitであるのに対して、channelFlow関数のブロックがsuspend ProducerScope<T>.() -> Unitであることから、ブロックが完了すると自動的にChannelをクローズするようにすることで、予期しない不具合を起こりづらくしてるのかな〜と思いました*3

channelFlowを使ってRxBindingを置き換える

ではflowViaChannel関数を使うのをやめてchannelFlow関数を使ってRxBindingを置き換えましょう

まずはbeforeのflowViaChannel関数を使うパターンです。

fun TextView.textChangeAsFlow() =
  flowViaChannel<String?> { channel ->
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
      channel.offer(it?.toString())
    }
    channel.invokeOnClose {
      removeTextChangedListener(textWatcher)
    }
  }

実はこれ1.3.0-M1では正しく動作しません。flowViaChannel関数の実装がchannelFlow関数を使う形に変更されたために、ブロックを抜けるとchannelがクローズされてしまうのです!怖いですね。

channelFlow関数を使った実装は次です。

fun TextView.textChangeAsFlow() =
  channelFlow<String?> {
    channel.offer(text.toString())
    val textWatcher = addTextChangedListener {
      channel.offer(it?.toString())
    }
    awaitClose {
      removeTextChangedListener(textWatcher)
    }
  }

channel.invokeOnCloseの代わりにawaitClose関数を使っています。これはProducerScopeに追加された拡張関数で、呼び出し元がcancelをするまで待ってくれます。便利ですね。

おわりに

Kotlin Coroutines 1.3.0-M1でFlowはPreviewからExperimentalになりました。まだ変更はいろいろありそうですが進捗してる感じがあってstableが楽しみですね。

*1:withContext関数をFQCNで書いているのは、FlowCollector.withContextの利用が禁止されていてコンパイルエラーになるためです。

*2:flow関数のブロックがsuspend FlowCollector.() -> Unitです

*3:普通に https://github.com/Kotlin/kotlinx.coroutines/pull/1214に書いてました..

Flutter for webでWebフォントを使う

使える気がするんだけど何か方法あるのかなーと思って試してみたら普通に使えばよいことがわかった。

index.htmlでWebフォントを読み込む

好きなフォントを書く

<html lang="en">
  <head>
    <!-- ...略 -->
    <link href="https://fonts.googleapis.com/css?family=Gentium+Book+Basic&display=swap" rel="stylesheet">
  </head>
</html>

fontFamilyで指定する

あとは普通にfontFamilyで名前を指定するだけ。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(
        widget.title,
        style: TextStyle(
            fontSize: 32,
            fontFamily: "Gentium Book Basic",
            fontWeight: FontWeight.bold),
      ),
    ),

こんな感じで反映される。

before after

まとめ

Webフォントをそのまま使えるのは良いですね~。

KtorでFirestore Local Emulatorにつなぐ

Firestore良さそうですよね。ローカルで動作を試せるLocal Emulatorがあるので気軽に試せてさらによさそうです。JVM環境で接続する例があんまり見当たらなかったのでメモします。

firestore local emulatorの準備をする

とりあえずインストールして、

firebase setup:emulators:firestore

次のコマンドで起動できればOKです。

firebase serve --only firestore

firebaseのバージョンとかでいろいろエラー出たりするのでググったりしてなんとか入れてください。

firestore local emulatorをポートを指定して起動する

firebase serve --only firestore ではポートが8080でしか起動できません、そこで次のコマンドでLocal Emulatorを起動します。

gcloud beta emulators firestore start --host-port=localhost:8812

次のような出力が得られれば成功です。

[firestore] API endpoint: http://localhost:8812
[firestore] If you are using a library that supports the FIRESTORE_EMULATOR_HOST environment variable, run:
[firestore]
[firestore]    export FIRESTORE_EMULATOR_HOST=localhost:8812
[firestore]
[firestore] Dev App Server is now running.
[firestore]

application.confにlocal emulatorの設定を書く

Ktorからlocalhost:8812のLocal Emulatorに接続するために、application.confに設定を書きます。 特にfirebaseのための書き方などはないので、適当に書きます。

ktor {
  firebase {
    project_id = $PROJECT_ID
    firestore {
      emulator {
        host = localhost
        port = 8812
      }
    }
  }
}

Ktorから設定を読み込む

application.confの内容はApplication.environmentから読み込めます。

fun Application.module() {
  val firestoreEmulatorHost =
    environment.config.propertyOrNull("ktor.firebase.firestore.emulator.host")?.getString()
  val firestoreEmulatorPort =
    environment.config.propertyOrNull("ktor.firebase.firestore.emulator.port")?.getString()?.toInt()
  val projectId =
    environment.config.propertyOrNull("ktor.firebase.project_id")?.getString() ?: throw IllegalStateException(
      "firebase project id not found"
    )
}

Firestoreを初期化する

FirestoreでLocal Emulatorに接続する場合はFirestoreOptionsを使って直接Firestoreのインスタンスを作ります。

val firestore = FirestoreOptions
  .newBuilder()
  .setHost("$firestoreEmulatorHost:$firestoreEmulatorPort")
  .build()
  .service

本物につなぐ場合も考慮して次のようなobjectを作りました。

import com.google.auth.oauth2.GoogleCredentials
import com.google.cloud.firestore.Firestore
import com.google.cloud.firestore.FirestoreOptions
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.cloud.FirestoreClient

object FirebaseInitializer {
  data class FirestoreEmulator(val firestoreEmulatorHost: String, val firestoreEmulatorPort: Int) {
    fun toUrl() = "$firestoreEmulatorHost:$firestoreEmulatorPort"
  }

  fun firestore(
    projectId: String,
    emulator: FirestoreEmulator?
  ): Firestore {
    return if (emulator != null) {
      FirestoreOptions
        .newBuilder()
        .setHost(emulator.toUrl())
        .build()
        .service
    } else {
      val credentials = GoogleCredentials.getApplicationDefault()
      val options = FirebaseOptions.Builder()
        .setCredentials(credentials)
        .setProjectId(projectId)
        .build()
      FirebaseApp.initializeApp(options)

      FirestoreClient.getFirestore()
    }
  }
}

こんな感じでFirestoreを取り出せます。

val firestore = FirebaseInitializer.firestore(
  projectId,
  if (firestoreEmulatorHost != null && firestoreEmulatorPort != null) {
    FirebaseInitializer.FirestoreEmulator(firestoreEmulatorHost, firestoreEmulatorPort)
  } else {
    null
  }
)

やったね。

おまけ: KoinでInjection

せっかくなのでKoinでFirestoreインスタンスをinjectできるようにします。 とりあえずKoinをdependenciesに追加し、

dependencies {
  implementation "org.koin:koin-core:2.0.1"
  implementation "org.koin:koin-ktor:2.0.1"
}

モジュールを実装します。Applicationの拡張関数にしとくと楽です。

import io.ktor.application.Application
import org.koin.dsl.module

fun Application.applicationModule() = module {
  single {
    val firestoreEmulatorHost =
      environment.config.propertyOrNull("ktor.firebase.firestore.emulator.host")?.getString()
    val firestoreEmulatorPort =
      environment.config.propertyOrNull("ktor.firebase.firestore.emulator.port")?.getString()?.toInt()
    val projectId =
      environment.config.propertyOrNull("ktor.firebase.project_id")?.getString() ?: throw IllegalStateException(
        "firebase project id not found"
      )
    FirebaseInitializer.firestore(
      projectId,
      if (firestoreEmulatorHost != null && firestoreEmulatorPort != null) {
        FirebaseInitializer.FirestoreEmulator(firestoreEmulatorHost, firestoreEmulatorPort)
      } else {
        null
      }
    )
  }
}

あとはアプリケーションの初期化時にKoinをinstallしてモジュールを設定すれば、適当に取り出せるようになります。

fun Application.module() {
  install(Koin) {
    modules(
      applicationModule()
    )
  }
  // ...
  routing {
    get("/firestore") {
      val firestore: Firestore by this@routing.inject()
        // do something
    }
  //...
}

まとめ

Firestore Local Emulator良さそう。admin的な画面って内蔵されてないんだろうか?ある気がする。わからない。

Kotlin Fest 2019のセッションに応募しました。

今年もKotlin Festが開かれますね。もうセッションの応募はされたでしょうか? トラック数が増えたので今年はさらにいろんなジャンルが聞けるのではないかとワクワクしています。Kotlin/JSとかKotlin/Native, MPPが特に気になります。

kotlin.connpass.com

とりあえず4つほど応募しました。応募内容は次のとおりです。 他の方々の応募状況も見てみたいっす

言語機能

Kotlin コルーチンを 理解しよう 2019

概要

Kotlin コルーチンは2017年3月にexperimentalな機能としてKotlin 1.1とともに登場しました。 その後2018年10月に正式版の1.0.0がリリースされるまでに、様々な変更がありました。 特に2018年9月に加えられた並行性の構造化に関する変更は、それ以前のコルーチンのコードが利用できなくなるなど、とても影響が大きいものでした。ある時点より以前の情報が誤りになってしまう状況は、学習者にとって混乱のもととなります。

本セッションではKotlinコルーチンの簡単な歴史をおさらいしつつ、コルーチンとはなにか、コルーチンの簡単な利用方法、コルーチンスコープと並行性の構造化がなぜ生まれたのか、アプリケーションでコルーチンを利用する時の考え方、コルーチンのテスト方法などについて解説します。本セッションがKotlin コルーチンの適切な知識へのガイドになれば幸いです。

Kotlin コルーチン 「Flow」を味わい尽くす

概要

Kotlin コルーチンといえば「async/await」がよく注目されますが、それ以外にもいくつかの機能があります。 Channelは、Kotlin1.1においてコルーチンをexperimentalでリリースした時から提供している機能のひとつです。 async/awaitは単一の値をやりとりしますが、Channelはストリームを取り扱います。

Channelは連続的な値を非同期に送受信する用途に便利ですが、ホットストリームである点が利用の幅を制限していました。 Koltin コルーチン 1.2.0から「Flow」というコールドストリームがpreview版として追加されました。 本セッションでは、ホットストリームとコールドストリームとはなにか、ホットストリームの課題、Flowの仕組み、Flowの現在の使い方、Flowの使い所などについて解説します。

Android Kotlin

Design of Android Application with Jetpack and Coroutines

概要

Google I/O 2019でKotlinがAndroidアプリケーション開発の第1言語に躍り出ました :tada: 。 Android Jetpackの各種ライブラリではKotlin向けの拡張(ktx)が充実し、また非同期処理を必要とする部分ではコルーチンのサポートが相次いで行われており、Android Jetpack + Kotlin + コルーチンでアプリケーションを作る下地が整いつつあります。

本セッションでは、Google I/O 2019で発表されたAndroid JetpackのLifecylce、LiveData、ViewModelのコルーチンに関する機能を説明し、これらを用いたときのアプリケーション設計をどうするべきかや、テストをどのように行うべきかなどについて解説します。

Server side Kotlin

Ktorで小さく始めるAPIサーバ

概要

2018年11月にJetbrain謹製のウェブアプリケーションフレームワークであるKtor 1.0がリリースされました。 Ktorはサーバのコアコンポーネントに対して、実行エンジン(TomcatやJettyなど)、認証、ロギング、CORS、Routingなど必要な機能をDSLで宣言的に追加していくスタイルを採用しています。 必要最小限の機能で始められるので、プロトタイピングや単一の責務を持つアプリケーションを素早く作るのに適しています。

本セッションではKtorのコンセプト、提供されている機能群、基本的な使い方を説明し、簡単なTODOアプリケーションのREST APIサーバを作ります。Ktorで小さく始めるAPIサーバを体験しましょう!

まとめ

どれか引っかかるといいなー。 どしどし応募しましょう!!