Waiting until boolean value is changed without blocking main JavaFX thread? - java

I have been reading for hours about different multi-threading techniques for JavaFX and cannot seem to find what I'm looking for. The application being worked on is the "Messenger" which part of a bigger application to provide a trading marketplace for a game.
A breakdown of the process I am having trouble with:
A window with a 'Contact Seller' button is displayed
The user clicks 'Contact Seller', and the Messenger window should be displayed
Using the seller's name from the main window, the Messenger should check if a chat already exists with that name
If the chat already exists, get the index of that chat in the Messenger's ListView, and select the chat so the Messenger's text area is populated with the corresponding messages
If the chat doesn't exist, create one
The issue:
Chats are stored server-side
Messages between clients are stored in a message 'cache' on the server when being processed from one user to the other (they are sorted to their respective chats, inserted into the chats, and pushed to the database upon client disconnection)
Messages received on the client are stored locally in the respective chat
When the Messenger is opened, it requests a list of chats for the signed-in user
The server sends an ArrayList of chats, and upon receiving the client builds the Messenger's ListView with these objects
Now when I need to open the Messenger in order to 'Contact Seller', I need to make sure the sync with the server is complete. Without doing so, I won't be able to properly check if a chat already exists with that name since I won't have the most up-to-date list.
The 'RequestWorker' thread that handles incoming server messages is NOT on the JavaFX thread.
The RequestWorker 'gets' the Messenger instance if it is currently open, and populates the ListView with the newly received chat list. (This needs to happen on the JavaFX thread, since I am working in the Messenger GUI)
What I am trying to do is set a static AtomicBoolean syncInProgress to true when the Messenger initiates a sync upon being displayed. When the RequestWorker receives the latest list back from the server and finishes populating the Messengers ListView, it sets syncInProgress to false.
The sync takes longer than opening the Messenger and making it check if a chat exists. Doing it this way it has no items populated yet in the ListView and the method is ineffective.
Calling a while loop to wait until the boolean is changed, blocks the JavaFX thread which means the RequestWorker cannot do what it needs to in the JavaFX thread.
How can I continuously check for this variable to be set false, then continue to 'Contact Seller' once the ListView has been properly populated?
Contact Seller method: The while loop in here causes a block on the JavaFX thread, thus not enabling RequestWorker to properly populate the ListView.
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(e -> messenger.chatInput.clear());
}
RequestWorker process chat block:
switch(receivedFromServer)
case "chatlist":
// Client requested a chat list, results were returned from the server, and now we need to populate the messenger list
ChatListForm chatlistform = (ChatListForm)processedRequest;
if (Messenger.isOpen)
{
FutureTask<Void> updateChatList = new FutureTask(() -> {
Messenger tempMessenger = trader.getMessenger();
int currentIndex = tempMessenger.chatListView.getSelectionModel().getSelectedIndex();
tempMessenger.populate(chatlistform.getChatList());
tempMessenger.chatListView.getSelectionModel().select(currentIndex);
}, null);
Platform.runLater(updateChatList); // RequestWorker needs access to the JavaFX application thread
try {
updateChatList.get(); // Wait until the ListView has been populated before setting 'syncInProgress' to false again
}
catch (InterruptedException e) {
Alert.display(null, "Sync interrupted.");
}
catch (ExecutionException e) {
Alert.display(null, "Sync failed.");
}
TarkovTrader.syncInProgress.compareAndSet(true, false); // The value of syncInProgress should be true, change to false. Sync complete
}
else
{
Platform.runLater(() -> Alert.display(null, "New chat received."));
TarkovTrader.syncInProgress.compareAndSet(true, false);
}
break;
Contact Seller button logic: If a messenger is not open, create it and pass to static contactSeller method to use.
contactButton.setOnAction(e -> {
Messenger messenger;
if (Messenger.isOpen)
{
// Get messenger
messenger = trader.getMessenger();
}
else
{
messenger = new Messenger(worker);
messenger.display();
trader.setMessenger(messenger);
}
Messenger.contactSeller(messenger, item.getUsername(), item.getName());
itemdisplay.close();
});
EDIT:
Partially using Slaw's idea (AtomicBoolean is still being used since I'm not sure how to do this without it), this is what I came up with...
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
Task<Void> waitForSync = new Task<Void>() {
#Override
public Void call()
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete
}
return null;
}
};
waitForSync.setOnSucceeded(e -> {
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(me -> messenger.chatInput.clear());
});
Thread t = new Thread(waitForSync);
t.setDaemon(true);
t.start();
}
Which does work, but this doesn't seem like a great solution. Is it fine to do something like this or is there a preferred method over this? I feel like the while loop and using a triggered boolean is sloppy..but is this common practice?

Don't try to handle the opened chat in the same method that schedules the chat retrieval. Instead create a queue of handlers that is executed as soon as the chat is ready.
Simplified Example
public class ChatManager {
private final Map<String, Chat> openChats = new HashMap<>();
// only call from application thread
public void openChat(String user, Consumer<Chat> chatReadyHandler) {
Chat chat = openChats.computeIfAbsent(user, this::createNewChat);
chat.addReadyHandler(chatReadyHandler);
}
private Chat createNewChat(String user) {
return new Chat(user);
}
public class Chat {
// list keeping track of handlers any used for synchronisation
private final ArrayList<Consumer<Chat>> readyHandlers = new ArrayList<>(1);
private boolean ready = false;
private final String user;
public String getUser() {
return user;
}
private void addReadyHandler(Consumer<Chat> chatReadyHandler) {
synchronized (readyHandlers) {
// if already ready, immediately execute, otherwise enqueue
if (ready) {
chatReadyHandler.accept(this);
} else {
readyHandlers.add(chatReadyHandler);
}
}
}
private void chatReady() {
synchronized (readyHandlers) {
ready = true;
}
// execute all handlers on the application thread
Platform.runLater(() -> {
synchronized (readyHandlers) {
for (Consumer<Chat> readyHandler : readyHandlers) {
readyHandler.accept(this);
}
readyHandlers.clear();
readyHandlers.trimToSize();
}
});
}
private Chat(String user) {
this.user = user;
new Thread(() -> {
try {
Thread.sleep(10000); // simulate time required to acquire chat
} catch (InterruptedException ex) {
}
chatReady();
}).start();
}
}
}
The following code creates a Chat with a user when Enter is pressed after typing the user name in the TextField and prints a message to the TextArea when the chat is ready.
#Override
public void start(Stage primaryStage) throws Exception {
ChatManager worker = new ChatManager();
TextField userName = new TextField();
TextArea textArea = new TextArea();
textArea.setEditable(false);
userName.setOnAction(evt -> {
String user = userName.getText();
userName.clear();
textArea.appendText("opening chat for " + user + "\n");
worker.openChat(user, chat -> textArea.appendText("chat for " + chat.getUser() + " ready\n"));
});
Scene scene = new Scene(new VBox(10, userName, textArea));
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Dynamical update a textfield on live events

i am at the moment developing a Softphone with javafx. and i kind of a have problem capturing incoming call to a textfield. an example of my code is here.
an incoming call is with Joptionpane successful bt i had like to have the value appear in call textfield just like telephone.
Thank you.
public void telephoneNumbs(String numbers) {
String replace = numbers.replace("sip:", "").trim().replace(".", ""); // Incoming Call Numbers from Sip UA
if (!replace.isEmpty()) {
List<TelephoneObj> telephons;
telTextField.setText(null); //init it with null
costumDao = new CostumersDao(); // costumers DB
telephons = costumDao.getOrCompareTelfone(numbers);
for (TelephoneObj tmp : telephons) {
System.out.println("Test: " + tmp.getTelephoneNums); // am getting exactle what i need here from my Database
//or
JOptionPane.showMessageDialog(null,"incoming:"+ tmp.getTelephoneNums); // it show it during incoming calls
//here is the problem. it wouldnt show the Value on the Textfield
telTextField.setText(tmp.getTelephoneNums); //try to push that Value(Telephone number) to show in JFXTextfield/it cold be any other Textfields
}
}
Sooo much happy today it went well with after 2days of thinking how to solve this miserable life of not taking time to think.
I finally got the answer by using Task to solve the problem.
Task<Void> task = new Task<Void>() {
{
updateMessage("");
}
#Override
public Void call() throws Exception {
while (true) {
updateMessage(callee);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
break;
}
}
return null;
}
};
//neuLabel.textProperty().bind(task.messageProperty());
kdAddrTel.textProperty().bind(task.messageProperty());
Thread th = new Thread(task);
th.setDaemon(true);
th.start();

Does my Retrofit/ReactiveX method actually retrieve data asynchronously?

swipeRefreshLayout.setOnRefreshListener(() -> {
swipeRefreshLayout.setRefreshing(true);
retrieveData(mCardAdapter, db);
});
For some reason the following method is blocking my main UI thread, but it should be running in the background. For example, the refresh indicator hangs when I run retrieveData(). If I initialize a progress dialog before running, it also hangs and I can't scroll through my RecyclerView. Am I fundamentally misunderstanding something here?
public void retrieveData(final CardAdapter mCardAdapter, SQLiteHelper db) {
CausticRetrofitService service = ServiceFactory.createRetrofitService(CausticRetrofitService.class, CausticRetrofitService.SERVICE_ENDPOINT);
service.getMedia()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber < MediaResponse > () {
#Override
public final void onCompleted() {
Log.e("CausticRetrofitService", "Caustic Request Completed!");
/* Cancel all progress indicators after data retrieval complete */
setRefreshingFalse();
// TODO: Add media to local data store and then display them one-by-one in real-time
mCardAdapter.addData(db.getAllMediaImages()); // Add all media images to card views
Log.d(getClass().toString(), "Added to local database: " + db.getAllMediaImages());
mCardAdapter.notifyDataSetChanged();
}
#Override
public final void onError(Throwable e) {
/* Cancel all progress indicators on data retrieval error */
setRefreshingFalse();
Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
Log.e("CausticRetrofitService", e.getMessage());
}
#Override
public final void onNext(MediaResponse mediaResponse) {
if (mediaResponse != null) {
Log.e("CausticRetrofitService", "Returned objects: " + mediaResponse.getResults());
for (String mediaId: mediaResponse.getResults()) {
Log.e("CausticRetrofitService", mediaId);
}
List < String > mediaIds = mediaResponse.getResults();
Log.d(getClass().toString(), "All Media IDs: " + mediaIds);
if (mediaIds.isEmpty()) {
Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
}
mCardAdapter.clear();
mCardAdapter.notifyDataSetChanged();
/* Store objects from remote web service to local database */
for (String mediaId: mediaIds) {
// TODO: Why are these null?
Log.d(getClass().toString(), "Media Id: " + mediaId);
MediaImage newMediaImage = new MediaImage();
newMediaImage.setTitle(mediaId);
db.addMediaImage(newMediaImage); // Add media image to local database
}
} else {
Log.e("CausticRetrofitService", "Object returned is null.");
}
}
});
}
I'm thinking that adding the remote data to the local data store in the onNext() method might be the thing that's blocking, although I'm not certain.
Your network call is done in a new thread as you specified, but the Subscriber methods onNext() and onComplete() runs on the observing Scheduler, which is the main thread.
You seem to be doing some database operations on those, try to offload the caching also to the background thread using a doOnNext() operator.
What doOnNext() will do, is that it is called for each emission in your stream.
It can go something like that
service.getMedia()
.doOnNext(data -> cacheData(data))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
Where cacheData() is a method that does all your DB calls. And the only things left in your onNext() and onComplete() would be updating the UI only.

How to uniquely identify each Task in javafx?

I have a ListView containing URLs. When a user click on one of the URL, a video is downloaded. I am calling the video download function within a Task which in turn is called in a Thread. A user can click on multiple video URL and the video would start to download. A separate Task would be created for each of the video. What i want to know is how to uniquely identify Task for each video?
Function to download video:
public void videoFileDownload(){
try {
videoDownloadUrl = lblURL.getText().toString();
IndexOfThisNode = hbox.getId();
String path = "XXXX";
downloadThisVideo = new VGet(new URL(videoDownloadUrl),new File(path));
downloadThisVideo.download();
System.out.println("Download this video: " + videoDownloadUrl + downloadThisVideo.getVideo().getState());
System.out.println("Download complete");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Retrying...");
}
}
Function containing Task:
public void showDetailsButton(){
btnSMDetails.addEventHandler(MouseEvent.MOUSE_CLICKED, (e)->{
System.out.println("\n" + "The index is: " + getIndex() + "\n");
showLoader();
//Task created to download videos in background without blocking UI
Task downloadVideoTask = new Task<Void>() {
#Override
public Void call() {
//SIMULATE A FILE DOWNLOAD
videoFileDownload();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
};
new Thread(downloadVideoTask).start();
downloadVideoTask.setOnSucceeded(taskFinishEvent ->{showLoader(); /*isButtonClicked="0";*/});
});
}
Listcells don't exist in a one to one relationship with the underlying list. There's only enough listcells instantiated to fill the viewport of the listview plus a couple extra. Data is swapped in and out of the listcells through the updateCell method.
So you can't store data in a listcell, since the cell will get reused for another list item if you scroll the list.
What you need to do is to store a reference to the task in the underlying list item. Modify your updateCell method to bind the visibility and value of your progress bar in the listcell to the task progress property.

JList in UI not reflecting changes from all peer processes

I am writing a simple chat application in Java. As soon as a new user joins, the server component of my application should inform all other clients about this event.
The relevant code on the server side is as follows:
private void notifyUser() throws IOException {
String[] user = new String[onlineUsers.size()];
int i = 0;
for(Socket sock : sockets){
out.get(i).writeInt(1);
out.get(i).writeObject(onlineUsers.toArray(user));
out.get(i).flush();
}
}
The client thread is listening on the socket using the following code:
ObjectInputStream in = new ObjectInputStream(sockfd.getInputStream());
while(true) {
command = in.readInt();
switch(command) {
case 1:
String[] user = (String[])in.readObject();
System.out.println(user);
client.addOnlineUsers(user);
}
}
The addOnlineUsers(..) method consists of the following code:
public void addOnlineUsers(String[] user) {
for(String s : user){
listmodel.addElement(s);
}
}
I have instantiated the JList like so:
JList list = new JList(listmodel);
, where listmodel is a public instance.
The problem is, however, that as soon as a new user joins the server, the JList of the 1st (i.e. the oldest) client is updated while the rest of the users don't receive any updates to the JList in their UI.
In the notifyUser method, you are only writing to the output stream of the first user in the array, i.e.
i = 0;
It should be incremented after
out.get(i).flush();

Bubbling events up through architectural layers in java

Use Case: Collect lower level Motion events, correlate them to remove duplicates (e.g., a person walked around a house passes in front of one camera then another), then report the correlated Detection event.
Approach: (see pic) I initiate Motion Events from the video analytics and other sensors which are received and correlated by the AwarenessAnalytics component, which then raises a Detection Event to the Home Automation Main. It is similar to Chain-of-Responsibility pattern, though in reverse with events.
I have defined two completely separate event interfaces in separate files in the same package;
public interface MotionEventListener {
public void motionDetected(MotionEvent event);
public void motionLocation (MotionLocation location);
public void motionCeased(MotionEvent event);
public void eventVideoComplete(String eventId);
}
public interface DetectionEventListener {
public void motionIsDetected(DetectionEvent event);
public void motionAtLocation (MotionLocation location);
public void motionHasCeased(DetectionEvent event);
public void eventVideoNowComplete(String eventId);
}
I create the Motion Events in the VideoAnalytic thread;
private synchronized void fireDetectedEvent() {
Object source = new Object();
alertStartTime = getDateTime();
eventId++;
System.out.println("*** Motion Detected! ***" + alertStartTime + ", eventId = " +
eventId);
// Send alert to listener
String details ="";
MotionEvent event = new MotionEvent(source, alertActive, eventId,
Calendar.getInstance(), cameraId, Classification.unknown, details, alertStartTime);
Iterator i = listeners.iterator();
if (alertActive) {
while(i.hasNext()) {
((MotionEventListener) i.next()).motionDetected(event);
}
} else {
while(i.hasNext()) {
((MotionEventListener) i.next()).motionCeased(event);
}
resetVideoStreamEventCounter = 0;// keeps track of how many video resets occur from one
//event to another
}
}
The Motion events are successfully caught in the AwarenessAnalytic layer, where I createa new Detection Event if there is not already an ongoing event;
public void motionDetected(MotionEvent e) {
System.out.println("Motion Detected Listener activated " + e.getCameraId());
if (alertCounter == 0) {
Object source = new Object();
System.out.println("*** Motion Detected! ***" );
// Send alert to listener
alertCounter++;
alertId++;
alertActive = true;
DetectionEvent event = new DetectionEvent(
source,
alertActive,
alertId,
e.getEventDateTime(),
e.getCameraId(),
e.getEventType(),
e.getKnownDetails(),
e.getEventSubdirectory());
Iterator i = listeners.iterator();
if (alertActive) {
while(i.hasNext()) {
((DetectionEventListener) i.next()).motionDetected(event);
}
} else {
alertCounter++;
}
}
System.out.println("Motion Detected event received by AA from " + e.getCameraId());
}
Design pictorial:
Problem:
I've tried to catch the events in Home Automation Main as follows;
AwarenessAnalytics awarenessAnalytic = new AwarenessAnalytics();
// establish the listener set
awarenessAnalytic.addListener(this);
However, this results in a compilation error "Cannot use this in a static context"
Do I need a separate listener class for this? Or something else?
#Kon provided the clues needed to solve this problem (he is the one who deserves the credit). I created a separated DetectionListener class that implemented DetectionEventListener;
public class DetectionListener implements DetectionEventListener {
public DetectionListener() {
super();
}
public void motionIsDetected(DetectionEvent e) {
System.out.println("Motion Detected Awareness Listener test driver activated " +
e.getCameraId());
}
public void motionAtLocation (MotionLocation e) {
System.out.println("Test driver Motion location = " + e.getX() + ", " + e.getY());
}
public void motionHasCeased(DetectionEvent e) {
System.out.println("Motion Ceased Listener test driver activated from " +
e.getCameraId());
}
public void eventVideoNowComplete (String eventId) {
System.out.println("Event Video test driver activated");
}
}
And then in the Home Automation Main set up the AwarenessAnalytics instance, the DetectionListener instance, and add it to the AwarenessAnalytics instance;
AwarenessAnalytics awarenessAnalytic = new AwarenessAnalytics();
// establish the listener set
DetectionEventListener Del = new DetectionListener();
awarenessAnalytic.addListener(Del);
Now I need to call the main from the DetectionListener to complete the circle and take action on the events.

Categories

Resources