Is there a way to have early-return behavior in co...
# compose
c
Is there a way to have early-return behavior in composables from sub-functions without using exceptions? In standard Kotlin, we can use exceptions for early return:
Copy code
// this is not idiomatic, just an extremely simplified example
fun foo(bar: String): Int {
    val int = bar.toIntOrNull()
    requireNotNull(int) { "Early return here" }
    return int
}
Using Arrow, the
either
comprehension enables similar features:
Copy code
fun foo(bar: String): Either<String, Int> {
    val int = bar.toIntOrNull()
    ensureNotNull(int) { "Early return here" }
    return int
}
Using Flows, similar behavior can be emulated using
catch
:
Copy code
fun foo(bar: Stirng): Flow<Result> = flow {
    val int = bar.toIntOrNull()
    requireNotNull(int) { "Early return here" }
    emit(Result.success(int))
}.catch {
    if (it !is IllegalArgumentException) throw it

    emit(Result.failure(it))
}
Which brings it to my question: what is the equivalent with
@Composable
functions?
1
z
It’s not really well-defined what happens when a composable throws. Since a composable can be invoked during recomposition without its caller also in the stack, it’s not clear how such an exception would be propagated.
c
It doesn't have to be implemented with exceptions, for example the Arrow version isn't.
I'm just searching for an idiom to return early when some condition isn't met, to avoid nesting for the rest of the function
p
What happens if you do return@BoxWithConstraints for instance
c
That's exactly the kind of behavior I'm searching for, but you can't do that in a sub-function
p
it is a function like any other. Actually not a function but an action. An action is a function that doesn't return a value, they are just meant to act/modify a context some how
c
Okay let me give you a more complete example
p
Oh it throws an exception right?
c
Copy code
@Composable
fun Foo(user: User) {
    ensure(user is Admin) { ... }

    // We only reach this point if user is an admin
    Text("I'm an admin!")
}

inline fun ensure(condition: Boolean, lazyMessage: () -> String) {
  contract {
    returns() implies condition
  }

  if (!condition)
    // What here to return from Foo?
}
Indeed if it was not a sub-function, I could use
return@Foo
and that would do exactly what I'm searching for
p
I got you Well, you can return a result to the caller function
On another hand I notice that if you return from a Composable lambda then I get an exception. Eg:
Copy code
Box(
    modifier = Modifier
        .background(Color.Red)
        .fillMaxWidth(0.5F)
        .height(redBoxHeightDp)
        .absoluteOffset(y = 100.dp)
        .onGloballyPositioned { coordinates ->
            boxHeight = (coordinates.size.height.toFloat())
        }
){ // BoxScope
    return@Box
    Text(text = "Pablo")
}
I get this.
Copy code
androidx.compose.runtime.ComposeRuntimeError: Compose Runtime internal error. Unexpected or incorrect use of the Compose internal runtime API (Start/end imbalance). Please report to Google or use <https://goo.gle/compose-feedback>
"Start/end imbalance" Got me thinking🤔
c
That sounds like a bug you should report 🤔
p
You think so, ok
c
IIRC the Compose compiler has to insert
compositionGroup.end()
or whatever on all function ends (e.g. all returns), it sounds like it's forgetting to do it in your case? Though I really don't know
p
Interesting
439 Views