月: 2024年2月

[Android] ImageComposeでローカルの画像が表示されているか確認するユニットテストを考える

ユニットテストでDrawable画像が表示されているかを確認したい時ってあんまり無いのでしょうか。Googleから公式で用意されていないようなのでEspressoの時は自作でDrawableMatcherを作って対応していました。Composeだとどうなんだろうと調べてみるとSemanticsというのを使うらしい。

https://developer.android.com/jetpack/compose/semantics?hl=ja

「テスト フレームワークは、このプロパティを使用してノードを検出し、それを操作して、アサーションを行います。」

一応Semantics自体はテストフレームワークのプロパティを設定してアサーションしても良いことは書いてありました。Semanticsを使うとModifierの属性を追加できるようなのでImageComposeにDrawableリソースを保存できるようにします。

build.gradle.ktsに下記を追加。

implementation("androidx.compose.ui:ui-test-junit4:2.6.2") // バージョンは適宜調べてください

下記のコードを追加。

import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.junit4.ComposeContentTestRule

val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
var SemanticsPropertyReceiver.drawableId by DrawableId
fun ComposeContentTestRule.onNodeWithDrawable(drawableId: Int): SemanticsNodeInteraction =
    onNode(SemanticsMatcher.expectValue(DrawableId, drawableId))

ImageComposeに画像を設定します。

Image(
    modifier = Modifier.semantics { drawableId = R.drawable.ic_app },
    painter = painterResource(id = R.drawble.ic_app),
    contentDescription = stringResource(id = R.string.app_name)
)

ユニットテストでonNodeWithDrawable()を使ってアサーションします。

@RunWith(AndroidJUnit4::class)
class ImageComposeTest {
    @get:Rule
    val composeRule = createComposeRule()

    @Test
    fun assert_image() {
        composeRule.setContent {
            // Imageを表示するCompose
      }
    composeRule.onNodeWithDrawable(R.drawable.ic_app).assertIsDisplayed()
    }
}

https://stackoverflow.com/a/71389302

これでImageComposeがDrawableリソースのsemanticsを持っているか確認できるが、painterと同じ設定をしてしまっているし、ImageComposeのcontentDescriptionはnullableであるが設定するべきで、contentDescriptionのアサーションをすれば実質同じことなので、なんかテストとしては微妙なのかなと思います。公式でonNodeWithDrawable()を作ってくれないかなぁ。


[Compose] LocalInspectionModeを使うと@Previewが少し便利になる

Composeの@Preview便利ですよね。@Previewをを使い込んでいくと「プログラム実行時の処理と@Preview時の処理を分けたい」場面が出てきます。例えばプログラム実行時にサーバから画像を取得して表示するけど@Preview時は通信できないのでローカルの画像を表示するとします。そのときはLocalInspectionModeを使うと便利です。

https://developer.android.com/jetpack/compose/tooling/previews?hl=ja#localinspectionmode

@Composable
fun compose() {
  if (LocalInspectionMode.current) {
    // @Previewでプレビュー表示されている場合
      // ローカル画像を表示する等
  } else {
    // 通常の実行時
    // 通信して画像を表示する等
  }
}

AndroidViewだと<ImageView tools:src=”@drawable/ic_app”>みたいな感じですね。ビルドが長いアプリほど導入のメリットが大きいんではないでしょうか。


[JetpackCompose] NestedScrollの状態でLazyVerticalGridのスクロールリスナーを設定する

LazyVerticalGrid(LazyHorizontalGrid)やLazyColumnでも同様かと思いますがスクロールしたときに他のCompose(View)の表示非表示を切り替えたりしたい場合があります。スクロール状態を取得できるComposeの引数があるかなと思いましたが、少し調べるとModifierでやるようなのでメモしておきます。

val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            if (available.y >= 0) { /*上にスクロール*/ }
            else { /*下にスクロール*/ } 
            return Offset.Zero
        }
    }
}

LazyVerticalGrid(
    modifier = Modifier.nestedScroll(nestedScrollConnection),
    columns = GridCells.Adaptive(400.dp),
    content = {
        items(listItems) { index ->
            val item = listItems[index]
            // itemを使って表示する内容をここに書く
        }
    }
)
        

if (available.y > 0)にしてしまうとオーバースクロールでも反応してしまうのでif (available.y >= 0)にします。NestedScrollは実装がうまくいかないことも多いので調べながら少しづつやっていこう…。DroidKaigi2023でも発表している人がいたからみんな苦労している感じがする


[Android] シングルモジュールのプロジェクトでDetektによる静的解析をする

巷には多くの静的解析ツールがあります。無料でAndroidのプロジェクトを解析したい場合はDetektがおすすめです。

https://github.com/detekt/detekt

導入も簡単でした。今回はシングルモジュールの場合の導入方法を記載します。appモジュールのbuild.gradle.ktsに下記を追加します。

plugins {
  ....
   id("io.gitlab.arturbosch.detekt").version("1.23.5")
}

detekt {
    // 並列処理
    parallel = true

    // 違反があった場合failさせない
    ignoreFailures = true

    // レポートファイルのバスを相対パスにする
    basePath = rootDir.absolutePath
}

ここで一旦sync project with gradle filesを実行します。すると./gradlew detektが実行できるようになっているのでターミナルで実行します。Detektの結果はモジュール内のbuild/reports/detekt内に出力されます。detekt.html, detekt.md, detekt.sarif, detekt.txt, detekt.xmlがあるのであとは煮るなり焼くなり…