-
I've been exploring Circuit for the past two days, so I apologize if I'm missing something obvious. However, I find the lack of guidance on handling long-running operations in presenters to be the roughest part of my experience so far. The best advice I've encountered is to use proper dependency injection and maintain a well-structured data layer, but none of the samples or documentation explicitly illustrate this approach. Below is an example that highlights my struggle: interface AuthService {
suspend fun signIn(user: String, password: String): Result<Unit>
}
@Parcelize
data object LoginScreen : Screen {
data class State(
val inProgress: Boolean,
val errorMessage: String?,
val eventSink: (Event) -> Unit,
) : CircuitUiState
sealed interface Event : CircuitUiEvent {
data object SignIn : Event
}
}
@Composable
fun LoginPresenter(
authService: AuthService,
navigator: Navigator,
scope: CoroutineScope,
) : LoginScreen.State {
var isSigningIn by rememberRetained { mutableStateOf(false) }
var signInError by rememberRetained { mutableStateOf<String?>(null) }
return LoginScreen.State(
inProgress = isSigningIn,
errorMessage = signInError,
) { event ->
when (event) {
SignIn -> {
isSigningIn = true
scope.launch {
authService.signIn("AzureDiamond", "hunter2")
.onSuccess { navigator.resetRoot(ProfileScreen) }
.onFailure { signInError = it.message }
isSigningIn = false
}
}
}
}
} In the above example, The goal is to keep the sign-in request in-flight as long as the Currently, the best solution I've found is Chris Banes' @Composable
fun rememberRetainedCoroutineScope(): CoroutineScope {
return rememberRetained("coroutine_scope") {
RememberObserverHolder(
value = CoroutineScope(context = Dispatchers.Main + Job()),
onDestroy = CoroutineScope::cancel,
)
}.value
}
internal class RememberObserverHolder<T>(
val value: T,
private val onDestroy: (T) -> Unit,
) : RememberObserver {
override fun onAbandoned() {
onDestroy(value)
}
override fun onForgotten() {
onDestroy(value)
}
override fun onRemembered() = Unit
} The only similar scenario I've found in the samples is in the Am I overlooking some crucial APIs or best practices in Circuit for handling long-running operations tied to a screen's lifecycle? If not, it would be helpful to include clearer documentation or examples for this use case. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Not everything needs to be first party! That solutions works well. We could look at adding a CircuitX artifact for it, open to feature requests.
You are right that we don't really try to detail this in docs, we intentionally aren't trying to teach larger data architecture concepts at the same time as Circuit and every app uses different patterns. We do actually implement a full data layer in the the STAR sample — it has a full repository implementation where the data layer's actions are largely independent of the UI reading from it. That said, it wouldn't hurt for us to maybe add a login sample at some point. You could look at how Tivi does it - look for usages of this class: https://github.com/chrisbanes/tivi/blob/a0c62c2c763c83e3a0ecf79b283224374bb06c4a/data/traktauth/src/commonMain/kotlin/app/tivi/data/traktauth/TraktAuthRepository.kt |
Beta Was this translation helpful? Give feedback.
Not everything needs to be first party! That solutions works well. We could look at adding a CircuitX artifact for it, open to feature requests.
You are right that we don't really try to detail this in docs, we intentionally aren't trying to teach larger data architecture concepts at the same time as Circuit and every app uses different patterns. We do actually implement a full…