The Code A and Image A is from the artical LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case).
The author told me "Trigger the event by setting a new Event as a new value", I think it should be "Trigger the event by setting a new Event as any value", right?
For example,
Step 1: The user clicks the button in master Activity with the code userClicksOnButton("StartDetails") , the Details Activity will start.
Step 2: The user presses back, coming back to the master activity
Step 3: The user clicks the button in master Activity with the code userClicksOnButton("StartDetails") again, the Details Activity will start again.
Is it right?
Code A
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})
Image A
You're having two different constraints on the data.
The LiveData emits every update to active observers, but the event itself is stateful. Thus a new event is required as a new LiveData value. The itemId could be any value, but the comment doesn't refer to it.
Trigger the event by setting a new Event as [a new] value
Related
I am building simple application that will track a certain tabletop game of 2 players.
I have a view called MatchView
class MatchView : View() {
// data
private var currentRound = SimpleIntegerProperty(0)
private var currentTurn = SimpleIntegerProperty(0)
override val root = borderpane {
center = label(currentRound.stringBinding{ "Round %d".format(it) })
// other layout-related stuff
subscribe<EndTurnEvent> {
endPlayerTurn()
}
}
private fun endPlayerTurn() {
// increment turn
currentTurn.plus(1)
currentRound.plus(currentTurn.value % 2)
}
}
that is subscribed to EndTurnEvent - event emitted by one of the fragments used by view.
The called method is supposed to increment value of currentTurn and if needed currentRound (round increments after second player ends their turn)
However neither the value of currentRound nor the one of currentTurn are getting increased when i call .plus() method.
I have tried editting values differently :
private fun endPlayerTurn() {
// increment turn
currentTurn.value = currentTurn.value + 1
currentRound.value = currentTurn.value % 2
}
But this throws me java.lang.IllegalStateException: Not on FX application thread
I am aware that putting properties into views is anti-pattern, but since I just want to keep track of 2 properties, I thought I could put them directly into View
Platform.runLater(() -> {
// Update GUI from another Thread here
});
I want to to observe a row in room database. it change after some period. but when we stop button click it need to be stop observing form database and when click start button it start observing again.
My current code is
To create Observer
private lateinit var recordObserver: Observer<Ride>
recordObserver= Observer<Ride> { rides ->
if (rides != null)
updateData(rides)
else
setDataToZero()
}
when(isState){
Constants.isrunning->{//need to start observer}
Constants.Stop->{//need to stop observer}
}
In order to start/stop observing LiveData you should use observe() / removeObserver() methods. As simple as that. If you have access to LifecycleOwner (Fragment, Activity) use fun observe(), if not - use fun observeForever().
Your code will look like this:
val liveData = database.observeRides() // get your live data
when(isState){
Constants.isrunning -> {
liveData.observe(this, recordObserver)
}
Constants.Stop -> {
liveData.removeObserver(recordObserver)
}
}
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.
I am trying to make a class that would take incoming user events, process them and then pass the result to whoever subscribed to it:
class EventProcessor
{
val flux: Flux<Result>
fun onUserEvent1(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun onUserEvent2(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun process(e : Event): Result
{
...
}
}
Then the client code can subscribe to EventProcessor::flux and get notified each time a user event has been successfully processed.
However, I do not know how to do this. I tried to construct the flux with the Flux::generate function like this:
class EventProcessor
{
private var sink: SynchronousSink<Result>? = null
val flux: Flux<Result> = Flux.generate{ sink = it }
fun onUserEvent1(e : Event)
{
val result = process(e)
sink?.next(result)
}
fun onUserEvent2(e : Event)
{
val result = process(e)
sink?.next(result)
}
....
}
But this does not work, since I am supposed to immediately call next on the SynchronousSink<Result> passed to me in Flux::generate. I cannot store the sink as in the example:
reactor.core.Exceptions$ErrorCallbackNotImplemented:
java.lang.IllegalStateException: The generator didn't call any of the
SynchronousSink method
I was also thinking about the Flux::merge and Flux::concat methods, but these are static and they create a new Flux. I just want to push things into the existing flux, such that whoever holds it, gets notified.
Based on my limited understanding of the reactive types, this is supposed to be a common use case. Yet I find it very difficult to actually implement it. This brings me to a suspicion that I am missing something crucial or that I am using the library in an odd way, in which it was not intended to be used. If this is the case, any advice is warmly welcome.
I am using Resource Change Listener to track the changes done to my project. This listener is invoked if I delete, or create or save any changes to a file, in the project. I have the ResourceDelta object. With this, how can I find if the file is being created or is going to be deleted.
Below is my code:
In the activator class of my plugin I have:
IResourceChangeListener listener = new MyResourceChangeListener();
this.workspace.addResourceChangeListener(this.listener);
In the MyResourceChangeListener class I have:
System.out.println(event.getBuildKind());
System.out.println(event.getSource());
System.out.println(event.getType());
if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
System.out.println("this is post change event");
final IResourceDelta delta = event.getDelta();
System.out.println(delta.getFlags());
System.out.println(delta.getKind());
System.out.println(delta.getFlags());
if (delta.getKind() == IResourceDelta.ADDED) {
System.out.println("this is ADD event");
}
if (delta.getKind() == IResourceDelta.CHANGED) {
System.out.println("this is CHANGED event");
}
if ((delta.getFlags() & IResourceDelta.CONTENT) == 0) {
System.out.println("this is CONTENT event");
}
}
Output is always as below, either i create a class, delete a class, or make changes and save a class :
0
org.eclipse.core.internal.resources.Workspace#5f9f1f42
1
this is post change event
0
4
0
this is CHANGED event
this is CONTENT event
How can I differentiate between save, delete or create events.
Test the bitmap returned by IResourceChangeEvent.getType() for PRE_DELETE bit. Register the listener specifically for that event type with IWorkspace.addResourceChangeListener(IResourceChangeListener, int)
This article might be useful, too.