Skip to content

Recipe: Retry a failed load

Problem: a load failed and the user taps “Retry”. You need to re-run the load and show the loading state again.

Drive retries with a counter held in rememberRetained. Use it as a key to produceRetainedState; incrementing it re-runs the producer.

@Composable
override fun present(): ArticleState {
  var retryCount by rememberRetained { mutableIntStateOf(0) }

  val result by produceRetainedState<ArticleResult>(ArticleResult.Loading, screen.id, retryCount) {
    value = ArticleResult.Loading
    value = articleRepository.load(screen.id)   // suspend one-shot that returns success/failure
  }

  return when (val current = result) {
    ArticleResult.Loading -> ArticleState.Loading
    is ArticleResult.Success -> ArticleState.Loaded(current.article)
    is ArticleResult.Failure ->
      ArticleState.Error(current.message) { event ->
        when (event) {
          ErrorEvent.Retry -> retryCount++   // changes the key → producer re-runs
        }
      }
  }
}

Because retryCount is one of the producer’s keys, incrementing it cancels any in-flight work, resets to Loading, and re-runs the load.

If your data source is a Flow rather than a one-shot suspend call, expose a refresh() on the repository instead and call it from the event — the Flow re-emits and your produceRetainedState collector (observing a Flow) picks it up without a counter.

See also: Show loading, loaded, and error states · Pull to refresh