https://kotlinlang.org logo
#compose
Title
# compose
s

ste

03/09/2022, 12:55 PM
I have to execute async operations on button clicks. May be the following extension considered a bad practice?
Copy code
fun Modifier.clickable(
    context: CoroutineContext,
    enabled: Boolean = true,
    onClick: suspend CoroutineScope.() -> Unit
) = composed {
    val coroutineScope = rememberCoroutineScope()
    clickable(enabled = enabled) { coroutineScope.launch(context = context, block = onClick) }
}
e

efemoney

03/09/2022, 4:42 PM
I do not think so (personally). The name clashes with the library extension but that's minor.
s

Stylianos Gakis

03/09/2022, 4:59 PM
This will however launch a new coroutine on every click without cancelling the old one. This is most likely not what you want, so you’ve been warned.
2
e

Eric Chee

03/09/2022, 5:34 PM
Agreed with @Stylianos Gakis. i too would highly recommend you avoid making suspend calls on the UI and let a separate thread handle the flow of UI actions with its own scope. See my message here as an example https://kotlinlang.slack.com/archives/CJLTWPH7S/p1645760062537329?thread_ts=1645758836.851679&cid=CJLTWPH7S
s

ste

03/09/2022, 7:48 PM
Thanks @Stylianos Gakis, I didn't think of that. @Eric Chee Well, honestly, I'm not a
ViewModel
user... it looks like boilerplate code to me - is it just a matter of where to put the code? What are the benefits of using the
ViewModel
scope?
e

Eric Chee

03/09/2022, 9:27 PM
@ste its less about the ViewModel and more about just allowing your UI to call non-suspendable functions and let the external scopes handle the asynchronous calls. If you’re not using ViewModel, then i would suggest using the fragment/activities lifecycleScope. where you can call .launch() from. therefore its a single reusable coroutine scope that is tied to lifecycle of the activity but if its a side effect, then you can also check the different options here too
but if you really want your Modifier extension, i would accept a coroutineScope as a parameter rather than creating a new coroutine on each click.
Copy code
fun Modifier.myClickable(
   scope: CoroutineScope,
   onClick: suspend CoroutineScope.() -> {}
) = scope.launch{ onClick() }
blob thinking upside down 1
2
s

ste

03/10/2022, 9:27 AM
Thanks for the clarification!
s

Stylianos Gakis

03/10/2022, 9:31 AM
Wait, this function also doesn’t do what you want. It’s an extension on Modifier, yet it doesn’t make use of it. Plus it just launches a new coroutine on creation, meaning if this is called from a Composable it might like fire off a ton of coroutines in composition? Haven’t tested it but you probably really do not want to do this Eric.
1
Not to mention onClick is a suspending function, which also has a CoroutineScope receiver. Please read this https://elizarov.medium.com/coroutine-context-and-scope-c8b255d59055
s

ste

03/10/2022, 9:46 AM
I think he meant the following:
Copy code
fun Modifier.clickable(
    scope: CoroutineScope,
    onClick: suspend CoroutineScope.() -> Unit
) = clickable(enabled = enabled) { scope.launch(block = onClick) }
e

efemoney

03/10/2022, 9:48 AM
If so there is no difference btw this and OP 🙂
s

ste

03/10/2022, 9:53 AM
Uhm, so they have the same effect of:
Copy code
val coroutineScope = rememberCoroutineScope()

// ...

Spacer(Modifier.clickable { coroutineScope.launch { ... } })
?
I mean, I thought this was the correct way to execute a suspend function when the user taps on something