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

Sergey Y.

09/03/2020, 10:45 AM
1413667: Prototype: Coroutine scrolling APIs | https://android-review.googlesource.com/c/platform/frameworks/support/+/1413667 very interesting... 🤔
👌 1
An experiment with concurrency?
a

Adam Powell

09/03/2020, 1:18 PM
Yes, we're doing a lot of preliminary work in this area to move things that happen over a period of time to use coroutines as that API surface
❤️ 9
It has a number of advantages in standardizing scoping, cancellation and completion "callbacks" for things like input and animation
And makes combining those things much smoother
l

louiscad

09/04/2020, 10:09 AM
I'd like to see it for input events like clicks (I use suspending extensions for
android.view.View
) and other changes like text changes in a text input field. Have you explored this to provide an alternative to the callbacks passing that compose currently has?
a

Adam Powell

09/04/2020, 1:56 PM
Yes, especially for touch input handling modifiers. For some cases it just adds complexity; an onClick is an instantaneous event that doesn't benefit from suspend. A more detailed handler that works with the lifecycle of touch events and can run a conditional fling animation after a series of motion events ends as part of the same cancellable job to support interruption? Now that's deeply useful.
This combines well with the
withFrameNanos
function as the lowest level suspending animation primitive
And is why the composition scopes for both
rememberCoroutineScope
and
launchInComposition
configure the dispatcher and frame clock to assist in supporting all of this such that you can act on the same frame input arrives before composition occurs in response
l

louiscad

09/04/2020, 2:01 PM
@Adam Powell
an onClick is an instantaneous event that doesn't benefit from suspend
Right now, I benefit from it because calling
awaitOneClick()
enables the target
View
to be clickable, which reflects that state visually, and once clicked, it becomes disabled again (unless enabled back), preventing the infamous double click double processing issue, and signaling the user that his action has been taken into account. That's why I disagree with your statement that I just quoted 🙂
a

Adam Powell

09/04/2020, 2:04 PM
Such constructs may be useful at the application layer but it's crufty for a lower layer button API to expose. If you wanted such a thing you can create a button composable that accepts a state object with such a method, and your button could call a material button composable with appropriate enabled and onClick parameters
1
Reversing that layering and treating an awaitClick as the primitive gets much more complicated
l

louiscad

09/04/2020, 2:05 PM
Reversing that layering and treating an awaitClick as the primitive gets much more complicated
Agreed
a

Adam Powell

09/04/2020, 2:06 PM
Though a slightly different shape in suspending gesture recognition to detect clicks more generally? Probably!
l

louiscad

09/04/2020, 2:14 PM
If you wanted such a thing you can create a button composable that accepts a state object with such a method, and your button could call a material button composable with appropriate enabled and onClick parameters
Could you write such an example in a gist?
a

Adam Powell

09/04/2020, 2:15 PM
Sure, after I finish making coffee 🙂
👍 1
l

louiscad

09/04/2020, 2:15 PM
Though a slightly different shape in suspending gesture recognition to detect clicks more generally? Probably!
You mean it would be possible to replace
onClick
and possible suspending adapter code to raw gesture detection under the hood, made simple with coroutines enabled API, with an even higher level API to detect just clicks?
a

Adam Powell

09/04/2020, 2:27 PM
yes, though that likely would not be the API that Button composables would expose to their callers
l

louiscad

09/04/2020, 2:44 PM
But one could perfectly wrap such a button with a composable that will handle the gestures and have that API?
a

Adam Powell

09/04/2020, 2:48 PM
it would look different depending on how many variants of Button we wanted to expose, and there is a lot of pressure to keep that simple and easy to understand without an explosion of overloaded options.
Likely we will lean toward, if you want a complicated button you will need to build your own out of the constituent parts
with one of those constituent parts being raw (yet likely suspending) gesture detection modifiers
l

louiscad

09/04/2020, 2:52 PM
Looking forward your post-coffee snippet to have a better idea of the options, how the end-API could look like and if there'll be a need to have custom buttons to be able to suspend until they are clicked.
a

Adam Powell

09/04/2020, 2:59 PM
I haven't run or tested this but it shows the idea
(with one quick edit there for a bug; please refresh)
as you can see you don't necessarily need a custom button to suspending-await clicks, you just need this little object to hold the awaiters for you
and that object can also provide the
enabled
parameter to those buttons
(one more update to the gist since I wasn't using the derived enabled state)
anyway you certainly can do this but I suspect it adds more complexity than you need. I enjoy coroutines quite a bit but considering how the rest of compose works via snapshot state updates, unless I was combining things in a very situational way I can't see this being a simpler way to deal with clicks in the general case.
l

louiscad

09/05/2020, 11:38 AM
I see. Thank you very much for that gist and your thoughts!
👍 1
Side feedback: I lookes at the documentation of
Button
in
androidx.compose.material
, and I saw that
enabled
makes the button non clickable… why couple the enabled state to the fact that the code can know a button has been clicked? I mean… I understand that it could be for convenience, but on the other hand, it prevents you to support the following use case aimed at improving UX and user understanding: The user sees a disabled button and would like to know why that option is not available. Because the UX designers are so user-friendly, an informational text is shown when the button is clicked telling why the button is disabled (e.g. the inputted data is not valid, or the device doesn't have required hardware…). Right now, it seems it'd need a wrapping click detector to support that use case, making the code significantly more complex when you wanted to improve the UX a little. Have you though about this in the team, and do you already have an idea of how the API could be designed to support this use case without making the simpler one more complex or more error-prone?
a

Adam Powell

09/05/2020, 2:12 PM
Yes, we've discussed that one quite a bit. I'm not sure the current direction on it is final but here's how we got here:
The initial API started with the click handler being nullable. The absence of the onClick parameter determined enabled status.
We didn't like what this does to calling code. It means a lot of this:
Copy code
Button(
  if (enabled) { { doThing() } } else null
)
I don't even remember if that expression is valid; it forces you into the case of conditional braces conflicting with the inline lambda syntax
The alternative is forward declaring your callbacks as vals and separating them from site of use
So that's kind of gross. Combine that with enabled state having a visual effect and we quickly recommended using a separate enabled Boolean param that defaults to true so that you can treat it separate from the reaction you will perform if the user acts on it.
Dispatching the click when the button is disabled then experiences a related problem: developers have to write
if (enabled)
logic in their handlers. Most will forget and won't expect they have to. Who writes tests to click their disabled buttons and confirm nothing happened?
So yes, in the event that you do want to respond to clicks on disabled buttons, you can conditionally apply a click modifier to the button yourself
l

louiscad

09/05/2020, 3:07 PM
There's a click modifier to append to a
Modifier
? If both
onClick
and a click modifier are applied, do both get called on click?
a

Adam Powell

09/05/2020, 3:16 PM
yes,
Modifier.clickable {}
. Innermost click modifier wins; it's the same as if you put a button within a larger clickable card layout.
👍 1