square / workflow-kotlin

A Swift and Kotlin library for making composable state machines, and UIs driven by those state machines.
https://square.github.io/workflow
Apache License 2.0
1.03k stars 101 forks source link

Running worker on click on button #640

Open gammafoxed opened 2 years ago

gammafoxed commented 2 years ago

Hello everyone! Perhaps the question is not to the address, but I still ask. I am implemented by simple workflow authorization on a server with via login and password. I encountered a problem that I can't run Worker by pressing the button.

override fun render(
    renderProps: Unit,
    renderState: StateSignIn,
    context: RenderContext
): RenderingSignIn {

    context.runningWorker(
        WorkerSignIn(
            renderState.email.getOrElse { "" },
            renderState.password.getOrElse { "" })
    ) { auth ->
        action {
            Log.d("TAG", "render: $auth")
        }
    }

    return RenderingSignIn(
        email = renderState.email.getOrElse { "" },
        password = renderState.password.getOrElse { "" },
        onEmailChanged = { context.actionSink.send(onChangeEmailAction(it)) },
        onPasswordChanged = { context.actionSink.send(onChangePasswordAction(it)) },
        onSignInButtonTap = {  },
        onSignUpButtonTap = { context.actionSink.send(onSignUpButtonTapAction()) }
    )
}

This code works, but that naturally, the network request occurs at the time of the first rendering.

But if I transfer the worker launch code in the callback click on the key, I get an error IllegalstateException: RenderContext cannot be used after render method returns.

    onSignInButtonTap = {
        context.runningWorker(
            WorkerSignIn(
                renderState.email.getOrElse { "" },
                renderState.password.getOrElse { "" })
        ) { auth ->
            action {
                Log.d("TAG", "render: $auth")
            }
        }
    }

Code of my Worker

class WorkerSignIn(
    private val email: String,
    private val password: String
) : Worker<Either<AuthServiceError, Token>>, KoinComponent {
    private val mServiceAuth: ServiceAuth by inject()
    override fun run(): Flow<Either<AuthServiceError, Token>> = flow {
        val res = mServiceAuth.signIn(email, password)
        emit(res)
    }
}

How can I trigger the worker in the time you need? I also do not understand the model of interaction between Workflow and Worker.

rjrjr commented 2 years ago

What you want to do is enter a different state from your click handler. That will lead to a new call to render(), and then you can schedule the worker.

sealed class StateSignIn {
  object LoggedOut : MyState()
  object MakingRequest: MyState()
  class LoggedIn(val auth: String) : MyState()
}

override fun render(
    renderProps: Unit,
    renderState: StateSignIn,
    context: RenderContext
): RenderingSignIn {
  if (renderState == MakingRequest) {
    context.runningWorker(
      WorkerSignIn(
    renderState.email.getOrElse { "" },
    renderState.password.getOrElse { "" })
      ) { auth ->
    action {
      state = LoggedIn(auth)
    }
      }
  }

  return RenderingSignIn(
    email = renderState.email.getOrElse { "" },
    password = renderState.password.getOrElse { "" },
    onEmailChanged = { context.actionSink.send(onChangeEmailAction(it)) },
    onPasswordChanged = { context.actionSink.send(onChangePasswordAction(it)) },
    onSignInButtonTap = context.eventHandler { state = MakingRequest },
    onSignUpButtonTap = { context.actionSink.send(onSignUpButtonTapAction()) }
  )
}
rjrjr commented 2 years ago

My code sample there is gibberish, e.g. it doesn't to pass through the email and password, but I hope it gets the important bit across.

gammafoxed commented 2 years ago

Wow! I understand the logic, but it's definitely not what I expected! This means that there is no easy way to call asynchronous subroutines other than running a full cycle: event -> state change -> rendering. Thanks!