nashcft's blog

時々何か書く。

Jetpack Room: One-to-many relationship の結合クエリで並び順を指定する方法のメモ

Jetpack Room で one-to-many relationship の tableを、 one 側を parent とし、 many 側を list property としてまとめて一つの結果として取得したいとき、 many 側の list の並び順を指定する方法についてのメモ、というか以下の issue のまとめ直し:

以後の説明で用いる実装例は Android Developers の以下のページに基にしているため、記事内では entity や DAO の定義などを省略している:

developer.android.com

また、以下に動作デモ用の repository を作成している:

github.com

方法 1. アプリ側で並び替える

クエリによる並び替えの制御を諦めて、取得した後にアプリ側のロジックで並べ替える。なんとも敗北感のある実装ではあるが、取得するデータのサイズがそんなに大きくならないと見込めるのであれば、場合によっては実装コスト的に検討の余地があるかもしれない。

dao.getUsersWithPlaylists().map { userWithPlaylists ->
    userWithPlaylists.copy(
        playlists = userWithPlaylists.playlist.sortedBy { it.playlistName }
    )
}

方法 2. Database view を用いて並び順を指定する

Embedded object と @Relation annotation を使って結合を表現する場合そのままではアプリ側で順序の指定をすることができないが、 Room 2.1.0 以降では SQLite の view という機能を使うことで view を介した順序の指定が可能である。 Database view については以下を参照。

developer.android.com

まず ORDER BY を指定したクエリを使った view を定義する:

@DatabaseView("SELECT * FROM playlist ORDER BY playlistName ASC")
data class SortedPlaylist(
    // SELECT の指定が "*" なので Playlist の property と一致させる
)

この database view を embedded object の @Relationentity に指定する。このとき property playList の型は List<Playlist> のままで問題ない:

data class UserWithPlaylists(
    @Embedded val user: User,
    @Relation(
        parentColumn = "userId",
        entityColumn = "userCreatorId",
        entity = SortedPlaylist::class,
    )
    val playlists: List<Playlist>,
)

方法 3. Multimap として取得する

以下のページで紹介されているように、 one 側を key, many 側を list value とした multi-map でクエリの返却値を表現することで、 SQLORDER BY を記述して任意の並び替えを指定することができる。 Room 2.4.0 から利用できる。

developer.android.com

@Query(
    value = """
        SELECT *
        FROM user
        JOIN playlist ON user.userId = playlist.userCreatorId
        ORDER BY user.userId ASC, playlist.playlistName ASC
    """
)
suspend fun getUsersWithPlaylists(): Map<User, List<Playlist>>

Map として返却する都合上 2つの table 間の結合しか表現できないという制約があるが、一方でこれが採用できる場面ではあれこれと class を作成しなくて良いという点はメリットとなる。 One 側を複数取得する場合の順序の保証については、生成されたコードの中身を見た感じ (JVM では) LinkedHashMap を使っていたので大丈夫なのではないか。個人的な所感としては Map の形で欲しいことなんてある? というのがあるが、アプリ用に別途 model を定義していて詰め替えが発生するような設計ならまあ良いかという感じもする。

おわりに

Database view を使うのが手法を統一できて良いかなーという所感。

美学の入門本を読んだ、読んでる 2

前回の記事でまだ積んである本があると書いたが、そのうち2冊を読み終わったのでまた感想メモ。

nashcft.hatenablog.com

読んだのは以下の2冊:

『近代美学入門』は前回のメモで挙げた2冊と異なり、主に西洋 (西欧) において近代美学が成り立った18世紀頃までの歴史的な流れを紹介している本。これと『美学への招待』を読めば古代から現代までの全体像を大雑把に眺められそうな感じ。著者によるとこの本の目的には近代美学の入門以外に「西洋近代美学およびそれにもとづく価値観を地域・時代的に特有なものとして相対化して捉えられるようになること」があり、そのためか内容としては美学そのものというよりは美学で扱われる「芸術」や「美」 (と美に関わるものとして「崇高」と「ピクチャレスク」) といった概念がどのようにして成り立ったのかを当時の西欧の状況を踏まえて説明するものが主となっている。

『美学への手引き』は『美学への招待』と『近代美学入門』の文献リストに載ってたので後から買い足したもの。内容はこれも美学史的な趣だが、『近代美学入門』と異なり20世紀以降の動向についても扱われているのと、哲学方面の議論に焦点を当てたものとなっており、議論の方向性もこれまで読んだ本たちと比べると読み物というよりは教科書的な雰囲気を帯びている。主な関心ごとに違いがあるとはいえこれ一冊で『近代美学入門』と『美学への招待』で触れた範囲に一通り目を通せるということもできるが、読みやすさという点でこちらの方は一段劣るので、前述の2冊の方が人に薦めやすいだろうという所感。


前回挙げた積み本の残りは西村清和 『現代アートの哲学』なのだが、この間さらにもう一冊買って積んでいる。買ったのは以下:

自分でもなんでこんなにハマっているのか不思議ではあるが、「なんとなく触れてみたら存外面白かったから」くらいしか自分の中から出てこないのでそういうものとして適当に楽しむことにしている。

現代アートの哲学』は上の2冊を読み終わってから手をつけ始めたけど色々あって中断中。というかここのところ全然読書ができていない (固めの内容の本を読む元気がない) し、もっと言うとこのブログも2冊を読み終えた6月末にゆるく書き始めたのに7月に入って破茶滅茶が発生してから諸々の余裕がなくなり今やっと書き上げたみたいな状況だったりする。ちなみに破茶滅茶はまだ継続している。

Android: 16 KB page size サポートメモ

モチベーション

2025-11-01 以降、 Android 15 (API level 35) 以上を target とするアプリは 16 KB page size のサポートが必須となる。

android-developers.googleblog.com

Starting November 1st, 2025, all new apps and updates to existing apps submitted to Google Play and targeting Android 15+ devices must support 16 KB page sizes.

サポート状況の確認方法

上の記事にあるように Play Console の app bundle explorer で確認できる他、以下のページにあるように CLI でも確認する方法がある:

また、 Android Studio Narwhal を使っていれば、未サポートの so ファイルが含まれている際に Analyze APK の Alignment 列や Run app の実行時に warning が出るようになったので、手元でも簡単に確認できる。以下は nowinandroid に Crashlytics NDK の 16 KB page sizes サポート前のバージョン (firebase-bom 33.1.0 or firebase-crashlytics-ndk 19.0.1) への依存を追加して発生させた 16 KB 未サポート時の警告表示の例。

Analyze APK の表示。16 KB page size 未対応の so ファイルがあると Alignment の列で警告が出る

Run を実行した後に表示される warning

私はこれを Android Studio Narwhal | 2025.1.1 の RC1 と RC2 で確認しているが、 New features in Android Studio Preview のページを見ると Feature Drop での新機能として紹介されておりちょっと状況が謎。

developer.android.com

対応

依存している外部ライブラリ

  • 16 KB page sizes をサポートしているバージョンに更新する
  • 開発が止まっていてサポートの見込みがないものに依存しているなら、依存を剥がす・代替を探す・fork して自分でメンテするなど頑張って何とかする

自分で開発しているアプリ・ライブラリ

この辺を見て 16KB memory page をサポートした状態に修正する:

AGP と NDK のバージョンをそれぞれ 8.5.1, r28 以上に更新できればビルド設定を特に弄らずに 16 KB page size に対応できるので、そこを目標とするのが良さそう。

NDK の最新バージョンは以下から確認する:

各バージョンのサポート期間については GitHub にある repository の wiki page の方が詳しい:

美学の入門本を読んだ、読んでる

特に強い動機があったわけでもなく、単に「そういえば美学ってどんなものなのだろう」という気持ちのためと、何かのきっかけで以下の文献リストを見たので、最近は興味の赴くままに美学に関する入門書を読むなどしていた。

morinorihide.hatenablog.com

これまでに読んだのは以下の2冊:

どちらも現代美学の本。『美学への招待』の方は西洋近代美学への対抗として現代の美学 (著者は「新美学」と呼んでいる) を置き、そこに至るまでの、特に二十世紀に起きた芸術や美学を取り巻く状況や議論の変化を辿りつつ、美学にはどんな問いやトピックがあるかを具体的な作品や実生活での場面を例に紹介している。また、最後の二章では現代の美学における主要なテーマについて著者の見解を述べている。第十章以外では突っ込んだ議論や解説を避けており、教科書的な入門書を期待していた場合は残念という感想になるかも知れない。だが読み物としては読みやすいし引き込まれるような読書体験だったので、そういう意味で「招待」なのだろう。当初はそんなに数を読む予定ではなかったし、『「美味しい」とは何か』も読むつもりはなかったのだけど、読んでみて存外に面白かったので他にも色々読んでみるかという気持ちになった本だった。

『「美味しい」とは何か』は料理や飲食は芸術か (美学の対象として扱えるか、ということ) をテーマとして美学について紹介・解説している。入門的な解説としては素直で、食をテーマにしているのも現代アート作品みたいな「これ本当に美的なものとして扱えるの?」という点でなるほど現代美学の題材としてのいい感じに身近な例ですねという感じなので、いわゆる芸術方面に馴染みのない人にはとっつきやすい入門書のように思う。という感じで読みやすく分かりやすい良い入門書という風なのだけど、「食は芸術か」というテーマに対する議論の進め方や到達点が読んでて「そうか...?」ってなるところが気になった。ちょうど以下の書評がこの点について指摘・解説している (というかこの書評を先に読んでいて、実際に書籍の方を読んでなるほどねとなった、という時系列)。この書評の著者は冒頭の文献リスト (この本がお勧めされている) を書いた人で、「お勧めの本なのは変わらないんだけどこういう批判もできるよ」ということらしい。

www.jstage.jst.go.jp

morinorihide.hatenablog.com


他に以下の3冊が積まれている:

そんな入門本ばかり読んでどうするのだ、と思わないこともないが、今のところ真面目に美学を掘り進めていくつもりではなく、背景の確保、つまり美学という領域の輪郭を何となく掴むことができれば OK という考えなのでまあこれで良しとしている。もっと深く潜ってみたい、となったら冒頭の文献リストの他の書籍や『分析美学入門』 (https://www.keisoshobo.co.jp/book/b109885.html) あたりを読み始めるのではなかろうか。

個人的 Android project における Gradle まわりの考え方2024年版

Android project で Gradle ファイルをどう書くかについて、現在の自分がどのような考えを持っているかつらつら書いてみる。

きっかけ

随分前の話だけど、以下の投稿から続く Gradle の機能採用に関する判断の考え方を読んで、そういえば自分も以前は同じようなスタンスだったことを思い出した。

それで自分の投稿を探してみると2022年頃にはこんなこと言っていた:

今はこの頃からスタンスが変わっており、以前以下のような投稿をしていた:

現在はこの投稿時からもまた少し変わった、というか当時は仕事で Gradle まわりをの整備を集中的に行っている最中だったこともあって気持ちが強くなっていたのが、時間が経って落ち着いたものになったというか、そんな感じ。

この考え方の変化を思い出してなんだか面白いなと感じたので、ついでに自分のも Gradle に対する考え方の現在のスナップショットを書き出してみるかと思い立ちこれを書くことにした。トピックは折角なのできっかけとなった投稿に倣って「Gradle ファイルを Groovy で書くか Kotlin で書くか」「共通設定をまとめるのに何を使うか」「依存ライブラリの管理方法」の3つにした。なお、この記事の内容は業務で開発するものなど「他人と共有する・他人に引き継ぐ」ことが想定される複数人で開発する Android project を対象としている。個人開発ではみなさん好きにやっていくのがよろしい。

Groovy か Kotlin か

現在は基本的には Kotlin DSL で記述するし、既に Groovy で書かれているものも書き換える方に倒すようにしている。理由は単純で、 project 内で使用する言語のバリエーションを減らしたいからである。2024年時点において Android アプリはほぼ Kotlin で書かれていることが期待できるので、じゃあ Gradle ファイルも Kotlin で書けた方が (Android アプリ開発においては) そこでしか使われない Groovy を覚えるより楽だし、Android アプリ開発初心者や Groovy/Gradle に慣れ親しんでない人は細かい文法の差異で混乱することがなくなって嬉しいよね、という発想。

ただ、絶対に Kotlin DSL にするというほど気持ちがあるわけではない。私自身は Gradle ファイルが Groovy で書かれていて困ることはないので、例えば Kotlin DSL が出る前から開発されているアプリでチームメンバーも古くからいる人たちがずっと残ってて、 Groovy で書かれていることに課題を感じていないみたいな状況だったら Kotlin DSL に書き換えようとはしないと思う。 Groovy 触ったことないって人が加入したらどうしようかって話を始める感じ。

そこまで気持ちが強くないのは書き換えによる体験の差がそこまで大きくないから、という印象にもとづいているように感じる。静的型付きの言語で書くようになったからと言ってコード補完が速くなるわけでもないし*1、私がこれまで触れてきたアプリの規模や構成だとビルド実行時間がわかりやすく短くなるわけでもなかった、といったような実利面の弱さをこれまでの経験から所感として持っている。なので自分にとっての書き換えのモチベーションは、繰り返しになるがあまりビルドスクリプトに触れる機会のない人にとっても負担が少なくなるようにアプリと同じ言語で書けるようにしようというメンテナンスへの配慮が殆どとなる。

共通な設定のまとめ方 (script plugins, pre-compiled plugins, binary plugins)

真面目に multi-module 化をして、数十 module にはなるだろうなという見立てがある場合には build-logic module を作成して binary plugins で共通設定を記述する、というか本当にちょっとしたプロダクトでない限り仕事で開発するようなアプリはそのようになるだろうと考えているので基本 binary plugins 派になったと言えるか。「本当にちょっとした」の自分の中での基準は分割したところで十個も行かないくらいで、このような規模の小さい project の場合は script plugins で済ませてしまうかもしれないし、そもそも記述をまとめず重複を受け入れてしまうかもしれない。

Project の規模以外の判断基準だと build-logic module の用意や Gradle plugin の書き方を覚える (覚えてもらう) ための手間とビルドパフォーマンスのトレードオフかなーという感じ。とはいえビルドパフォーマンスの方が大事でしょというのと今は nowinandroidDroidKaigi 公式アプリみたいな参考実装があるという点から迷ったら binary plugins を書く方に倒しがち。

きっかけで書いたように元々全部 script plugins でいいじゃんと考えてたところから変化したのは、おもに以下の記事・スライドから影響を受けたため:

依存プラグインやライブラリの定義・管理方法

これのスタンスに関しては自分はかなり適当で、文字列リテラルで何度も同じものを書く羽目にならなければ別に何でも構わないと思っている。他は強いて言うなら Renovate や Dependabot みたいなので更新を自動化できることくらいだろうか。

ここのところは Gradle が公式で出している仕組みだからということで version catalog を入れるようにしているが、どちらかというと type-safe project accessors は絶対使うのでそれと構文を合わせるために使っているという面が強い気がする。

Version catalog と言えば以前は呼び出し側からのコードジャンプで生成コードに飛ばされるなど不便に感じることもあったが、最近は IDE のサポートも充実しており特使用感で気になることは無くなってきた。もしくは単に慣れたか。

考え方の変化について

変化については大雑把にはふたつ理由があって、ひとつはここ2年ほどビルドスクリプトまわりを自由にさせてもらえている*2ことでそれなりの知識と経験を得られたのが大きかったと思う。これによって自分の中でのコントロール可能な要素が広がり、作り込み度合いの段階ややりすぎ感の出てくる程度の肌感を得ることができた。また、上で挙げた repository や資料といった参考として紹介できるものの把握や評価ができたのも、こちら側に倒して良いかと考える後押しになった。

もうひとつは今いる組織の性質的に新しい仕組みや機能を導入する傾向が強く、保守的な対応の方が穏便だと考えていても割と先進的な選択をとりがちな状況に置かれているという事情がある。結局のところ保守的な対応方針を取るにはそれ相応に古くからの深い知識や長い付き合い (ただし漫然としたものではない) による匙加減の把握といったものが必要で、それらがあって初めて必要最低限の対応や素朴な構成の維持が可能になると考えている。そういった Gradle 熟練者のいない、つまりはこれから Gradle について学んでいくことになる組織では、割と無邪気に新機能を放り込まれたり、かなり大味な設定変更で異様に先進的な設定になったりすることがままある (あった)。それなら昔ながらのやり方に押し込むよりは先回りして現代的な構成をとっていた方が変更に対するコントロールがしやすく、かつ他チームメンバーのメンテや習得のモチベーションを損なわずにやっていけるだろうという気持ちになり、じゃあもうどんどん先行していくかとガチャガチャやっている内に考え方もそっち寄りになったように思う。

こんな感じで割と現代的な機能を採用するのにポジティブな考え方になってはいるが、上の3トピックに対するスタンスでも書いているように絶対そうするというほどの気持ちの強さがあるわけではないのは、そうするように説得したり、既存のものを書き換えたりするほどの特別なメリットがあるとは自分自身は感じていないからだろう。なので、きっかけにある TimeTree のようにすでに体制があって、それで上手くやってきているところでは自分も態々コストかけて書き換えたりはしないだろうなあとは思う。自分は今のところ諸々含めて「これからやっていく」という文脈の中にいるので、じゃあ .kts で書く方が Gradle に慣れていない人にもなじみやすいだろう、これから multi-module 化を本格的にやっていくなら下準備として build-logic module で binary plugins として共通部分を括り出しておくと module 増やすの楽だし将来パフォーマンス面も苦しくなりにくいだろう、とか、そういう判断をして良いのでそうしているという感じ。

おわりに

Gradle まわりの扱いについて、現時点での考え方と、以前の素朴に書いていれば良いという考え方からどのように変わったのかについて書いた。自分は他の人がどういうことを考えているのか知れるのが割と嬉しいのできっかけの投稿や記事も含めてこういう文章は好きなのだが、今回の場合何か主張があって書いたわけではないので全体的に取り留めのないものになってしまった。まあこういった話題には固定的な「答え」のようなものは無く、それぞれの状況があるばかりなのでそれに応じて上手くやっていきましょう、皆さんはどうですか? という気持ちだったのかもしれない。

9月末ぐらいにぼんやりと書き始めてから中々まとまらず結局年末ギリギリになってしまったが何とか2024年内に書き終えられてよかった。

*1:これは自分が Gradle ビルドスクリプトの基本的な API をある程度把握しているから迷わずタイプできるという点も影響しているかもしれない。たとえば自分で何かしらの property や関数の呼び出しを書き切る方が補完サジェストが表示されるより早い、みたいなことがよくある。

*2:というか最初はとあるビルド構成まわりが廃墟寸前の project を全部いい感じにする or die みたいな状況だった

Kotlin: non-packed klibs メモ

Kotlin 2.1.0 更新でちょっと躓いたので調べたことの記録として。あまり真面目に調べていないのでちゃんとしてない部分がある。

Non-packed klibs is 何

Kotlin 2.1.0 でサポートされるようになった機能。 What's new での記載は以下:

https://kotlinlang.org/docs/whatsnew21.html#support-for-non-packed-klibs

まずそもそも klibs って何、というのがあるが、これまでの multiplatform module における native target のコンパイル成果物は *.klib というファイルで、このコンパイル生成物のことを指している。ちなみに .klib ファイルの実態はただの zip ファイルっぽく、 What's new で言ってる non-packed klibs というのはこの .klib ファイルに格納されているもののことのようだ。つまり 2.1.0 から by default での生成物が .klib ファイルに圧縮 (= pack) される前の状態になるということらしい。

DroidKaigi 2024 公式アプリの app-ios-shared module を IosArm64 を target にしてコンパイルした時の生成物。左が Kotlin 2.0.21、右が Kotlin 2.1.0 での実行時の結果。

あと成果物を packed にするか unpacked にするかの設定について記載があるけどこれはライブラリ開発者向けっぽい? 今回は結局使い所がなかったのでよく理解していないままでいる。

これ関連で遭遇した問題

ある KMP プロジェクトの Kotlin のバージョンを 2.1.0 に上げた時に、 iOS target でビルドが失敗するようになった。

* What went wrong:
Execution failed for task ':<module>:compileKotlinIosArm64'.
> java.io.FileNotFoundException: /<path to module>/build/classes/kotlin/iosArm64/main/klib/<module> (Is a directory)

エラー内容について簡単に調べてみたら以下のページがヒットした:

https://slack-chats.kotlinlang.org/t/26714754/with-kotlin-2-1-0-new-https-kotlinlang-org-docs-whatsnew21-h

github.com

対象のプロジェクトも moko-resources を使用していて、これの更新をしばらくサボっていたこともあってこの件そのものっぽいなーという気持ちで読み進めていたら、 non-packed klib に対応したバージョンに更新しないとダメだよって書いてあったのでハイとなった。 0.24.4 で対応されており、更新したら問題が解消された。よかったですね。

github.com

修正内容 を見た感じ resources-generator 内で klib artifact を扱う時に packed klib しか考慮しておらず、無条件で unzip しようとして失敗していたというのが原因だったようだ。

おしまい

ライブラリの更新をサボってはいけない

Android: 新しいバージョンがリリースされる時の対応の流れに関する備忘録

何度か同じような内容を人に伝える機会に遭遇するなと思ったのでブログ記事にしてしまおう、ということで、 Android の新しいバージョンのリリースに対してどういう流れで対応を進めるかについての自分の考え方のメモ。

Android バージョンアップによって発生する変更

以下の3種類に大別できる:

  1. アプリの target API level にかかわらず、当該バージョンで動くデバイス上でアプリを動かす際に関係する変更
  2. アプリの target API level (= target SDK version) をその SDK に合わせた時、当該バージョンで動くデバイス上でアプリを動かす際に関係するもの
  3. SDKAPI 変更
    • Android framework の API の追加・変更・削除
    • Non-SDK interface restrictions の更新

バージョンアップ対応の進め方

まず自分の開発しているアプリへの影響について調査し、必要な対応タスクを上述の3種類に対応した3つのタスクグループとしてまとめる。着手は以下の優先順位で行う:

  1. "Behavior changes: all apps" 関連
  2. SDKAPI 変更 -> compileSdk を上げる
  3. "Behavior changes: Apps targeting Android XX" 関連 -> targetSdk を上げる

優先順位は実質的なデッドラインに当たるイベントに基づいている。つまり、1. に関しては新バージョンの final release, 2. は Jetpack などのライブラリが compileSdk を上げたリリースを行った時 (final release が行われてしばらく経ったくらい)、そして3. は Google Play の target API level requirements で要求されるようになったら (2024年現在の目安としては final release の翌年の8月末)、ということになる。

Final release 前後で全ての対応を完了できるならそのように進めるのが望ましいが、諸々の事情でもっとかかるみたいな状況になった場合は上記の優先順位とデッドラインをもとにいつからいつまでの間に取り組ませてほしいなど調整をしつつやっていくことになる。 Target SDK version を上げるための対応は素朴にデッドラインに合わせて計画を立てるとスケジュールが間延びしてダレがちなので、リリースされた年内で完了させられるように持っていきたいなと考えている。

なぜこういうことを考えるのか

主にはプロダクトオーナーとかプロジェクトマネージャーとかと計画づくりやスケジュール調整をする時に、どういう対応の段階があってそれぞれの対応が完了すると何がクリアになるかというのを共有するため。あとは何かしらの事情でスケジュール的に余裕がなくて、それぞれの対応に十分な期間や人員を確保しつつビジネス要望にも応えるには何がどの程度まで調整可能かみたいな相談というか交渉というかみたいなのが発生した時に必要になることもある。