/rmyhal

Loading… Less is More

Medium

Headline illustration from developer.android.com

Improving Content Loading UX with Jetpack Compose

Does anyone remember ContentLoadingProgressBar from the ancient XML layouts?

ContentLoadingProgressBar implements a ProgressBar that waits a minimum time to be dismissed before showing. Once visible, the progress bar will be visible for a minimum amount of time to avoid “flashes” in the UI.

Avoiding flashes in the UI — is what we came here for. Jetpack Compose, while packed with a variety of UI components, doesn’t include a direct counterpart for the ContentLoadingProgressBar, leaving developers without a built-in option for a seamless, flash-free content loading experience.


To give our visual learners a glimpse of what we want to avoid, here’s a quick GIF of that all-too-familiar jarring loading bar flashing unnecessarily on the screen.

Not the best content loading When Loading Gets Flashy (In a Bad Way)

When content loads quickly, there’s no need to display a loader. Now, imagine having a skeleton shimmer instead of a simple loading indicator — if it only appears for ~150ms, the experience would be even worse than the example shown in the GIF.

contentment to the rescue

contentment offers customizable loading behavior, letting you set delays and minimum display times for your loading indicators. It ensures the indicator is either not shown at all if content loads quickly, or stays visible long enough to prevent flashes in the UI — all through a convenient API.

implementation "me.rmyhal.contentment:contentment:2.0.1" 

The library contains three main components:

  • Contentment {} — this is the entry point to the library and acts as the builder. It’s customizable, allowing to specify the delay time and minimum show time for the loading indicator. The default values, inspired by ContentLoadingProgressBar, are set to 500ms.
  • indicator {} — used to define what loading content should look like. Whether it’s a progress bar, a skeleton shimmer, a Lottie animation, or any other loading content, you place it within this block.
  • content {} — this wrapper function is for your main content.

Check out this quick example to see contentment in action:

@Composable
fun ScreenContent(uiState: UiState) {
  Contentment {
    when (uiState) {
      is Loading -> indicator { CircularProgressIndicator() }
      is Loaded -> content { LoadedContent(uiState) }
      // multiple content's are possible too
      is Failed -> content { FailedContent(uiState) }
    }
  }
}

And here’s how our loading experience looks now:

Good content loading Less is more

No more flashing loading indicators when content loads quickly.

There’s also a handy demo app available that lets you experiment with different delays and configurations to get a better sense of how the loading experience can be customized.


As I mentioned the ContentLoadingProgressBar earlier, the library also includes a Compose implementation of this component. You just need to wrap your progress element with ContentLoadingIndicator {} composable and voilà:

var loading = remember { mutableStateOf(true) }
ContentLoadingIndicator(
  loading = loading,
) {
  // supports any standalone progress component
  CircularProgressIndicator()
}

Thanks for reading! 💚