"Retry When Ready" — A Smarter Way to Handle Network Failures in Jetpack Compose
"Please try again."
These are the words users dread to see — especially when they’ve done nothing wrong except have poor internet.
In this post, I’ll show you how to build a reusable, respectful retry system in Jetpack Compose that:
- Remembers failed API requests
- Waits for internet to return
- Gently asks the user: “Would you like to retry?”
- Works not just for login — but any network-dependent call
Let’s build this like a good story: with empathy, architecture, and a clean separation of concerns.
The Problem: Network Failures Break Trust
Imagine this:
-
A user opens your app and enters their login credentials.
-
They tap Log In.
-
Nothing happens — or worse, an error appears:
“Login failed. Please try again.”
Why?
No internet. Maybe they walked through a tunnel. Maybe their Wi-Fi dropped.
Now imagine if the app said:
“You're back online. Would you like to retry logging in?”
Boom. That's thoughtful UX.
The Goal
We want to create a centralized retry mechanism that:
-
Works for any failed API
-
Remembers the user's action
-
Waits for the network
-
Gives control back to the user via a dialog
-
Is reusable across all ViewModels and screens
Let’s do it.
The Architecture
We already had a basic UiEvent
system in our app. We enhanced it like this:
sealed class UiEvent {
data class ShowAlert(val title: String, val message: String) : UiEvent()
object ShowLoader : UiEvent()
object HideLoader : UiEvent()
data class ShowRetryDialog(
val title: String,
val message: String,
val retryAction: RetryAction
) : UiEvent()
enum class RetryAction {
LOGIN,
FETCH_PROFILE,
SEND_OTP
// add more as needed
}
}
Then, we used polymorphism to make this generic and reusable.
Smart ViewModels
In LoginViewModel
private var lastLoginRequest: LoginRequest? = null
private var isRetryPending = false
fun loginUser(request: LoginRequest) {
lastLoginRequest = request
isRetryPending = false
// Trigger login API
}
fun markLoginRetryNeeded() {
isRetryPending = true
}
fun promptLoginRetryOnNetworkRestored() {
if (isRetryPending) {
showRetryDialog(
title = "Internet Restored",
message = "Do you want to retry login?",
action = UiEvent.RetryAction.LOGIN
)
}
}
override fun handleRetryAction(action: UiEvent.RetryAction) {
when (action) {
UiEvent.RetryAction.LOGIN -> retryLogin()
}
}
private fun retryLogin() {
lastLoginRequest?.let { loginUser(it) }
}
In the Base AppUiViewModel
open fun handleRetryAction(action: UiEvent.RetryAction) {
// No-op by default
}
Now, any child ViewModel can override only what it needs.
The Magic: UiEventHandler
This composable is used by all screens to handle events like loaders, alerts, and now… retries!
retryDialog?.let { dialog ->
AlertDialog(
onDismissRequest = { retryDialog = null },
title = { Text(dialog.title) },
text = { Text(dialog.message) },
confirmButton = {
TextButton(onClick = {
appUiViewModel.handleRetryAction(dialog.retryAction)
retryDialog = null
}) {
Text("Retry")
}
},
dismissButton = {
TextButton(onClick = {
retryDialog = null
}) {
Text("Cancel")
}
}
)
}
It doesn't care what the retry action is — it simply delegates.
Scaling the Pattern
Want to use this for more than login?
Here’s how:
1 Add a new enum entry:
enum class RetryAction {
LOGIN,
FETCH_PROFILE,
SEND_OTP
}
2 In your ProfileViewModel
:
override fun handleRetryAction(action: UiEvent.RetryAction) {
when (action) {
UiEvent.RetryAction.FETCH_PROFILE -> fetchProfile(lastRequest)
}
}
3 In your repo:
fun fetchProfile(request: ProfileRequest) {
lastRequest = request
// API call
}
Now, any ViewModel can opt in. No code duplication. No tight coupling.
Real-World Experience
This approach has made our app:
-
More user-friendly
-
More resilient
-
More scalable
We no longer fear network drops. Instead, we anticipate them. We recover from them.
And more importantly — we empower the user to decide.
Want More Like This?
If you found this helpful:
Clap 👏 a few times
Follow me on Medium
Subscribe for email alerts to get real-world Compose & architecture tips delivered to your inbox
Let’s build smarter, friendlier apps — one retry at a time.
Comments
Post a Comment