Google I/Oで発表された、まだリリースされていない機能ですが、一番気になっている機能でもあります。
個人的にRoborazziというAndroid端末を使わずにスクショを取れるライブラリを作っているので、その両方の将来性みたいなところでも気になるところです。
確認方法
まだ未リリースの機能ではあるのですが、ソースコードはmainブランチ(mirror-goog-studio-main)に入ったりしており、見ていくことができます。
コミットでいうと 13c6af9acff44468927e0e8e0c7f6bdb1e6f4d41
時点での情報をお知らせします。
Android Gradle PluginはAOSPの以下にあるようです。
https://android.googlesource.com/platform/tools/base/+/13c6af9acff44468927e0e8e0c7f6bdb1e6f4d41/build-system
以下のmirror-goog-studio-mainブランチで開発が継続的にされているようです。
https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/build-system
スクリーンショットテストのテストを確認して挙動を確認する
まず何にしても使い方を知るにはテストを確認するとわかりやすい気がしています。
まとめると以下です
- 事前条件
- androidTest/にPreview関数の付いたComposable関数を置く
- android.experimental.enableScreenshotTest を有効にする
- screenshotTestDebugAndroidTest --record-golden でComposable関数のPreviewのスクショを撮る
ちょっとなんでかはわからないですが、デバイスで動かすわけではないのにandroidTestに置くようです (今後変わるかな?)
スクリーンショットテストのタスクを確認する
ここでどういう仕組みで動いているのか気になってきますよね?
タスクのコードは以下になります。
- JUnitPlatformTestFrameworkを使う
- JUnitPlatformTestFrameworkのincludeEngines("screenshot-test-engine")によってそのエンジンを使ってテストを実行するEngineを選ぶ
- dependencyにengineである "com.android.tools.screenshot:junit-engine" (未リリース) を追加する ※1
- エンジンを使ってテストを動かす
ちなみにこのjunit-engineはまだmaven.google.comにないようなので、実際に動かそうとすると失敗します。
※1
val engineVersion = "0.0.1" +
if (Version.ANDROID_GRADLE_PLUGIN_VERSION.endsWith("-dev"))
"-dev"
else
"-alpha01"
dependencies.add(
configurationName,
"com.android.tools.screenshot:junit-engine:${engineVersion}")
}
screenshot:junit-engineを確認する
Engineのコードはこちらになります。
- previewJarのMainを動かす。
- Preview関数のあるソースコード一覧。output pathやgolden pathなどを渡す。
- previewJarはスクリーンショットテストのタスクで設定している ※2
※2
container.create(previewlibCliToolConfigurationName).apply {
isVisible = false
isTransitive = true
isCanBeConsumed = false
description = "A configuration to resolve PreviewLib CLI tool dependencies."
}
dependencies.add(
previewlibCliToolConfigurationName,
"com.android.screenshot.cli:screenshot:${Version.ANDROID_TOOLS_BASE_VERSION}")
screenshot.cli:screenshotを確認する
- 自分自身のjarを解凍する。 (ちょっとなぜこれが必要なのかよく分からなかった)
- ComposeApplication.setupEnvVars(argumentState.extractionDir)なるものを呼ぶ
- ScreenshotProvider.verifyScreenshot(findPreviewNodes(projects[0], argumentState.filePath!!)を呼び出す
ComposeのPreviewはどうやって作られるのか?
ScreenshotProviderは以下にあります。
https://android.googlesource.com/platform/tools/base/+/13c6af9acff44468927e0e8e0c7f6bdb1e6f4d41/previewlib/cli/src/main/java/com/android/screenshot/cli/ScreenshotProvider.kt
ここではComposePreviewElementというものを呼び出しています。なんだかxmlという単語結構でてきます。
ComposePreviewElementはandroid/tools/idea/にあるものなので、Android Studio側で使われる実装だと思われます。
ComposePreviewElementのコードは以下のようになっています。xmlでComposeを作ることによってPreviewを表示していそうということがわかりました。
override fun toPreviewXml(): PreviewXmlBuilder {
val matchParent = displaySettings.showDecoration
val width =
dimensionToString(
configuration.width,
if (matchParent) SdkConstants.VALUE_MATCH_PARENT else VALUE_WRAP_CONTENT
)
...
val xmlBuilder =
PreviewXmlBuilder(COMPOSE_VIEW_ADAPTER_FQN)
.androidAttribute(ATTR_LAYOUT_WIDTH, width)
.androidAttribute(ATTR_LAYOUT_HEIGHT, height)
...
.toolsAttribute("composableName", composableMethodFqn) // ← ここでComposeの関数の場所を渡す
...
return xmlBuilder
}
で、このComposeを表示しているAndroid Viewは実際にどこなのか?が疑問になります。
コードを追うと androidx.compose.ui.tooling.ComposeViewAdapter
というクラスのようです。
そのコードは以下にあります。FrameLayoutを継承したViewになっています。
実際に中では以下のようにComposable関数を呼び出しているようです。
ComposableInvoker.invokeComposable(
className,
methodName,
composer,
*getPreviewProviderParameters(parameterProvider, parameterProviderIndex)
)
まとめ
個人的にはなかなか冒険感があって面白かったです。
- 現状はスクリーンショットテストは、なぜかandroidTest sourcesetにPreview関数を置く形で実装する
- Android StudioのPreviewがベースになって実装されているのでPreview以上のことは難しそうに見えた
- (なのでRobolectricによってEspresso、Hiltなどと一緒に使えるRoborazziとは競合しなさう)
- ComposeのPreviewがxmlのPreviewによって実現されていた