updating label with progress of http post not working. IllegalStateException - kotlin

I am having trouble with binding a UI component to an observable that gets updated progress from a http post event. I get an IllegalStateException
As I understand it the issue is the bind update is not happening on the UI thread. The answers I have read say that I need to use runAsync and then specify a UI block to update the UI component, but I am at a loss for how to accomplish this.
// View class
private val controller: ZumController by inject()
item("_Upload") {
isMnemonicParsing = true
action {
controller.uploadToServer()
}
}
bottom = label() {
useMaxWidth = true
padding = Insets(5.0, 10.0, 5.0, 10.0)
this.bind(controller.progress)
}
// Controller class
var progress = SimpleStringProperty("Select images to upload")
fun uploadToServer() {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
progress.value = (readBytes.toFloat() / totalBytes.toFloat() * 100).toString()}
.response { _ -> }
}
}
How would I go about making sure the UI is updated during the application thread when I need progress before function call (uploadToServer()) returns? Sorry if this has already been answered, I still don't get exactly what is happening here.

I've solved my problem with the following changes. I pass the FXTask to function uploadToServer(). There I updateMessage() with the progress callback for the http POST request. I can't say its the best way but it works. feel free to update this answer with more clear and concise code
item("_Upload") {
isMnemonicParsing = true
action {
runAsync {
controller.uploadToServer(this)
} ui {
}
}
}
fun uploadToServer(task: FXTask<*>) {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
val perComplete = readBytes.toFloat() / totalBytes.toFloat() * 100
task.updateMessage("Uploading $p %.2f".format(perComplete).plus("%"))
}
.response { _ -> }
}
}

TornadoFX has a built in TaskStatus object which has properties for the progress of the task. You can bind one or more of the properties in the TaskStatus object to any UI element, and simply call updateProgress from within your controller function. You don't even need to pass in the TaskStatus object, as the default instance will be used if you don't.
There are a few test appa within the framework that does this:
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/AsyncProgressApp.kt
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/TaskStatusTest.kt
That said, a quick and dirty solution for updating the UI from any other thread is simply wrapping the UI manipulation code inside runLater {}. This will work equally well for just updating a label for example.

Related

Why doesn't App crash when I use collect a flow from the UI directly from launch in Jetpack Compose?

I have read the article. I know the following content just like Image B.
Warning: Never collect a flow from the UI directly from launch or the launchIn extension function if the UI needs to be updated. These functions process events even when the view is not visible. This behavior can lead to app crashes. To avoid that, use the repeatOnLifecycle API as shown above.
But the Code A can work well without wrapped with repeatOnLifecycle, why?
Code A
#Composable
fun Greeting(handleMeter: HandleMeter,lifecycleScope: LifecycleCoroutineScope) {
Column(
modifier = Modifier.fillMaxSize()
) {
var my by remember { mutableStateOf(5)}
Text(text = "OK ${my}")
var dataInfo = remember { handleMeter.uiState }
lifecycleScope.launch {
dataInfo.collect { my=dataInfo.value }
}
}
class HandleMeter: ViewModel() {
val uiState = MutableStateFlow<Int>(0)
...
}
Image B
Code A will not work in real life. If you need to run some non-UI code in a composable function, use callbacks (like onClick) or LaunchedEffect (or other side effects).
LaunchedEffect {
dataInfo.collect {my=dataInfo.value}
}
Side effects are bound to composables, there is no need to specify the owner of their lifecycle directly.
Also, you can easily convert any flow to state:
val my = handleMeter.uiState.collectAsState()

How to i chain two parts of an animation together in jetpack compose so the the offset increases then decreases?

Ive recently got into doing animations using jet pack compose and am wondering how you can make it so that when you increase a value in an offset, once the animation reaches that value it then changes the value to another value. So like update transition but instead of at the same time, one after the other.
Actually #RaBaKa's answer is partially correct, but it's missing information about how the animation should be run.
It should be done as a side effect. For example, you can use LaunchedEffect: it is already running in a coroutine scope. It is perfectly normal to run one animation after another - as soon as the first suspend function finishes, the second will be started:
val value = remember { Animatable(0f) }
LaunchedEffect(Unit) {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
Text(value.value.toString())
If you want to do this in response to some action, such as pressing a button, you need to run the coroutine yourself. The main thing is to run the animations in the same coroutine so that they are chained.
val value = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
}) {
}
Text(value.value.toString())
The correct answer is to use Kotlin coroutines. I managed to get it working fine. You have to use coroutines in order to launch the animations in the correct sequence like this:
animationRoutine.launch {
coroutineScope {
launch {
animate(
startingValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
launch {
animate(
initialValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
}
Each of launch scope launches everything in a non blocking way if you tell it to allowing you to run multiple animations at once at a lower level and to sequence the animations you add another coroutine for the next part of the animation.
Maybe you can use Animatable
val value = remember { Animatable(0f) } //Initial Value
Then in compose you can just use
value.animateTo(20f)
then
value.animateTo(10f)
For more information visit the official documentation

Receiving ResponseAlreadySentException after attempting to respond to ApplicationCall objects passed through StateFlow

I am testing an event driven architecture in KTOR. My Core logic is held in a class that reacts to different Event types being emitted by a StateFlow. EventGenerators push Events into the StateFlow which are picked up by the Core.
However, when the Core attempts to respond to an ApplicationCall embedded in one of my Events I receive an ResponseAlreadySentException and I'm not sure why this would be the case. This does not happen if I bypass the StateFlow and call the Core class directly from the EventGenerator. I am not responding to ApplicationCalls anywhere else in my code, and have checked with breakpoints that the only .respond line is not being hit multiple times.
MyStateFlow class:
class MyStateFlow {
val state: StateFlow<CoreEvent>
get() = _state
private val _state = MutableStateFlow<CoreEvent>(CoreEvent.NothingEvent)
suspend fun update(event: CoreEvent) {
_state.value = event
}
}
My Core class:
class Core(
myStateFlow: MyStateFlow,
coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.IO
) {
init {
CoroutineScope(coroutineContext).launch {
myStateFlow.state.collect {
onEvent(it)
}
}
}
suspend fun onEvent(event: CoreEvent) {
when(event) {
is FooEvent {
event.call.respond(HttpStatusCode.OK, "bar")
}
...
}
}
}
One of my EventGenerators is a Route in my KTOR Application class:
get("/foo") {
myStateFlow.update(CoreEvent.FooEvent(call))
}
However, hitting /f00 in my browser returns either an ResponseAlreadySentException or an java.lang.UnsupportedOperationException with message: "Headers can no longer be set because response was already completed". The error response can flip between the two while I'm tinkering with different attempted solutions, but they seem to be saying the same thing: The call has already been responded to before I attempt to call call.respond(...).
If I change my Route instead to call the Core.onEvent() directly, hitting /foo returns "bar" in my browser as is the intended behaviour:
get("/foo") {
core.onEvent(CoreEvent.FooEvent(call))
}
For completeness, my dependency versions are:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "io.ktor:ktor-server-netty:1.4.1"
Thank you in advanced for any insight you can offer.

How to modify variables outside of their scope in kotlin?

I understand that in Kotlin there is no such thing as "Non-local variables" or "Global Variables" I am looking for a way to modify variables in another "Scope" in Kotlin by using the function below:
class Listres(){
var listsize = 0
fun gatherlistresult(){
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
}
}
}
the value of listsize is always 0 (logging the result from inside of the .addOnSuccessListener scope returns 8) so clearly the listsize variable isn't being modified. I have seen many different posts about this topic on other sites , but none fit my usecase.
I simply want to modify listsize inside of the .addOnSuccessListener callback
This method will always be returned 0 as the addOnSuccessListener() listener will be invoked after the method execution completed. The addOnSuccessListener() is a callback method for asynchronous operation and you will get the value if it gives success only.
You can get the value by changing the code as below:
class Demo {
fun registerListResult() {
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
processResult(listsize)
}
listallinfo.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
fun processResult(listsize: Int) {
print(listResult+"") // you will get the 8 here as you said
}
}
What you're looking for is a way to bridge some asynchronous processing into a synchronous context. If possible it's usually better (in my opinion) to stick to one model (sync or async) throughout your code base.
That being said, sometimes these circumstances are out of our control. One approach I've used in similar situations involves introducing a BlockingQueue as a data pipe to transfer data from the async context to the sync context. In your case, that might look something like this:
class Demo {
var listSize = 0
fun registerListResult() {
val listAll = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
val dataQueue = ArrayBlockingQueue<Int>(1)
listAll.addOnSuccessListener { dataQueue.put(it.items.size) }
listSize = dataQueue.take()
}
}
The key points are:
there is a blocking variant of the Queue interface that will be used to pipe data from the async context (listener) into the sync context (calling code)
data is put() on the queue within the OnSuccessListener
the calling code invokes the queue's take() method, which will cause that thread to block until a value is available
If that doesn't work for you, hopefully it will at least inspire some new thoughts!

Add onClick to Button in Ktor with Kotlinx

I am currently trying out Ktor. But it already fails on registering a onClickListener to the button.
I am just wondering because the onClick extends from String. If I click on the onClick the Code I get is this one:
var CommonAttributeGroupFacade.onClick : String
get() = attributeStringString.get(this, "onclick")
set(newValue) {attributeStringString.set(this, "onclick", newValue)}
So I think onClick has a getter and setter but not more. I installed location and FreeMarker so this is my route:
get<CharityRoute> {
val id = it.id
call.respondHtml {
body {
h1 { +"${it.id}" }
ul {
for (n in it.list) {
li { +"$n" }
}
}
button {
text("Run function")
onClick = "Hello"
}
}
}
}
Maybe someone has more experience with Ktor and especially with Kotlinx.
Problem
It depends which module you are working in.
On the server-side (JVM) you can not write Javascript-Code in Kotlin and assign it to a function like that.
To find a solution, I have to further know, what exactly it is you want to achieve.
Here are two of many possibilities, what you could do:
Solution 1
You can assign a javascript-code as string. You did just that by assigning the code "Hello".
Maybe you want to call a function, which was globally made available in the window object?
Solution 2
In client-side JS code, you can import kotlinx.html.js.onClickFunction and simply write
button{
onClickFunction = { println("clicked") }
}
to execute code written in Kotlin.