【Jetpack Compose】脱 Tv Lazy Layout --- テスト編
本記事の内容
Android TV 向けのライブラリである tv-foundation 1.0.0-alpha11 にて、遂に Tv Lazy Layout が非推奨 になりました。
Tv Lazy Layout の引数である PivotOffsets
に関して、デフォルト値を使用している場合は単に Tv
のプレフィックスを外すだけで問題ないです。
しかし、デフォルトとは異なる値を設定している場合は、CompositionLocal を利用して BringIntoViewSpec
インスタンスを配布する実装に変更する必要があります。
(詳しい内容は 移行前後の差分編, アニメーション編 を参照してください🙏)
本記事ではこの Tv Lazy Layout から Lazy Layout の移行中にハマったポイントのうち、テストに関する内容を記載します。
パート | 内容 |
---|---|
Part.1 | 移行前後の差分編 |
Part.2 | アニメーション編 |
Part.3 | テスト編 (本記事) |
テスト環境における挙動差分
APK をビルドしてアプリ上で挙動確認を行い、無事に Tv Lazy Layout から Lazy Layout に移行できたと思っても、最後に思わぬ落とし穴が存在しました。
テスト環境における挙動差分です。
私が現在開発を行なっているアプリでは Roborazzi を用いた VRT が導入されているのですが、そこで意図しない挙動差分が検出されました。
Roborazzi に限らず、テスト環境全般で生じる問題であったため本記事にて残しておきます。
本セクションでは以下のサンプルアプリを用いて説明を行います。
【Tv Lazy Layout】 引数の PivotOffset を利用
(当然ですが) Tv Lazy Layout では、テスト環境であっても引数に渡した PivotOffset
に基づいてスクロールが行われます。
使用したサンプルアプリだと、引数のデフォルト値として設定されている PivotOffset
を利用しています。
よって、アプリ環境かテスト環境に問わず フォーカスしているアイテムの上部が TvLazyColumn における height の 30% 地点にくる という挙動になります。
実際にテスト環境で item 10
にフォーカスを当てた際のスクリーンショットを Roborazzi を用いて取得すると以下のようになります。
テスト環境におけるスクリーンショット | (比較用) アプリでの挙動 |
---|---|
【Lazy Layout】 デフォルトの BringIntoViewSpec を利用
テスト環境で Tv Lazy Layout を用いた際のスクロール挙動はアプリ環境の挙動と一致していました。
一方で Lazy Layout に移行してしまうと、テスト環境でのスクロールはアプリ環境の挙動とは異なってしまいます。
実際にテスト環境で item 10
にフォーカスを当てた際のスクリーンショットを Roborazzi を用いて取得すると以下のようになります。
テスト環境におけるスクリーンショット | (比較用) アプリでの挙動 |
---|---|
アプリ環境だとこれまでの記載と同様に、フォーカスしているアイテムの 上部 が TvLazyColumn における height の 30% 地点にくるようになっています。
しかし、テスト環境ではフォーカスしているアイテムの 下部 が TvLazyColumn における height の 100% 地点にくるような挙動となっています。
この原因は LocalBringIntoViewSpec
の初期値に関係しています。
この LocalBringIntoViewSpec
は以下のような実装となっています。[1]
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@get:ExperimentalFoundationApi
@ExperimentalFoundationApi
actual val LocalBringIntoViewSpec: ProvidableCompositionLocal<BringIntoViewSpec> =
compositionLocalWithComputedDefaultOf {
val hasTvFeature =
LocalContext.currentValue.packageManager.hasSystemFeature(FEATURE_LEANBACK)
if (!hasTvFeature) {
// PivotOffset(parentFraction = 0f, childFraction = 0f) と同じスクロールの基準位置
// アニメーションも設定されていない
DefaultBringIntoViewSpec
} else {
// PivotOffset(parentFraction = 0.3f, childFraction = 0f) と同等
PivotBringIntoViewSpec
}
}
アプリ環境では hasTvFeature == true
であるため PivotBringIntoViewSpec
が参照されます。
一方で、テスト環境では hasTvFeature == false
であるため DefaultBringIntoViewSpec
が参照されるようになっていました。
よって、PackageManager を Shadow して PackageManager.FEATURE_LEANBACK
を有効化させる必要があります。
私は以下のような TestRule を実装して解決しました。
class ShadowPackageManagerRule : TestRule {
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
val shadowPackageManager =
Shadows.shadowOf(ApplicationProvider.getApplicationContext<Context>().packageManager)
shadowPackageManager.setSystemFeature(PackageManager.FEATURE_LEANBACK, true)
base.evaluate()
}
}
}
}
Leanback にしか関係のないパラメータかと思い油断していましたが、テスト環境でも PackageManager.FEATURE_LEANBACK
を有効化するように心がけておくと良さそうです👌
まとめ
3つの記事に分けて Tv Lazy Layout から Lazy Layout への移行における注意点を説明してきました。
今回の記事ではテスト環境における落とし穴について説明しました。
テレビ特有の実装は PackageManager.FEATURE_LEANBACK
を参照している可能性があるため、テスト環境ではこれを有効化されているとより本番環境に近い挙動を再現できそうです。
Tv Lazy Layout から Lazy Layout への移行も完了し、Android TV における Compose の挙動もより安定化してきたように感じます。
Android TV に関する情報は少なく、未だ気づいていない不具合や実装ミスが存在している可能性があります。
また何か知見が得られたら共有しようと思いますし、何か知見が得られた際には記事に残していただけると大変嬉しいです☺️
-
tv-foundation 1.0.0 alpha-10 ↩︎
Discussion