placeholder

Functions summary

Modifier
@Composable
Modifier.placeholder(
    placeholderState: PlaceholderState,
    shape: Shape,
    color: Color
)

Modifier.placeholder draws a skeleton shape over a component, for situations when no provisional content (such as cached data) is available.

Functions

Modifier.placeholder

@Composable
fun Modifier.placeholder(
    placeholderState: PlaceholderState,
    shape: Shape = PlaceholderDefaults.shape,
    color: Color = PlaceholderDefaults.color
): Modifier

Modifier.placeholder draws a skeleton shape over a component, for situations when no provisional content (such as cached data) is available. The placeholder skeleton can be displayed instead, while the content is loading. The reveal of the content will be animated when it becomes available (and hidden again if the content becomes unavailable), unless the ReducedMotion setting is enabled, in which case those are instantaneous. NOTE: For animations to work, an AppScaffold should be used.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.FilledTonalButton
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.placeholder
import androidx.wear.compose.material3.placeholderShimmer
import androidx.wear.compose.material3.rememberPlaceholderState

var labelText by remember { mutableStateOf("") }
var imageVector: ImageVector? by remember { mutableStateOf(null) }
val buttonPlaceholderState =
    rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null)

FilledTonalButton(
    onClick = { /* Do something */ },
    enabled = true,
    modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState),
    label = {
        Text(
            text = labelText,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.fillMaxWidth().placeholder(buttonPlaceholderState),
        )
    },
    icon = {
        Box(
            modifier =
                Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState)
        ) {
            if (imageVector != null) {
                Icon(
                    imageVector = imageVector!!,
                    contentDescription = "Heart",
                    modifier =
                        Modifier.wrapContentSize(align = Alignment.Center)
                            .size(ButtonDefaults.IconSize)
                            .fillMaxSize(),
                )
            }
        }
    },
)
// Simulate content loading completing in stages
LaunchedEffect(Unit) {
    delay(2000)
    imageVector = Icons.Filled.Favorite
    delay(1000)
    labelText = "A label"
}

If there is some cached data for this field, it may be better to show that while loading, see

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.FilledTonalButton
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.placeholder
import androidx.wear.compose.material3.placeholderShimmer
import androidx.wear.compose.material3.rememberPlaceholderState

var labelText by remember { mutableStateOf("Cached Data") }
var imageVector: ImageVector? by remember { mutableStateOf(null) }
val buttonPlaceholderState =
    rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null)

// Put placeholderShimmer in the container and placeholder in the elements of the content that
// have no cached data.
FilledTonalButton(
    onClick = { /* Do something */ },
    enabled = true,
    modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState),
    label = {
        Text(
            text = labelText,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.fillMaxWidth(),
        )
    },
    icon = {
        Box(
            modifier =
                Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState)
        ) {
            if (imageVector != null) {
                Icon(
                    imageVector = imageVector!!,
                    contentDescription = "Heart",
                    modifier =
                        Modifier.wrapContentSize(align = Alignment.Center)
                            .size(ButtonDefaults.IconSize)
                            .fillMaxSize(),
                )
            }
        }
    },
)
// Simulate content loading completing in stages
LaunchedEffect(Unit) {
    delay(2000)
    imageVector = Icons.Filled.Favorite
    delay(1000)
    labelText = "A label"
}

Note that the component should still be sized close to the target, so the final reveal of the content is less disruptive.

Parameters
placeholderState: PlaceholderState

the state used to coordinate several placeholder effects.

shape: Shape = PlaceholderDefaults.shape

the shape of the placeholder.

color: Color = PlaceholderDefaults.color

the color to use in the placeholder.