Skip to main content
Donovan LaDuke - Developer

Improving Perceived Performance with Delayed Visibility


As Seen In - jetc.dev Newsletter Issue #222

While improving performance is an important metric, an equally important metric is perceived performance. Users will often care less about how fast a piece of software is as compared to how fast the software feels. While there are many approaches to accomplishing this, the one this article will show is preventing unnecessary loading spinners.

Delayed Visibility #

When loading data, the UI is unlikely to know if the data will be loaded from a fast source (like an in-memory cache) or a slow source (like a network request). In this case the naive solution is to just always show a loading indicator. This will work just fine in the "slow source" scenario, but for "fast sources" this will lead to a flickering effect where the indicator is shown briefly before disappearing.

To avoid this flicker, a brief rendering delay can be added to the loading indicator. By choosing a short delay before rendering (like 100ms), the loading spinner will show quick enough to let the user know a "slow source" is loading, but will hide quick enough that the indicator won't render for a "fast source".

There are likely several ways of accomplishing this, but here is an example inspired by this Stack Overflow post on a visibility modifier and this summary on delaying a composable's visibility.

@Composable
fun Modifier.delayedVisibility(delay: Duration): Modifier {
  var show by remember { mutableStateOf(false) }

  LaunchedEffect(Unit) {
    launch {
      delay(delay)
      show = true
    }
  }

  return layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
      if (show) {
        placeable.placeRelative(0, 0)
      }
    }
  }
}

Then in practice, the modifier is used as follows.

@Composable
fun ExampleComposable(isLoading: Boolean) {
  if(isLoading) {
    CircularProgressIndicator(
      modifier = Modifier.delayedVisibility(
        delay = 100.milliseconds,
      )
    )
  } else {
    // TODO Content
  }
}

This usage could be further refined by incorporating an AnimatedVisibility or other animations to improve the transition between states, giving an even more seamless illusion for the end user.

Conclusion #

While the best thing to do for users is actually improve performance, that isn't always feasible and has limits. Improving perceived performance is a great way to smooth over the rough edges that naturally occur when developing apps for mobile devices. Consider using delayed visibility to give users the perception of a faster app experience. Until next time, thanks!

Did you find this content helpful?

Please share this post and be sure to subscribe to the RSS feed to be notified of all future articles!

Want to go above and beyond? Help me out by sending me $1 on Ko-fi. It goes a long way in helping run this site and keeping it advertisement free. Thank you in advance!

Buy me a Coffee on Ko-fi