Events, Data and State flow
As you may already know, Kunafa relies heavily on observables and events to handle data manipulation and state representation. I'll touch briefly on each one of these topics and provide an simple example that ties them together at the end.
Events
Basic events:
- onChange, onClick, onHover ... etc
you'll find those quite a lot in forms in general.
textInput { text = formViewModel.username onChange = { formViewModel.username = text } }
Not so basic:
- observables
you'll use them to make your own types of events. For further reading refer to this and this
override fun onViewCreated(lifeCycleOwner) {
viewModel.getFormData()
viewModel.submitState.observe {
if( it == BasicUiState.Loaded ) {
formViewModel.username = ""
formViewModel.email = ""
}
}
}
Data:
It can be anything, in our example we deal with two strings that we populate the form input with and edit then submit to send it back to the server.
State Flow:
We start with getting the data from the server in onViewCreated
and then we edit the data and submit that to the server after clicking submit.
class ExampleForm(private val viewModel: FormViewModel): Component() {
override fun onViewCreated(lifeCycleOwner) {
viewModel.getFormData()
}
override fun View?.getView() = verticalLayout {
form {
label("Username", isRequired = true)
textInput {
text = formViewModel.username
onChange = {
formViewModel.username = text
}
}
label("Email", isRequired = true)
textInput {
text = formViewModel.email
onChange = {
formViewModel.email = text
}
}
button {
text = "submit"
onClick = {
viewModel.submitData()
}
}
}
}
}
class FormViewModel() {
var username = ""
var email = ""
val uiState = Observable<BasicUiState>()
val submitState = Observable<BasicUiState>()
fun getFormData() {
basicNetworkCall(uiState) {
val someData = someGlobalClient.getDataFromServer()
username = someData.username
email = someData.email
}
}
fun submitData() {
basicNetworkCall(submitState) {
someGlobalClient.submitData(username, email)
}
}
}
Basic network call
we use basicNetworkCall
to make network calls and tie a uiState
to the state of the network call whether its failure or success. this uiState
enum can be observed in the component to trigger an event.
basicNetworkCall
wraps networkCall
to make the network call
fun basicNetworkCall(uiState: Observable<BasicUiState>, call: suspend CoroutineScope.() -> Unit) {
networkCall(
before = { uiState.value = BasicUiState.Loading },
onConnectionError = { uiState.value = BasicUiState.Error }
) {
try {
call()
uiState.value = BasicUiState.Loaded
} catch (t: Throwable) {
uiState.value = BasicUiState.Error
t.printStackTrace()
}
}
}
Network call
networkCall
handles the actual network call delegated by basicNetworkCall
, you have functions that can be provided by the user if they care about fine grained error handling, otherwise default functions will be used.
fun networkCall(
before: () -> Unit = {},
final: suspend CoroutineScope.() -> Unit = { },
onConnectionError: suspend CoroutineScope.() -> Unit = { },
onUnknownError: suspend CoroutineScope.() -> Unit = onConnectionError,
onUnauthorized: suspend CoroutineScope.() -> Unit = onConnectionError,
onInvalidRequest: suspend CoroutineScope.() -> Unit = onConnectionError,
onUserDisabled: suspend CoroutineScope.() -> Unit = { logoutUser() },
call: suspend CoroutineScope.() -> Unit
): Job {
before()
return GlobalScope.launch(Dispatchers.Default) {
try {
withTimeout(30_000) {
call()
}
} catch (e: ConnectionErrorException) {
withContext(Dispatchers.Main) { onConnectionError() }
} catch (e: UnknownErrorException) {
withContext(Dispatchers.Main) { onUnknownError() }
} catch (e: UnauthorizedException) {
withContext(Dispatchers.Main) { onUnauthorized() }
} catch (e: DisabledUserException) {
withContext(Dispatchers.Main) { onUserDisabled() }
} catch (e: InvalidRequestException) {
withContext(Dispatchers.Main) { onInvalidRequest() }
} catch (e: TimeoutCancellationException) {
withContext(Dispatchers.Main) { onConnectionError() }
} catch (e: Throwable) {
withContext(Dispatchers.Main) { onInvalidRequest() }
} finally {
withContext(Dispatchers.Main) { final() }
}
}
}
No Comments