Blog Infos
Author
Published
Topics
, , , ,
Published

Green Apple Lot by RP Photography

 

Shimmer is an effect that improves the user experience by providing a smoother transition between the “loading” and “ready” states by showing a skeleton of the UI that is about to be shown. This effect reduces the user uncertainty about what will be shown, and it’s widely used in applications that rely on remote data.

The goal of this article is not to show how to implement the shimmer effect, there are multiple interesting articles about this topic. This article will cover how to seamlessly integrate it with your existent Compose code.

Challenge

Recently, I was tasked to rebuild a screen that loads content from the server and display to the user. During the development, I thought it would be nice to add a shimmer effect since the request took a few seconds and the screen doesn’t have a cache mechanism.

Loading without shimmer


Loading with shimmer

 

My first attempt was to create a new Composable to “mirror” the real one, having the shimmer effect. The benefit of this approach is that since there is no data ready, we can draw shapes in the same position as the real components. However, maintaining two different sets of Composables is difficult, especially for more complex screens that have multiple nested ones. In addition, all the UI changes in the original Composable needs to be ported to these “mirrored” versions to keep the consistency.

After some investigation, I decided to mix-and-match some solutions and develop a way to use the shimmer, modifying as little as possible the existing Composables.

Implementation
Adding the shimmer effect

As mentioned earlier, there are several references on how to implement your custom shimmer effect. To keep this article simple and focused on the integration, we will use the valentinilk/compose-shimmer library, which is easy to use and have multiplatform support.

Implementing a new Modifier

We will implement a new Modifier to wrap the shimmer effect and draw the placeholder, if needed. Here is the proposed code:

@Composable
fun Modifier.shimmerable(
enabled: Boolean,
color: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
shape: Shape = RoundedCornerShape(8.dp),
): Modifier {
if (!enabled) return this
return this
.shimmer() // 3rd party library call
.background(color = color, shape = shape)
.drawWithContent {
// Do not draw the actual content.
}
}
view raw Shimmerable.kt hosted with ❤ by GitHub

Analyzing the code above, the implementation:

  1. Receives a boolean and have an early return if the effect should not be enabled
  2. Receives a color and shape that can be customized
  3. If enabled, the Modifier will draw the shape where the content should be and do not draw the content at all

Here’s how the Compose Preview will look like:

Preview: shimmer disabled (top) vs. shimmer enabled (bottom)

For using it, all we need to do is adding the Modifier to the Composables we want to have this support:

@Composable
private fun ItemCard(
item: ItemData,
isLoading: Boolean = false
modifier: Modifier = Modifier
) {
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.shimmerable(enabled = isLoading)
)
// [...] Remaining Composable code
}
view raw MyComposable.kt hosted with ❤ by GitHub
Integrating with the existing code

As you might have noticed, even though we are sending the isLoading value to enable the shimmer effect, since the code is loading the data from the server, we don’t have ItemData available yet to display. To help solve this issue, we can apply the concepts:

  1. Make the Composable stateless, making it easy to integrate any data without introducing side effects;
  2. Create a fake representation of the data, similarly to the data we will use in the Composable Preview

After applying the concepts above, we would have a code similar to the following:

// [...] Composable screen code
when (state) {
MainState.Loading -> {
ItemCard(
item = fakeData,
isLoading = true,
)
}
is MainState.Success -> {
ItemCard(
item = state.itemData,
isLoading = false,
)
}
}
// Fake data to be used while loading and in the preview
private val fakeData = ItemData(
imageResId = android.R.drawable.ic_dialog_info,
title = LoremIpsum(4).values.toList().first(),
description = LoremIpsum(8).values.toList().first(),
)
@PreviewLightDark
@Composable
fun ItemDataPreview() {
ExampleTheme {
ItemCard(item = fakeData)
}
}
view raw MyScreen.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

When interacting with a mobile app that regularly crashes or freezes, 53% of users uninstalled the app, 37% stopped using it, and 28% looked for a replacement.
Watch Video

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Engineer
Instabug

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Enginee ...
Instabug

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Engineer
Instabug

Jobs

No results found.

Optional: Implement a custom CompositionLocal

Before diving into the implementation, it’s important to notice that CompositionLocal should be used with care, since its over-usage adds extra complexity to the code. For more information about this component and when or not use, please access the official docs.

The solution we developed worked good so far, however the original goal was to change the original Composable code as little as possible. In the current state, we need to pass the isLoading: Boolean around to determine if theModifier.shimmerable should be enabled or not.

By implementing a custom CompositionLocal, we can pass this information down the Composable nodes implicitly, making the Composables unaware of this value since it only matters for our custom Modifier. One possible implementation for this feature is:

@Composable
fun Modifier.shimmerable(
shape: Shape = RoundedCornerShape(8.dp),
color: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
): Modifier {
if (!LocalShimmerState.current.isLoading) return this
return this
.shimmer()
.background(color = color, shape = shape)
.drawWithContent {
// Do not draw the actual content.
}
}
data class ShimmerState(val isLoading: Boolean)
val LocalShimmerState = compositionLocalOf { ShimmerState(isLoading = false) }
@Composable
fun ShimmerProvider(isLoading: Boolean = true, content: @Composable (Boolean) -> Unit) {
CompositionLocalProvider(
value = LocalShimmerState provides ShimmerState(isLoading = isLoading),
content = { content(isLoading) },
)
}
view raw Shimmerable.kt hosted with ❤ by GitHub

The usage will be simplified to:

// [...] Composable screen code
when (state) {
MainState.Loading -> {
ShimmerProvider {
ItemCard(item = fakeData)
}
}
is MainState.Success -> {
ItemCard(item = state.itemData)
}
}
@Composable
private fun ItemCard(
item: ItemData,
modifier: Modifier = Modifier
) {
Text(
text = item.description,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.shimmerable()
)
// [...] Remaining Composable code
}
view raw MyScreen.kt hosted with ❤ by GitHub

After this changes, we only need two changes in our code:

  1. Wrap the loading state with the shimmer with our CompositionLocal
  2. Add .shimmerable() in all the Composables we want to have this support
Code diff: before and after shimmer support — just one line changed per component
What’s next?

As usual, the code used here is available in full on GitHub. Feel free to use and modify it as you wish. 😊

Conclusion

The shimmer effect is a well known strategy to improve the user experience by creating a smoother transition. Implementing the shimmer effect in Compose is well documented and there are libraries to help us on this task. However, making it easy to implement on existing codebases and keep it scalable might be a challenge. Hopefully, this article help you have some ideas on how to achieve it.

Thank you so much for reading my article! ❤️

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Using annotations in Kotlin has some nuances that are useful to know
READ MORE
blog
One of the latest trends in UI design is blurring the background content behind the foreground elements. This creates a sense of depth, transparency, and focus,…
READ MORE
blog
Now that Android Studio Iguana is out and stable, I wanted to write about…
READ MORE
blog
The suspension capability is the most essential feature upon which all other Kotlin Coroutines…
READ MORE
Menu