nashcft's blog

時々何か書く。

Android 15 で edge-to-edge が強制されるかもという話のメモ

何のこと?

www.androidauthority.com

この記事の途中に実装に関する言及があった:

[...], while I was digging through Android 14 QPR2 Beta 3, I discovered a new app compatibility change named EDGE_TO_EDGE_BY_DEFAULT with this description: “make app go edge-to-edge by default if the target SDK is VANILLA_ICE_CREAM or above.” Vanilla Ice Cream happens to be the internal dessert name for Android 15, which means this compatibility change will be applied to apps that target this year’s upcoming release.

じゃあどんな感じか見てみるか、となったので軽く目を通した。

実装とかのリンク

当該の compatibility change のフラグと追加された commit は以下:

あと現時点ではこれ関連だと以下の commit とか mDefaultEdgeToEdge が使われているあたりを見ておけば十分だろうか:

実装内のコメントを見るに SDK version 35 から入れるつもりでいると捉えて良さそうに思う。

おわりに

本当に Android 15 から有効になるなら、その内以下のページにリストアップされるだろう。とはいえこのレベルの挙動変更なら release note で言及されるとは思う。

developer.android.com

Android 13 で getSerializable/getParcelable の API 置き換えが発生して、でも getParcelable にはバグがあって、その後

Android 13 のリリースに関して以下のような Serializable / Parcelable の扱いがしんどいという話があった。この記事ではその後どうなったかについて簡単に記録しておこうと思う。

speakerdeck.com

先に結論を書いておくと、 AndroidX Core に compatible API が実装されたのでそれらを使えば OK という状況になった*1

スライドの要点

その後

Parcelable 向けの対応

https://issuetracker.google.com/issues/242048899 を読むと、実はスライド発表時点で Parcelable 関係の compatible API の対応については既に実装されていたことがわかる。ただしその時点では未リリースだったし、 issue の関連付けも行われていなかった*2。この変更は年が明けてすぐの 2023-01-11 に行われた AndroidX Core の 1.10.0-alpha01 でリリースされている。

Serializable 向けの対応

上記の Parcelable への対応に関する issue に以下の issue が Serializable 向けの feature request としてリンクされており、こちらでトラッキングされるものと思って見ていたが結局使われることはなかった。

その後以下の issue と CL が提出され、 Serializable についてはこちらで話が進められることになった。

この変更は 2024-01-10 に AndroidX Core の 1.13.0-alpha03 でリリースされている。なぜか release note に記載はない。

まとめ

  • AndroidX Core の BundleCompat, IntentCompat, ParcelCompat を使おう
    • Parcelable 取得の compatible API1.10.0 から
    • Serializable 取得の compatible API1.13.0 から

*1:Serializable に関しては AndroidX Core 1.13.0-alpha03 からなので 2024-03-31 時点で stable は未リリース

*2:NPE バグの報告 issue にはリンクされていた https://issuetracker.google.com/issues/240585930#comment6

Klock の今

Kotlin multiplatform 向け datetime ライブラリの Klock は昨年の8月にリリースされた 4.0.10 以降リリースされていないように見える。

参考: https://mvnrepository.com/artifact/com.soywiz.korlibs.klock/klock

実際は現在もメンテが継続されており、 korlibs-time という名前で公開されている。現時点で最新バージョンは 5.4.0

https://mvnrepository.com/artifact/com.soywiz.korge/korlibs-time

これは Klock が含まれる Kotlin 製ゲームエンジン KorGE の 5.0 開発段階で仕切り直し的な状況が発生しライブラリ群の構造が整理されたことによる。そのため 5.0.0 - 5.1.0 の間は korge-foundation という全部入りライブラリとして公開されていた。その後また機能毎に module をばらして公開するかということになり現在再び分離中という状況である。 korlibs-timekorlibs-template と共に 5.2.0 から独立した module としてリリースされた。

github.com

また、 Korlibs 系の module 群は project の構成をもっとシンプルにするために別の repository に移すかという計画も進められており、暫くしたらメンテは以下の repository で行われるようになると思われる。

github.com

github.com

余談

"仕切り直し的な状況が発生し" と書いたが、何があったかというと2023年の7月に KorGE の作者が引退宣言をし暫く有志によるメンテナンスモードになるということが起こっていた。

web.archive.org

https://github.com/korlibs/korge/blob/v4.0.10/README.md

In maintenance mode. Soywiz (the founder/primary lead/developer) has retired from maintaining this project (2023/07/27).
Currently this project is only being maintained by a handful of people who may/may not be as familiar with the architecture.
Do not expect any major updates in the future and expect less support if you do decide to use this project.
We are open for pull requests if there are any issues you'd like to fix yourself.

その後作者に心境の変化があったようで9月中頃から開発に復帰し KorGE 5 のロードマップを発表して KorGE 復活、という流れで現在に至る。

web.archive.org

前掲の記事2つは KorGE の公式ブログから削除されているので wayback machine から引っ張ってきている。

Dependabot: 設定の備忘録

最近 dependabot でライブラリ更新を自動化している repository の dependabot.yml を手入れしていたのでそのメモ。

Gradle: settings.gradle で依存取得先を設定している場合に更新検知できないライブラリがある

2024-02-19 追記

以下の修正によって dependencyResulutionManagement の設定は読まれるようになった:

github.com

追記おわり

Project の依存解決の設定を settings.gradledependencyResolutionManagementpluginManagement で記述している場合、一部のライブラリの更新検知ができないということが起こる。 Log を見てみると maven-metadata.xml の取得先として Maven Central (plugin の場合はこれに加えて Gradle plugin portal) しか参照しておらず、たとえば AndroidJetpack library など Google Maven Repository に配置されているライブラリはバージョン情報の取得に失敗していることがわかる。

# e.g. AndroidX Core
updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Checking if androidx.core:core-ktx 1.12.0 needs updating
  proxy | YYYY/MM/DD hh:mm:ss [xxxx] GET https://repo.maven.apache.org:443/maven2/androidx/core/core-ktx/maven-metadata.xml
  proxy | YYYY/MM/DD hh:mm:ss [xxxx] 404 https://repo.maven.apache.org:443/maven2/androidx/core/core-ktx/maven-metadata.xml
updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Latest version is 
updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Requirements to unlock update_not_possible
updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Requirements update strategy 
updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> No update possible for androidx.core:core-ktx 1.12.0

build.gradle で依存解決の設定を記述している場合は設定した repository が maven-metadata.xml の取得先に追加されていて期待通りにバージョン情報を取得できているので参照先の取得のために settings.gradle を読んでいないのだと思われる。 Dependabot のコードを何となく眺めた感じでもそうっぽい気がする。根拠にしてるのは以下のファイル:

github.com

これに関する issue はすでに報告されている:

github.com

以下の issue のコメントに workaround が示されており、 private package へアクセスするための設定に使われる registries にアクセスしたい maven repository について追記してそれを参照するようにすれば metadata の取得先に追加される。

github.com

docs.github.com

version: 2
# ↓を追加
registries:
  maven-google:
    type: maven-repository
    url: "https://dl.google.com/dl/android/maven2/" # https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/dsl/RepositoryHandler.html#google--
updates:
  - package-ecosystem: "gradle"
    directory: "/"
    # ↓を追加
    registries:
      - maven-google
    # ...

設定を追記した後の実行ログは以下のような感じ。 maven-metadata.xml の取得時のアクセス先に https://dl.google.com:443/dl/android/maven2/ が増えて、そちらで取得成功しておりバージョン情報の評価ができていることがわかる。

 updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Checking if androidx.core:core-ktx 1.12.0 needs updating
   proxy | YYYY/MM/DD hh:mm:ss [xxxx] GET https://repo.maven.apache.org:443/maven2/androidx/core/core-ktx/maven-metadata.xml
   proxy | YYYY/MM/DD hh:mm:ss [xxxx] 404 https://repo.maven.apache.org:443/maven2/androidx/core/core-ktx/maven-metadata.xml
   proxy | YYYY/MM/DD hh:mm:ss [xxxx] GET https://dl.google.com:443/dl/android/maven2/androidx/core/core-ktx/maven-metadata.xml
   proxy | YYYY/MM/DD hh:mm:ss [xxxx] 200 https://dl.google.com:443/dl/android/maven2/androidx/core/core-ktx/maven-metadata.xml
 updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> Latest version is 1.12.0
 updater | YYYY/MM/DD hh:mm:ss INFO <job_xxxxx> No update needed for androidx.core:core-ktx 1.12.0

GitHub Actions: repository local な composite action 内で使用している action の更新検知をしたい

Repository 内で composite action を定義している場合、その中で使われている action の更新を検知するには / で設定しているのとは別にそれぞれの composite action に対して設定を書く必要がある。

参考:

qiita.com

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/" # これは .github/workflows/ 以下の yaml が対象になる
    # ...
  - package-ecosystem: "github-actions"
    directory: "/.github/actions/my-action1"
    # ...
  - package-ecosystem: "github-actions"
    directory: "/.github/actions/my-action2"
    # ...

directory の指定にワイルドカードを使えるようにする feature request はあるにはあるけど何年も動いてないのであまり期待できなそう:

github.com

おわりに

セットアップまで含めて Renovate 使うのとどっちが楽なんだろ

READ_MEDIA_IMAGES と READ_MEDIA_VIDEO の使用に制限がつくらしい

API level 33 からアプリ外のメディアファイルへのアクセスに必要な permission として READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO が追加された*1が、これらの内 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO の使用に関するポリシーの追加が 2023-10-25 付で発表されていた。

support.google.com

更新されたポリシーの preview は以下のページから確認できる。

support.google.com

これらのページによると、画像や動画のデータは "personal and sensitive user data subject to Google Play's User Data policy" であるためあまり無闇に対象となる permission を使わせないようにしたいので、デバイス上の画像や動画への広範なアクセスがアプリの主たる機能に直接関係するような場合だけこれらの permission を要求していいことにするよ、そのために READ_MEDIA_IMAGESREAD_MEDIA_VIDEO を要求するアプリに関しては使用が妥当なものかを審査するよ、とのこと。また、ファイル選んでアップロードするみたいなユースケースは上記の審査には通らないので system で提供している photo picker を使って permission 無しでのアクセスをするようにしてほしいということだそう。細かい話は以下のヘルプページの FAQ で色々書かれているのでそちらを確認してほしい。

support.google.com

スケジュール的にはだいたい来年中に対応を済ませましょうという感じ。

メジャーな対応ケースとしては、アプリに画像や動画の投稿・アップロード機能があって、そのために自作の picker を使っているとかで API level 33 以上で当該 permission を要求している場合に、 picker を Android が提供している photo picker に置き換えて permission 要求の記述を削除するというものになるだろう。 Photo picker の使い方は activity result contract を作って投げるだけで簡単なので、公式ドキュメントに目を通せばその他の事項含めて割とすぐに置き換えることはできると思う。

developer.android.com

余談だが複数選択をした際に photo picker から返ってくるファイルの順番が選択順ではないので、それだと困るという人は以下の issue に vote しておくと早く対応してくれるかもしれない。

Kover 触ったメモ

DroidKaigi 2023 公式アプリなどで触ったので所感などを書き残しておく。この記事の内容は Kover 0.7.3 時点の機能に基づいている。

書いてたら思ったより長くなったので先に結論を書いておくと「簡単なので使う場合は各自でいい感じにやっといてください」になる。

Kover とは

JetBrains が開発している code coverage の toolset。要するに JaCoCo みたいなやつ。今回は Gradle plugin について扱うが他に CLI も存在する。

使用メモ

使い方に関しては解説や要約が必要なほど多機能なわけではないので、何ができるかとかのちゃんとした説明は docs を読んでほしい。

Tasks

  • koverHtmlReport: 計測結果を html で出力
    • 手元で見る時はこれ
  • koverXmlReport 計測結果を xml で出力
    • codecov とかに食わせる時はこれ。
  • koverBinaryReport: 計測結果を binary file で出力
    • そういう形式を要求する何かに食わせる時用 (使ったことあるので具体例を知らない)
  • koverLog: 計測結果を task のログとして出力
  • koverVerify: 設定した rule を満たすかの検査
    • 設定については後述
    • rule を満たさない場合は task が失敗する

Android module に対しては上記の task 名の末尾に build variant が付く。

設定まわり

koverReport の中で記述する。 以下は大雑把な設定項目の説明:

  • filter: 計測対象の追加・除外の設定
    • class の指定に関しては fully qualified な class 名が求められるので package 部分から記述する必要がある
    • wildcard の記述で *** が使えるがこれらの違いは無いとのこと (?)
  • verify: verification task に関する設定
    • 評価する単位 -> 全体、 class 毎、 package 毎
    • OK とする coverage の下限・上限
    • 計測値の基準 (lines / bytecode instructions / branches) と単位 (covered or missed に対する count / percentage)
  • defaults: default task に対する設定
    • filterverify はこの中でも設定可能
    • 他に出力形式毎の諸々の設定をすることが可能
  • androidReports
    • Android module のそれぞれの build variant に対する設定
    • 記述できる設定については defaults と同じ

Coverage report の集約

:foo:bar があったとして、 :foo の方の build.gradle で以下のように :bar への依存を追加すると、 :foo の方で kover task を実行した時に :bar のものとまとめられた coverage report が作成される:

// foo/build.gradle
dependencies {
  kover(projects.bar)
}

kover() で依存に追加される module は 同様に kover gradle plugin を適用する必要がある。また、 report の集約を行う際、 koverReport による設定は各 module のものではなく集約を行う (i.e. kover() による依存の設定を記述した) module のもののみが適用される。

所感

どこで report を集約するか

Kotlin/JVM や KMP で Android を考慮しない module のみから構成される project であれば root project で集約して良いと思う。 Android の考慮が入ると割と悩ましい。というのも root project は build variant を持っていないのでここで default report に build variant を merge させるということができないからである。別の場所で集約するとなると、 application project の場合は app module のような代表的な module が候補として検討できるが、 library project の場合は全てを集約した all-in-one module でも存在しない限りそういう候補は project 内で挙げにくく、 report 集約のためにわざわざ専用の module を作るかと言うとそれも微妙では? と感じる。また application project でも複数の app module を持っている、 product flavor の構成によって一つの build variant で全ての実装に対する coverage report を取得することができないといった場合には同様の課題が発生すると思われる。

現時点 (0.7.3) では複数の build variant に対する report を1つに集約する設定方法みたいなものは無さそうだが、複数の build variant をまとめた custom report variant を作成するという機能が以下の issue で検討されているので、将来的にはなんとかなるかもしれない。でも自分の持っていない build variant を解決できるものなのだろうか...?

github.com

設定の記述で楽をしたいが...

触ってみた感じの印象として今のところ Kover の DSL は単純で習得しやすい一方で素朴な書き方しかできず、素直に書くと楽したくなるところで面倒が発生するなあというものがある。具体的には report の集約を行っている場合、新しい module を追加する度に追加された module に kover gradle plugin を適用するのと集約 module の dependencies に kover(projects.newModule) を追加するのを忘れずに行う必要があるという点。これは地味にダルいし人間のやることなので忘れてしまうこともあるだろうしでどうにかしたいが、現時点では Kover DSL 自体はこれを解決する機能を提供していない。

今年の DroidKaigi 公式アプリでは Kover の設定に関する convention plugin が実装されていたので、そこに上記の課題を何とかするための実装を追加して、 report を集約する module (i.e.:app-android module) 以外では Kover に関して気にしなくて良くなるようにした。

github.com

この追加実装では以下の3つの操作をしている*1:

  1. この convention plugin を適用した module に kover gradle plugin を適用する
  2. この convention plugin を適用した module 以外の module に kover gradle plugin を適用する
  3. この convention plugin を適用した module に対して、 2. の操作を行った module を report の集約対象として依存に追加する

ただし、レビューコメントでも指摘されているようにこのアプローチは Gradle の configuration cache 関連で将来導入されるであろう project isolation と思い切り衝突することをしている。現状これでも configuration cache を効かせられるし、今後このようなユースケースに対して project isolation を守りつつ同様の設定ができるようにになる API が追加される可能性もあるが、あくまで今の内だけの手段として捉えておいた方が良いのではないかと思う。

Configuration cache と project isolation について参考:

ところで multi-module project での設定を簡単にするための機能を追加しようという issue が作成されているので、この設定記述の問題も Kover 側で何とかしてくれそうな雰囲気がある。 Custom report variant で root project から各 subproject の build variant をうまく扱えるようになったら Android project でも root project に設定を全部書いて済ませられるようになるのではないかと期待している。

github.com

とは言え今の時点ではどうすりゃいいのさとなるが、個人的には多少の手間を飲み込んで report 集約の依存の記述は集約場所の build.gradle に直接記述し、 plugin の適用と共通で使える koverReport の設定を convention plugin 化して、 DroidKaigi 2023 公式アプリの AndroidFeaturePlugin.kt みたいな各 module 共通で適用する plugin をまとめて適用する convention plugin に追加して使うくらいがいいんじゃないかなと思う。こうするとおまけとして kover gradle plugin を適用した module 全てに koverReport の設定が入るので module 単位で kover reoprt task を実行した時も集約した report を作る時と同じ設定で動くようになる。 Report ファイルの生成は module 個別に行うことはまずないだろうが、 verification は DSL に module 単位での評価という設定は無いので、 module 単位で基準を満たす or not を見られるようになるのは便利かもしれない... のだが、実は 0.7.3 の時点では全ての report variant 向けとなるはずの koverReport 直下の verify による verification rule の設定が Android build variant に対応する report task には適用されないというバグがあり、 Android project ではこのように設定を共通化しても verification rule が共有できない場合がある*2:

github.com

おわりに

だらだらと Kover を触った所感を書いた。 API がわかりやすく簡単に設定を書けるので、 JaCoCo で自作 merge task を作って頑張ってるようなところではこちらに移行した方が幸せになれるんじゃないかと思う。ただし Android project で flavor まわりの構成が複雑だと現時点ではうまくやるにはまだ機能不足に感じるので、そういう場合は頑張って gradle script を書いてどうにかなることを祈るか、もう暫く様子を見るのがいいかもしれない。

*1:2. と 3. に関して、実際は今回行った実装では考慮不足で module 以外の階層部分 (:core とか) にも操作が適用されてしまい色々無駄が発生しているのだが、結果に影響は出ないからいいやということでサボっている

*2:Default report task には問題の verification rule が適用されるので、 kover gradle plugin を適用する全ての module が同一の build variant を持っているならば、それを default task に merge した上で default report task を使用することで問題を回避し verification rule を共有できる

Kotlin: runCatching と coroutine

内容的には以下の issue で議論されていることの抜粋のようなものだが、つまるところ現状 Kotlin Coroutines と runCatching (より詳細には runCatching の block 内で suspend function を呼んだ場合) の食い合わせが悪い問題に対してどういう対処ができるのかについて、備忘としてまとめておく。

github.com

runCatching は全ての例外を catch する

現時点のコード:

github.com

public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

runCatchingblock の実行中に発生した例外を全て捕捉し、 Result として返すという振る舞いになっている。 Result.failure(e) は catch した ThrowableResult.Failure で wrap して Result に詰めて返すというもの。

Kotlin Coroutines の cancellation 復習

Kotlin Coroutines では coroutine が cancel された際にシグナルとして現在中断されている箇所で CancellationException を throw し、これが起動中の coroutine 内を通り抜けていく。 runCatching はこの CancellationException も捕捉してしまうので、都度自分で rethrow 処理を実装しないと cancel シグナルの伝搬がそこで止まってしまって上層に cancel されたことが伝わらなくなったり、 cancel 時の振舞いが意図しないものになる可能性があったりといった不都合が出る。

CancellationException の伝搬が上手くいかないとどうなるかについてはそこまで真面目に探していないこともあって今のところ良い感じにまとめられた内容の資料を見つけられていないが、冒頭の issue のコメントや以下のページから雰囲気は掴めると思う。

kotlinlang.org

medium.com

現時点の workaround

じゃあ毎回 CancellationException だけ rethrow するのを書くかというとダルく、特別 cancel された時に実行したい処理を書くなんてことがある場合以外は CancellationException の存在をロジック中で意識させたくないというのが正直なところだと思う。なので現時点では冒頭の issue で提案されているような runCatchingCancellationException の catch/rethrow を追加した関数を定義して使うのが落とし所としては無難だろう:

suspend fun <R> suspendRunCatching(block: suspend () -> R): Result<R> {
  return try {
    Result.success(block())
  } catch (ce: CancellationException) {
    throw ce
  } catch (e: Throwable) {
    Result.failure(e)
  }
}

このアプローチは実際に android/nowinandroid で採用されている。

github.com

ただ冒頭の issue に CancellationException の中でも timeout 由来のもの (TimeoutCancellationException) は別で考慮した方がいいんじゃないか? という意見があり、何をどう catch するかは検討の余地がありそう。

また runCatchingThrowable を catch した所で ensureActive() を呼ぶのはどうかという案も出されているが、 ensureActive() を呼ぶには CoroutineScope, CoroutineContext, または Job のいずれかが見えている必要があり使用に制限が出てくるので、個人的にはあまり筋が良いとは感じない。

メモ