ispbox
08/27/2019, 7:24 AMfun View.onClick(action: suspend (View) -> Unit) {
// launch one actor
val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
for (event in channel) action(event)
}
// install a listener to activate this actor
setOnClickListener {
eventActor.offer(it)
}
}
Another cool guy here suggests even better dsl solution:
https://www.hellsoft.se/even-smarter-async-with-coroutine-actors/
But my dream was to have some handy solution like View.awaitOneClick
(maybe, View.processOneClick
?) that incorporates waiting for coroutine to finish and then unblocks button.
My current solution is pretty simple - just disable button before long operation, and enable it after its completion. How do you handle the similar tasks, and what is the real necessity for View.awaitOneClick
?louiscad
08/27/2019, 7:26 AMGlobalScope
in UI. I just make the button disabled at first, and awaitOneClick
handles the rest (enabling it while waiting for the click). Then, I wait for the processing, then call awaitOneClick
again. You can see it in the sample in the develop
branch.louiscad
08/27/2019, 7:27 AMispbox
08/27/2019, 8:40 PMispbox
08/27/2019, 8:44 PMispbox
08/27/2019, 8:51 PMactor
approach from coroutine's author, and dismissed consequent clicks by offering messages via actor`s channel. But it looks not so good for me now, because you have to create these scaffolds everywhere or have one centralized service for that purpose and inject it here and there. I was thinking about silver bullet, but looks like there is no one 🙂 So will try with some helper for button disabling after click and enabling after long-running task.ispbox
08/27/2019, 9:06 PMoverride suspend fun doAction(v: View) {
when (v.id) {
R.id.userRegisterButton -> listener?.onUserRegister()
R.id.accountSignInButton -> listener?.onAccountSignIn()
R.id.skipRegistrationButton -> {
val progressContext = ProgressContext(
action = { listener?.onSkipRegistrationAction() == true },
postAction = { actionResult ->
if (actionResult) {
listener?.onSkipRegistrationPostAction()
}
})
progressService.execute(progressContext)
}
}
}
where ProgressService was implemented based on the following interface:
/**
* Interface for progress service without android platform specifics.
*/
interface ProgressService {
/**
* Executes action in background thread and shows progress.
*/
suspend fun execute(progressContext: ProgressContext)
/**
* Pauses execution of post action.
* Post action usually touches UI that can be not available at the moment.
*/
fun pausePostAction()
/**
* Resumes execution of post action.
*/
fun resumePostAction()
}
louiscad
08/27/2019, 9:49 PMView
implementation is the Ui
implementation, and its contract is the implemented Ui
sub-interface. The Activity
is the controller/presenter in that case, although you can easily extract it away as a (suspending) function.ispbox
08/28/2019, 4:30 AMawaitForClick
instead of click
-> handleClick
? The latter works well in most cases, and if you want any long-lasting operation - you just launch new coroutine on desired dispatcher (and dismiss it on finish)? OK, for annoying popups it is OK to launch one loop and show popup every second, but what's the reason of waiting for simple navigation to other activity?louiscad
08/28/2019, 6:10 AMawaitOneClick()
is not really better than using onClick { ... }
, but if you want to keep the button disabled while something is happening, then it becomes useful.ispbox
08/28/2019, 4:40 PM