【AndroidStudio】Debug Console の ログ フィルター がないので fold する方法

Retrofit でテストしてみたらログが見づらい。

WARNING: An illegal reflective access operation has occurred

何なんですかね。

まずこれ。


WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by retrofit2.Platform (file:/Users/mersan/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.8.0/53fa357bd7538d2c4872bddf33654f113cf6652b/retrofit-2.8.0.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of retrofit2.Platform
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Groovy と Java のバージョンによる相性なのか、なんなのか、よくわからん。

👉 [GROOVY-8339] Fix warning "An illegal reflective access operation has occurred" - ASF JIRA hatena-bookmark
👉 [GROOVY-9103] CLONE - CLONE - Fix warning "An illegal reflective access operation has occurred" - ASF JIRA hatena-bookmark

これについて、Jake さんは問題ないと言っています。

The reflection works around a bug in the JDK which was fixed in 14 but it's only used for default methods. As it's only a warning, it's not preventing your call from working.

👉 Illegal reflective access · Issue #3341 · square/retrofit hatena-bookmark

あと、Retrofit のログも見づらい。


Apr 12, 2023 10:19:16 AM okhttp3.internal.platform.Platform log
INFO: Server: cloudflare
Apr 12, 2023 10:19:16 AM okhttp3.internal.platform.Platform log
INFO: CF-RAY: 7b67a3d78f9f2647-NRT
Apr 12, 2023 10:19:16 AM okhttp3.internal.platform.Platform log
INFO: alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
Apr 12, 2023 10:19:16 AM okhttp3.internal.platform.Platform log
INFO: <-- END HTTP

まあ、Level.NONE にすれば消えるのだが、通信状態は見えなくなるし。


val interceptor = HttpLoggingInterceptor().apply {
  level = if (BuildConfig.DEBUG) Level.BODY else Level.NONE
}

👉 【Retorofit】コピペで使える NetworkModule【Dagger Hilt】 hatena-bookmark

 

■ Filter messages in Android Studio debug console

こんな機能あったんですね。

Right click on the line you want to hide, on the popup menu click "Fold lines like this". You'll be prompted a window where you can edit the folded lines content.

Ps. Folded lines content work the way contains string method does.

👉 Filter messages in Android Studio debug console - Stack Overflow hatena-bookmark

隠したい行で右クリックから Fold Lines Like This です。

折り畳まれて、とりあえずは見やすくなりました!

まだまだ使ったことのない知らない機能がたくさんありますわあ。


AndroidStudioでMarkdownプレビューが表示されない時の対処法

プラグインは対応はしてるのに未だ表示されず。

👉 Markdown - IntelliJ IDEs Plugin | Marketplace hatena-bookmark

一時期は表示されてたことありませんでしたっけ。

 

■ プレビューを表示させる

これ。

The Java Chromium Embedded Framework (JCEF) is a simple framework for embedding Chromium-based browsers in other applications using the Java programming language.

👉 JetBrains/jcef: A simple framework for embedding Chromium-based browsers into Java-based applications. hatena-bookmark

AndroidStudio 稼働のための Java を JCEF が含まれているものに変更すればプレビューを表示できるようです。

[email protected] #19Jun 30, 2022 12:57PM
Based on #7

TLDR for https://intellij-support.jetbrains.com/hc/en-us/articles/206544879-Selecting-the-JDK-version-the-IDE-will-run-under:

In the Android Studio:

1. Find action (ctrl + shift + A / command + shift + A)
2. Search for Choose Boot Java Runtime for the IDE
3. Select the latest version in the "New:" dropdown - e.g. 11.0.12+7-b1504.27 JetBrains Runtime with JCEF
4. OK
5. Restart
Worked in: Android Studio Chipmunk | 2021.2.1 Patch 1 | Build #AI-212.5712.43.2112.8609683

👉 Regression in markdown support (crashes 100% of the time). Missing JCEF [159933628] - Visible to Public - Issue Tracker hatena-bookmark

リスタートすると正しく表示されました。

 

■ まとめ

[email protected] #20Jul 1, 2022 03:48AM
Why isn't it the default?

ほんとそう、なんか問題あるんでしょうか、未検証なのでしょうか?

ちなみに、Mermaid プラグインははっきり未対応のようです。

👉 Mermaid - IntelliJ IDEs Plugin | Marketplace hatena-bookmark


【Android】Gradle Version Catalog 関連のプラグインを探す

Gradle Version Catalog。

TOML ファイル作って使ってみました。

Android Studio でこんな。

⚠ This project uses Gradle Version Catalogs: this tool may not behave as expected.

なんか気持ちが悪いですよね。

update dependencies まわりがこれまで通りに正常に動いてるかどうか、心配になります。

そこらをチェックしてくれるツールを探します。

以下、人気順。


👉 ben-manes/gradle-versions-plugin: Gradle plugin to discover dependency updates hatena-bookmark


👉 jmfayard/refreshVersions: Life is too short to google for dependencies and versions hatena-bookmark


👉 littlerobots/version-catalog-update-plugin: Gradle plugin for updating a project version catalog hatena-bookmark

それぞれどこかで見かけたことがあると思います。

機能は、そのうち Android Studio にマージされるんだろうなと妄想。

👉 Jetpack Compose Samples でも使われている「Version catalog update plugin」で libs.versions.toml を書き出してみる hatena-bookmark
👉 「⚠ This project uses Gradle Version Catalogs: this tool may not behave as expected.」→ 今現在、Gradle Version Catalog には gradle-versions-plugin が必須では? hatena-bookmark


【Gradle Version Catalog】libs.versions.toml キー名の形式 camelCase vs kebab-case

一流のプログラマーのコードには、それぞれのこだわりや哲学があります。

Gradle Version Catalog で利用される設定ファイルの libs.versions.toml を眺めながら、気になったことがありました。

キー名の形式です。

Gradle Version Catalog

 

■ いろんな形式がある

Naming convention (programming)
👉 Naming convention (programming) - Wikipedia hatena-bookmark

 

■ TOML 的には

Case についてははっきりとしたきまりはないようです。

キーはベア・キー、クォート付きキー、ドット付きキーのいずれかです。
ベア・キーはASCII英数字とアンダースコア、ダッシュのみで構成されます(A-Za-z0-9_-)。 ASCII数字のみでも構いませんが、文字列として解釈されることに注意してください(例: 1234)。

👉キー TOML: 日本語 v1.0.0-rc.2 hatena-bookmark

こんなきまりもあります。

インライン・テーブルは一行で表現されるべきです。インライン・テーブルでは、配列と違って、最後の要素のあとにコンマを足すことはできません。波括弧の内側では改行は認められません。ただし、それぞれの値の中で改行することは、それぞれの値型で認められている範囲でできます。ですが、できるとしてもインライン・テーブルを複数行に分割することはなるべく避けるべきです。もしあなたがその必要があるように感じたのならば、普通のテーブルを用いるべきです。


name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }

👉 インライン・テーブル TOML: 日本語 v1.0.0-rc.2 hatena-bookmark

 

■ GitHub で見てみる

camelCase


[libraries]
androidPlugin = { module = "com.android.tools.build:gradle", version = "4.2.2" }
robovmPlugin = { module = "com.mobidevelop.robovm:robovm-gradle-plugin", version.ref = "robovm" }
kotlinStdLib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.5.1" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }

👉 retrofit/libs.versions.toml at 5180f481c58b0be80ad81a0c1ec540fe7e91ee81 · square/retrofit hatena-bookmark

kebab-case


[libraries]
plugin-android = { module = "com.android.tools.build:gradle", version.ref = "plugin-android" }
plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "plugin-kotlin" }
plugin-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "plugin-kotlin" }
plugin-gver = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "plugin-gver" }

👉 kmm-production-sample/libs.versions.toml at aed57893bab7e88a0df50285196e942be6934bf8 · Kotlin/kmm-production-sample hatena-bookmark
👉 PosterBox/libs.versions.toml at 9e988e7fb62620a6913493aeecc51cb71aba3595 · JakeWharton/PosterBox hatena-bookmark
👉 awesome-kotlin/libs.versions.toml at ae83263c467da6f81bb1abc7536ab6af7bc5b4d0 · KotlinBy/awesome-kotlin hatena-bookmark

camelCase - kebab-case


[libraries]
gradlePlugin-android = { module = "com.android.tools.build:gradle", version = "4.2.2" }
gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinCompiler" }
gradlePlugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "1.6.20" }

👉 leakcanary/libs.versions.toml at f9462209ab553c76ad77673f7bebbd155fa76fa9 · square/leakcanary hatena-bookmark

その他


jsoup = { module = "org.jsoup:jsoup", version.ref = "1.15.3" }

でなく


jsoup = "org.jsoup:jsoup:1.15.3"

とも書ける。

👉 okhttp/libs.versions.toml at a94769c31ff18c5c768f790a92eab1de9b27fd83 · square/okhttp hatena-bookmark

あと、どこを選択するか、という問題もある。


com.google.firebase:firebase-bom
                    ^^^^^^^^^^^^

com.google.firebase:firebase-*
                    ^^^^^^^^^^

com.google.accompanist:accompanist-*
    ^^^^^^.^^^^^^^^^^^

com.google.accompanist:accompanist-imageloading-core
    ^^^^^^.^^^^^^^^^^^.            ^^^^^^^^^^^^^^^^^

com.google.accompanist:accompanist-glide
    ^^^^^^.^^^^^^^^^^^.            ^^^^^

com.google.android.libraries.places:places
    ^^^^^^.^^^^^^^.          ^^^^^^

com.google.maps.android:*
    ^^^^^^.     ^^^^^^^.^

com.google.maps.android:android-maps-utils
    ^^^^^^.     ^^^^^^^.        ^^^^^^^^^^

com.google.maps.android:maps-ktx
    ^^^^^^.     ^^^^^^^.^^^^^^^^

👉 refreshVersions/google-version-alias-rules.txt at main · jmfayard/refreshVersions hatena-bookmark

 

■ まとめ

「.(ドット)」に置き換えられて読み込まれる build,gradle 側の考慮も必要になります。

「重複しない直感的に分かりやすい名前」「kebab-case」にするのがスッキリしていいように思うけどみんなはどうしてますか?

👉 Jetpack Compose Samples でも使われている「Version catalog update plugin」で libs.versions.toml を書き出してみる hatena-bookmark
👉 Gradle Version Catalog への書き換えツールを作る【python】 hatena-bookmark
👉 「⚠ This project uses Gradle Version Catalogs: this tool may not behave as expected.」→ 今現在、Gradle Version Catalog には gradle-versions-plugin が必須では? hatena-bookmark


KMM や マルチプラットフォーム を見据えて SQLDelight で Repository

👉 Getting Started - Multiplatform - SQLDelight hatena-bookmark
👉 SQLDelight 1.x Quick Start Guide for Android – Handstand Sam hatena-bookmark

マルチプラットフォームに対応していますが、

まずは、Android のみで使ってみると良いです。

👉 Getting Started - Android - SQLDelight hatena-bookmark

Todo アプリ向けの Repository を作ります。

Hilt と Flow を使っています。

 

■ インストール

Gradleまわりは書き方いろいろですけども適宜書き換えてください。


buildscript {
  repositories {
    google()
    mavenCentral()
  }
  dependencies {
    classpath 'com.squareup.sqldelight:gradle-plugin:1.5.3'
  }
}

apply plugin: 'com.squareup.sqldelight'

// android driver
implementation "com.squareup.sqldelight:android-driver:1.5.3"

// flow-coroutine extension 
implementation "com.squareup.sqldelight:coroutines-extensions:1.5.3"

 

■ スキーマ / クエリー

パッケージ名を、com.your.package としています。


-- app/src/main/sqldelight/com/your/package/data/Todo.sq

CREATE TABLE todo (
  id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  text TEXT NOT NULL,
  updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

INSERT INTO todo(text) VALUES ('宿題をする');
INSERT INTO todo(text) VALUES ('マンガを読む');
INSERT INTO todo(text) VALUES ('プールに行く');

selectAll:
SELECT * FROM todo ORDER BY updated DESC;

deleteAll:
DELETE FROM todo;

insert:
INSERT INTO todo (text) VALUES (:text);

update:
UPDATE todo SET text = :text, updated = (strftime('%s', 'now')) WHERE id = :entryId;

delete:
DELETE FROM todo
WHERE id = :entryId;

count:
SELECT COUNT(id) FROM todo;

Todo.sq ファイルをテキストで作成して、テーブル定義、実行クエリーとメソッド名を箇条書きします。

配置位置は、上記コメントの位置が自然で分かりやすいと思います。

この場合、SQLDelight によって以下に実装に利用するクラス群が生成されます。


app/build/generated/sqldelight/code/Database/debug/com/your/package/

 

■ Database Module

SQLDelightによって生成された Database クラスを利用して書きます。

今回は Android向けなので、AndroidSqliteDriver を使っています。


@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

  @Provides
  @Singleton
  fun provideDatabase(@ApplicationContext context: Context): Database {
    val driver = AndroidSqliteDriver(Database.Schema, context, DB_NAME)
    return Database(driver)
  }

  private const val DB_NAME = "database.db"
}

JetpackCompose の UI である Screen-level の Composable はマルチに稼働しているので、 @Singleton としておくことを忘れてはなりません。

 

■ Repository

時間のかかる処理は Flow を使っておきます。


class TodoRepository @Inject constructor(
  private val database: Database
) {

  fun load(): Flow<List<Todo>> {
    return database.todoQueries.selectAll().asFlow().mapToList(Dispatchers.IO)
  }

  fun count(): Flow<Long> {
    return database.todoQueries.count().asFlow().mapToOne(Dispatchers.IO)
  }

  fun insert(text: String) {
    database.todoQueries.insert(text)
  }

  fun update(id: Long, text: String) {
    database.todoQueries.update(text, id)
  }

  fun delete(id: Long) {
    database.todoQueries.delete(id)
  }

}

Flow - Couroutine Extension は非常に便利です。


fun load(): Flow<List<Todo>> {
  return database.todoQueries.selectAll().asFlow().mapToList(Dispatchers.IO)
}

Cold な Flow で単発の List<Todo> の emit ではなく、データベースが変更されるたびに、新しい List<Todo> を emit してくれます。


@JvmName("toFlow")
fun <T : Any> Query<T>.asFlow(): Flow<Query<T>> = flow {
  val channel = Channel<Unit>(CONFLATED)
  channel.trySend(Unit)

  val listener = object : Query.Listener {
    override fun queryResultsChanged() {
      channel.trySend(Unit)
    }
  }

  addListener(listener)
  try {
    for (item in channel) {
      emit(this@asFlow)
    }
  } finally {
    removeListener(listener)
  }
}

👉 sqldelight/FlowExtensions.kt at master · cashapp/sqldelight hatena-bookmark

(更新中...)

👉 Jetpack Compose で Todo アプリを作ってみた - Qiita hatena-bookmark