どういうときにComposable関数がキャッシュされる?されない?ってすごく重要だと思っていますがみなさん理解されていますか?
Compose 1.0.1で行っています。
これらのアノテーションについてはこちらで紹介しています。
また以下のドキュメントで言及されています
実際どうなるのみたいなところが気になったので、少し気になるパターンでまとめてみました。
キャッシュされているときはでコンパイルすると以下のようなskipのコードが入ります。
このGradle Pluginを使うと以下のようなJavaコードを簡単に見ることができます。
https://github.com/takahirom/decomposer
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", article), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
チェックに用いたコード
@Composable
fun String(name: String) {
Text(text = "Hello $name!")
}
data class Article(val title: String)
@Composable
fun DataClass(article: Article) {
Text(text = "Hello $article")
}
data class MutableArticle(var title: String)
@Composable
fun MutableDataClass(article: MutableArticle) {
Text(text = "Hello $article")
}
data class HasThrowableDataClass(val error:Throwable)
@Composable
fun HasThrowableDataClass(hasThrowableDataClass: HasThrowableDataClass) {
Text(text = "Hello $hasThrowableDataClass")
}
@Immutable data class HasThrowableWithImmutableAnnotation(val error:Throwable)
@Composable
fun HasThrowableWithImmutableAnnotation(hasThrowable: HasThrowableWithImmutableAnnotation) {
Text(text = "Hello $hasThrowable")
}
@Stable data class HasThrowableWithStableAnnotation(val error:Throwable)
@Composable
fun HasThrowableWithStableAnnotation(hasThrowable: HasThrowableWithStableAnnotation) {
Text(text = "Hello $hasThrowable")
}
data class HasSimpleClass(val simpleClass: SimpleClass)
class SimpleClass(val simpleText:String)
@Composable
fun HasSimpleClass(simpleClass: HasSimpleClass) {
Text(text = "Hello $simpleClass")
}
data class HasHasThrowableDataClass(val obj: HasThrowableDataClass)
@Composable
fun HasHasThrowableDataClass(obj: HasHasThrowableDataClass) {
Text(text = "Hello $obj")
}
上記のコードは以下のリポジトリにおいてあり、ビルドすれば勝手にJavaコードがapp/build/decompiledにでコンパイルされたJavaコードが見られます。
https://github.com/takahirom/jetpack-compose-stability/blob/main/app/src/main/java/com/github/takahirom/jetpack_compose_stability_samples/StabilityChecks.kt
デコンパイル結果はこちら
package com.github.takahirom.jetpack_compose_stability_samples;
import androidx.compose.material.TextKt;
import androidx.compose.runtime.Composable;
import androidx.compose.runtime.Composer;
import androidx.compose.runtime.ComposerKt;
import androidx.compose.runtime.ScopeUpdateScope;
import androidx.compose.ui.Modifier;
import androidx.compose.ui.text.TextStyle;
import androidx.compose.ui.text.font.FontFamily;
import androidx.compose.ui.text.font.FontStyle;
import androidx.compose.ui.text.font.FontWeight;
import androidx.compose.ui.text.style.TextAlign;
import androidx.compose.ui.text.style.TextDecoration;
import kotlin.Metadata;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 5, 1},
k = 2,
xi = 48,
d1 = {"\u0000H\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0015\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003H\u0007¢\u0006\u0002\u0010\u0004\u001a\u0015\u0010\u0005\u001a\u00020\u00012\u0006\u0010\u0006\u001a\u00020\u0007H\u0007¢\u0006\u0002\u0010\b\u001a\u0015\u0010\t\u001a\u00020\u00012\u0006\u0010\n\u001a\u00020\u000bH\u0007¢\u0006\u0002\u0010\f\u001a\u0015\u0010\r\u001a\u00020\u00012\u0006\u0010\u000e\u001a\u00020\u000fH\u0007¢\u0006\u0002\u0010\u0010\u001a\u0015\u0010\u0011\u001a\u00020\u00012\u0006\u0010\u0012\u001a\u00020\u0013H\u0007¢\u0006\u0002\u0010\u0014\u001a\u0015\u0010\u0015\u001a\u00020\u00012\u0006\u0010\u0012\u001a\u00020\u0016H\u0007¢\u0006\u0002\u0010\u0017\u001a\u0015\u0010\u0018\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0019H\u0007¢\u0006\u0002\u0010\u001a\u001a\u0015\u0010\u001b\u001a\u00020\u00012\u0006\u0010\u001c\u001a\u00020\u001dH\u0007¢\u0006\u0002\u0010\u001e¨\u0006\u001f"},
d2 = {"DataClass", "", "article", "Lcom/github/takahirom/jetpack_compose_stability_samples/Article;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/Article;Landroidx/compose/runtime/Composer;I)V", "HasHasThrowableDataClass", "obj", "Lcom/github/takahirom/jetpack_compose_stability_samples/HasHasThrowableDataClass;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/HasHasThrowableDataClass;Landroidx/compose/runtime/Composer;I)V", "HasSimpleClass", "simpleClass", "Lcom/github/takahirom/jetpack_compose_stability_samples/HasSimpleClass;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/HasSimpleClass;Landroidx/compose/runtime/Composer;I)V", "HasThrowableDataClass", "hasThrowableDataClass", "Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableDataClass;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableDataClass;Landroidx/compose/runtime/Composer;I)V", "HasThrowableWithImmutableAnnotation", "hasThrowable", "Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableWithImmutableAnnotation;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableWithImmutableAnnotation;Landroidx/compose/runtime/Composer;I)V", "HasThrowableWithStableAnnotation", "Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableWithStableAnnotation;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/HasThrowableWithStableAnnotation;Landroidx/compose/runtime/Composer;I)V", "MutableDataClass", "Lcom/github/takahirom/jetpack_compose_stability_samples/MutableArticle;", "(Lcom/github/takahirom/jetpack_compose_stability_samples/MutableArticle;Landroidx/compose/runtime/Composer;I)V", "String", "name", "", "(Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V", "app_release"}
)
public final class StabilityChecksKt {
@Composable
public static final void String(@NotNull final String name, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(name, "name");
$composer = $composer.startRestartGroup(-1464156855);
ComposerKt.sourceInformation($composer, "C(String)");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(name) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I("Hello " + name + '!', (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.String(name, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void DataClass(@NotNull final Article article, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(article, "article");
$composer = $composer.startRestartGroup(-924370680);
ComposerKt.sourceInformation($composer, "C(DataClass)");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(article) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", article), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.DataClass(article, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void MutableDataClass(@NotNull final MutableArticle article, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(article, "article");
$composer = $composer.startRestartGroup(-738357243);
ComposerKt.sourceInformation($composer, "C(MutableDataClass)");
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", article), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
ScopeUpdateScope var3 = $composer.endRestartGroup();
if (var3 != null) {
var3.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.MutableDataClass(article, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void HasThrowableDataClass(@NotNull final HasThrowableDataClass hasThrowableDataClass, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(hasThrowableDataClass, "hasThrowableDataClass");
$composer = $composer.startRestartGroup(1081258760);
ComposerKt.sourceInformation($composer, "C(HasThrowableDataClass)");
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", hasThrowableDataClass), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
ScopeUpdateScope var3 = $composer.endRestartGroup();
if (var3 != null) {
var3.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.HasThrowableDataClass(hasThrowableDataClass, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void HasThrowableWithImmutableAnnotation(@NotNull final HasThrowableWithImmutableAnnotation hasThrowable, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(hasThrowable, "hasThrowable");
$composer = $composer.startRestartGroup(-1872658300);
ComposerKt.sourceInformation($composer, "C(HasThrowableWithImmutableAnnotation)");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(hasThrowable) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", hasThrowable), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.HasThrowableWithImmutableAnnotation(hasThrowable, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void HasThrowableWithStableAnnotation(@NotNull final HasThrowableWithStableAnnotation hasThrowable, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(hasThrowable, "hasThrowable");
$composer = $composer.startRestartGroup(340271846);
ComposerKt.sourceInformation($composer, "C(HasThrowableWithStableAnnotation)");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(hasThrowable) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", hasThrowable), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.HasThrowableWithStableAnnotation(hasThrowable, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void HasSimpleClass(@NotNull final HasSimpleClass simpleClass, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(simpleClass, "simpleClass");
$composer = $composer.startRestartGroup(790858239);
ComposerKt.sourceInformation($composer, "C(HasSimpleClass)");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(simpleClass) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", simpleClass), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.HasSimpleClass(simpleClass, $composer, $changed | 1);
}
}));
}
}
@Composable
public static final void HasHasThrowableDataClass(@NotNull final HasHasThrowableDataClass obj, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(obj, "obj");
$composer = $composer.startRestartGroup(1108688073);
ComposerKt.sourceInformation($composer, "C(HasHasThrowableDataClass)");
TextKt.Text-fLXpl1I(Intrinsics.stringPlus("Hello ", obj), (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 0, 64, 65534);
ScopeUpdateScope var3 = $composer.endRestartGroup();
if (var3 != null) {
var3.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
StabilityChecksKt.HasHasThrowableDataClass(obj, $composer, $changed | 1);
}
}));
}
}
}
キャッシュされたかの結果
試したもの | キャッシュされた? |
---|---|
String | ○ |
data class Article(val title: String) | ○ |
data class MutableArticle(var title: String) | ✗ |
data class HasThrowableDataClass(val error:Throwable) | ✗ |
@Immutable data class HasThrowableWithImmutableAnnotation(val error:Throwable) |
○ |
@Stable data class HasThrowableWithStableAnnotation(val error:Throwable) |
○ |
data class HasSimpleClass(val simpleClass: SimpleClass) class SimpleClass(val simpleText:String) |
○ |
data class HasHasThrowableDataClass(val obj: HasThrowableDataClass) | ✗ |
valにしていて、変わらない想定でも、なにかinterfaceとかthrowableとかが入り込んじゃう場合は @Stable
などをつけたほうが良さそうに見えます。
Stable
とImmutable
の違いをこのバイトコード上では見つけることができませんでした。
changed()は以下のようになっていたので、両方equalsが使われていそうに見えました。
@ComposeCompilerApi
override fun changed(value: Any?): Boolean {
return if (nextSlot() != value) {
updateValue(value)
true
} else {
false
}
}