Script – Auth Retry with Backoff
Retries, timeouts, and transient error UIs belong in a script, not scattered across nodes.
When to use:
- Auth flows that need retries/backoff/timeout and transient error UIs.
- Anytime you want retry policy to be testable headlessly.
sealed interface AuthOutput {
data class Success(val userId: String) : AuthOutput
data class Error(val reason: String) : AuthOutput
data object RetryTapped : AuthOutput
}
// Headless node just emits outputs; no retry policy inside.
class SignInNode(parentScope: CoroutineScope) :
EffectfulStatefulNode<SignInState, SignInEvent, AuthOutput, SignInEffect>(
parentScope = parentScope,
initialState = SignInState()
) { /* ... */ }
private suspend fun NavFlowScriptScope<AuthOutput, *>.runSignInFlow(
authRepository: AuthRepository,
scope: CoroutineScope
) {
showRoot { SignInNode(scope) }
repeat(3) { attempt ->
val result = withTimeoutOrNull(10_000) { authRepository.signIn() }
if (result != null) {
showRoot { SuccessNode(scope) }
return
}
val delayMs = 1_000L * (attempt + 1)
trace { "auth retry attempt=${attempt + 1}, backoff=${delayMs}ms" }
pushNode { ErrorBannerNode(scope, attempt = attempt + 1, backoffMs = delayMs) }
awaitOutputOfType<AuthOutput.RetryTapped>()
navFlow.pop()
delay(delayMs)
}
showRoot { PermanentFailureNode(scope) }
}
Why it matters:
- Retry/backoff/timeout policy lives in one coroutine, not inside UI nodes.
- Nodes stay headless (emit outputs only), while the script decides when to show error banners and when to give up.
trace+withTimeoutOrNullmake the flow observable and testable viaFlowTestScenario.launchScript.