Why the GUI is taking so much time to show up? [duplicate] - java

I have a function which is supposed to return a list from the result of a Task API.
#Override
public List performQuery(boolean isPaginationQuery, boolean isSortingQuery {
try {
TaskImpl taskImpl = new TaskImpl(isPaginationQuery,
isSortingQuery);
queryExecutor.submit(taskImpl).get();
return taskImpl.get();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Inner class which performs the updates
private class TaskImpl extends Task<List> {
private boolean isPaginationQuery, isSortingQuery;
public TaskImpl(boolean isPaginationQuery, boolean isSortingQuery) {
this.isPaginationQuery = isPaginationQuery;
this.isSortingQuery = isSortingQuery;
}
#Override
protected List call() throws Exception {
Platform.runLater(() -> {
loaderContainer.setVisible(true);
loaderContainer.toFront();
});
HSession hSession = new HSession();
TaskInfoDao taskInfoDao = new TaskInfoDaoImpl(hSession.getSession(), currentConnection.getConnectionId());
if (!isPaginationQuery && !isSortingQuery) {
paginator.setTotal(taskInfoDao.getTaskInfoWithFiltersCount(paginator.getFilterMap(), false));
}
Stream<TaskInfo> resultStream = taskInfoDao.getTaskInfoWithFilters(paginator.getFilterMap(), false,
paginator.getStartIndex() * paginator.getPageSize(),
paginator.getPageSize() * paginator.getPageGap());
List<TaskInfoTableView> data = createData(resultStream);
hSession.close();
return data;
}
#Override
protected void succeeded() {
super.succeeded();
try {
//set the pagination if the task is complete
//and it is not a pagination query
if (!isPaginationQuery) {
((TaskInfoViewController) uiController).setPagination(
FXCollections.observableArrayList(get()));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
#Override
protected void cancelled() {
super.cancelled();
updateMessage("Cancelled!");
}
#Override
protected void failed() {
super.failed();
updateMessage("Failed!");
}
}
performQuery function calls the thread and waits for its result.
The loader is being displayed from inside the TaskImpl class using Platform.runLater.
But the loader does not appear until the task has finished i.e. loader appears after the completion of call() function's execution.
When i remove the taskImpl.get() the loader works fine.
Any help is appreciated.
P.S. : Under any case, I need the result of the Task API outside the Inner class( outside TaskImpl )

First of all, it seems like you are not very familiar with asynchronous programming. Having performQuery() to return a List shows that you are expecting to run this synchronously - there is no way for you to return results before you get the results. This is exactly why you are freezing your UI.
The important thing to understand about asynchronous programming is, you would start doing something (i.e. a task) in another thread, and return immediately. When there is result returned from the task, you switch back to the UI (JavaFX Application) thread to update it. You can see this as event-driven approach.
Therefore, for your case, you should directly update the list (the list which you are returning in performQuery()) in the succeeded() method that you have overridden in TaskImpl class.
If the list that you should be updating is not in the scope of TaskImpl, then you can the functional interfaces in java.util.function package to do it for you. This means that you would create that functional interface object at the right scope, and pass in into TaskImpl during object construction, and call that interface in succeeded().
Update
If I assume this is what calls performQuery():
public class MyController {
#FXML
TableView<Foo> tableView;
public void initialize() {
List result = queryController.performQuery(true, true);
tableView.getItems().addAll(result);
}
}
Then, I would probably do something like this:
public class MyController {
#FXML
TableView<Foo> tableView;
public void initialize() {
List result = queryController.performQuery(true, true, list -> tableView.getItems.addAll(list));
}
}
public class QueryController {
#Override
public void performQuery(boolean isPaginationQuery, boolean isSortingQuery, java.util.function.Consumer<List> onQuerySucceeded) {
try {
TaskImpl taskImpl = new TaskImpl(isPaginationQuery,
isSortingQuery, onQuerySucceeded);
queryExecutor.submit(taskImpl);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private class TaskImpl extends Task<List> {
private final java.util.function.Consumer<List> onQuerySucceeded;
public TaskImpl(boolean isPaginationQuery, boolean isSortingQuery, java.util.function.Consumer<List> onQuerySucceeded) {
this.isPaginationQuery = isPaginationQuery;
this.isSortingQuery = isSortingQuery;
this.onQuerySucceeded = onQuerySucceeded;
}
#Override
protected void succeeded() {
super.succeeded();
// Not sure what the original codes are doing.
try {
//set the pagination if the task is complete
//and it is not a pagination query
if (!isPaginationQuery) {
((TaskInfoViewController) uiController).setPagination(
FXCollections.observableArrayList(get()));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// This is what is being added in
onQuerySucceeded.accept(this.getValue());
}
}

Related

How to wait for a JavaFX Service to finish before returning data in caller?

Working on my first Java project I can't seem to get around this probably basic problem: In a JavaFX application I have a DAO class, which starts a service to get values from a mysql db, builds an object from it and returns the object to the caller. But the object never gets build, because the return happens before the service has succeeded.
public IQA getQA(int id) throws SQLException {
try {
GetQuizService getQuizService = new GetQuizService();
getQuizService.restart();
getQuizService.setId(id);
getQuizService.setOnSucceeded(e -> {
this.quiz = getQuizService.getValue();
});
} catch (Exception e) {
System.err.println(e);
}
return quiz;
}
The service works fine, inside the onSucceeded action the object is present, but how can I make the return wait until the service has finished?
As requested here's a minimal version of the GetQuizService
public class GetQuizService extends Service<Quiz> {
private int id;
private Quiz quiz;
public void setId(int id) {
this.id = id;
}
#Override
protected Task<Quiz> createTask() {
return new Task<Quiz>() {
#Override
protected Quiz call() throws Exception {
// Severall calls to db here, Quiz object gets constructed
return quiz;
}
};
}
}
The problem in your code is, that you service methods are executed asynchronously.
You should return the Task<Quiz> instead of quiz and use that to update your frontend if the result is received (I have to few information to create an appropriate example for you).
Another option is to pass a callback to your service, which is invoked when the result is received instead of returning the quiz.
public void getQA(int id, QuizReceiver callback) throws SQLException {
try {
GetQuizService getQuizService = new GetQuizService();
getQuizService.restart();
getQuizService.setId(id);
getQuizService.setOnSucceeded(e -> {
callback.quizReceived(getQuizService.getValue());
});
} catch (Exception e) {
System.err.println(e);
}
return quiz;
}
public interface OuizReceiver {
void quizReceived(IQA quiz);
}

Strategy for multiple subscribers and flow of data using RxJava 2

I cannot decide how to implement this task correctly using RxJava2.
The problem is following. I am recording audio using AuidoRecord.
Currently I have implemented the custom Flowable class like that
private class StreamAudioRecordRunnable extends Flowable<short[]> implements Runnable {
private int mShortBufferSize;
private List<Subscriber<? super short[]>> mSubscribers = new ArrayList<>();
private short[] mAudioShortBuffer;
private void removeAllNullableSubscribers() {
mSubscribers.removeAll(Collections.singleton(null));
}
private void notifyAllSubscribers(short[] audioBuffer) {
removeAllNullableSubscribers();
for (Subscriber<? super short[]> subscriber : mSubscribers) {
subscriber.onNext(audioBuffer);
}
}
#Override
protected void subscribeActual(Subscriber<? super short[]> newSubscriber) {
mSubscribers.add(newSubscriber);
}
private void notifyAllSubscribersAboutError(Throwable error) {
for (Subscriber<? super short[]> subscriber : mSubscribers) {
subscriber.onError(error);
}
}
#Override
public void run() {
// Init stuff
while (mIsRecording.get()) {
int ret;
ret = mAudioRecord.read(mAudioShortBuffer, 0, mShortBufferSize);
notifyAllSubscribers(mAudioShortBuffer);
}
mAudioRecord.release();
}
}
As you can see I am manually adding subscribers to the list. Then when I get new buffer all subscribers are notified.
I am guessing that this is not the most performant way to do this.
What I need
As far as this flowable running in a service. It should run until the service is alive, even if there are no subscribers.
Subscribers are not constant, they may subscribe and then unsubscribe, but the Flowable/Observable should still be running.
As the data emitted by the Flowable is the stream, subscribers should not be notified about already emitted items, they should only get current streaming data. Fire and forget.
The Flowable should run even all subscribers are gone.
Please suggest the right strategy to implement this.
I would be grateful for any help.
Something like
public class StreamAudioRecordRunnable {
private int mShortBufferSize;
private short[] mAudioShortBuffer;
private ConnectedFlowable<short[]> audioFlowable();
public StreamAudioRecordRunnable() {
audioFlowable = Flowable.create(new ObservableOnSubscribe<short[]>() {
#Override
public void subscribe(FlowableEmitter<short[]> emitter) throws Exception {
try {
while (mIsRecording.get()) {
int ret;
ret = mAudioRecord.read(mAudioShortBuffer, 0, mShortBufferSize);
emitter.onNext(mAudioShortBuffer);
}
emitter.onComplete();
mAudioRecord.release();
} catch (Exception e) {
emitter.onError(e);
mAudioRecord.release();
}
}
}).subscribeOn(Schedulers.io()).publish();
}
public Flowable<short[]> getFlowable() {
return audioFlowable.hide();
}
#Override
public void start() {
audioObservable.connect();
}
}
would be my preference.

Wrapping an EnumMap in a observable SimpleMapProperty

I need to add a SimpleMapProperty to a JavaFX service class, but I am not sure of the correct syntax of if I am using the correct approach. Note that I am not trying to make the JavaFX service appear like a Java Bean, I just need to know how to listen for updates to an EnumMap from a enum ModuleType (that can be TYPEA or TYPEB) and an associated Boolean flag. Essentially, this can be thought of as a pair of watchdog timers wrapped in a single EnumMap.
I am having trouble understanding how to add the underlying EnumMap entries (there should be 2 - one for each ModuleType described above).
public class UDPListenerService extends Service<Void> {
// 'watchdog' property
private final MapProperty<ModuleType, Boolean> watchdog;
// 'watchdog' SimpleMapProperty bound property getter
public ObservableMap<ModuleType, Boolean> getWatchdog() {
return watchdog.get();
}
// 'watchdog' SimpleMapProperty bound property setter
public void setWatchdog(ObservableMap<ModuleType, Boolean> aValue) {
watchdog.set(aValue);
}
// 'watchdog' SimpleMapProperty bound property
public MapProperty<ModuleType, Boolean> watchdogProperty() {
return watchdog;
}
/**
* Constructor
*/
public UDPListenerService()
{
this.watchdog = new SimpleMapProperty<>(
FXCollections.observableHashMap());
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
updateMessage("Running...");
while (!isCancelled()) {
try {
Thread.sleep(1000);
Platform.runLater(() -> {
try {
// update do some processing here
// . . .
// pet the watchdog
// setWatchdog
if (testforModuleType==ModuleType.TYPEA) {
// please help with syntax
setWatchdog(ModuleType.TYPEA, false);
} else {
// please help with syntax
setWatchdog(ModuleType.TYPEB, false);
}
} catch (StatusRuntimeException ex) {
// watchdog timed out - listener will
// update gui components
if (testforModuleType==ModuleType.TYPEA) {
// please help with syntax
setWatchdog(ModuleType.TYPEA, true);
} else {
// please help with syntax
setWatchdog(ModuleType.TYPEB, true);
}
}
});
} catch (InterruptedException ex) {}
}
updateMessage("Cancelled");
return null;
}
};
}
}
The way I use this class is in the JavaFX controller class where I add a listener that populates java gui elements depending on whether the associated Boolean flag is true or false.
Usually a readonly map property is used for this kind of behavior, i.e. a ObservableMap field with only a getter. Only the contents of the map are modified; no new map is assigned to the field after the initial map is assigned.
private final ObservableMap<ModuleType, Boolean> watchdog;
public ObservableMap<ModuleType, Boolean> getWatchdog() {
return watchdog;
}
The map itself is modified the same way a java.util.Map would be modified, e.g. in this case using the put method. Changes can be observed e.g. using a MapChangeListener or Bindings.valueAt.
Furthermore EnumMap can be used as backing Map for a ObservableMap, but to do this the observableMap method needs to be used instead of the observableHashMap method.
The following example randomly selects / deselects values of 2 checkboxes based on values in a ObservableMap.
private CheckBox checkBoxA;
private CheckBox checkBoxB;
private ObservableMap<ModuleType, Boolean> map;
#Override
public void start(Stage primaryStage) {
checkBoxA = new CheckBox("type A");
checkBoxB = new CheckBox("type B");
map = FXCollections.observableMap(new EnumMap<>(ModuleType.class));
initMapListeners();
Thread t = new Thread(() -> {
Random random = new Random();
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
boolean b1 = random.nextBoolean();
boolean b2 = random.nextBoolean();
Platform.runLater(() -> {
map.put(ModuleType.TYPEA, b1);
map.put(ModuleType.TYPEB, b2);
});
}
});
t.setDaemon(true);
t.start();
Scene scene = new Scene(new VBox(10, checkBoxA, checkBoxB));
primaryStage.setScene(scene);
primaryStage.show();
}
Both the following implementations of initMapListeners() would both set the CheckBox.selected states based on the map values.
private void initMapListeners() {
checkBoxA.selectedProperty().bind(Bindings.valueAt(map, ModuleType.TYPEA));
checkBoxB.selectedProperty().bind(Bindings.valueAt(map, ModuleType.TYPEB));
}
private void initMapListeners() {
map.addListener((MapChangeListener.Change<? extends ModuleType, ? extends Boolean> change) -> {
if (change.wasAdded()) {
if (change.getKey() == ModuleType.TYPEA) {
checkBoxA.setSelected(change.getValueAdded());
} else if (change.getKey() == ModuleType.TYPEB) {
checkBoxB.setSelected(change.getValueAdded());
}
}
});
}

How do I change rx.Observable<java.util.list<T>> to java.util.list<T>

Declaration
#GET("api/Game/SearchGames")
Observable<List<GameModel>> searchGames();
This is the network call
public static Observable<List<GameModel>> searchGames () {
VersusAPI client = VersusServiceGenerator.createService(VersusAPI.class);
Observable<List<GameModel>> ob = client.searchGames();
return ob;
}
Here is where I implement.
mAdapterMyGames = new RecyclerViewAdapter(searchGames());
searchGames() returns rx.Observable<java.util.list<GameModel>>. How do I change that to only java.util.list<GameModel>?
You don't properly understand what is an Observable.
It is an object, to which You can subscribe() to get the result of it's operation. Usually, only when subscribing to an Observable it starts and you can get the result inside Subscriber's onNext() function.
So in your case:
Subscribe to this Observable.
Look for the result inside this subscriber's onNext function.
searchGames().subscribe(new new Subscriber<List<GameModel>>() {
#Override
public void onNext(List<GameModel> gameModels) {
//TODO make sth useful with models
}
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) { }
)

JavaFX ObservableList change not updating ListView

So I am creating a UI with JavaFX for a server controller, what it is does not matter, all that is important is that the server.getClients(); returns an ArrayList of IClients.
I wish to display these clients (they are represented by IP but once again, this doesn't seem relevant) in a ListView. However, clients may connect at any given point in time and when this happens, they get added to the server's IClient ArrayList. When this List is updated, I want the ListView to refresh and show the new client. For some reason, I simply cannot get this to work.
I am very new to JavaFX and I think I might be overseeing something.
I'm very sorry if this is a duplicate or obvious, I have searched for a long time over the past couple of days but I might have overlooked a solution.
The following code is the abbreviated version of my FXMLController for the JavaFX application:
/*imports*/
public class FXMLController implements Initializable {//serverUI.FXMLController
#FXML private ListView clientListView;
/*some more (irrelevant) code*/
private IServer server;
/*some more (irrelevant) code*/
private ObservableList<IClient> serverClientsObservableList;
#Override
public void initialize(URL url, ResourceBundle rb) {
System.out.println("initialization...");
/*some more (irrelevant) code*/
//the server was started here
// FXML Controls
initClientListView();
/*some more (irrelevant) code*/
System.out.println("initialized");
}
/*some more (irrelevant) code*/
private void initClientListView() {
System.out.println("clientListView");
serverClientsObservableList = FXCollections.observableList(server.getClients());
serverClientsObservableList.addListener(new ListChangeListener<IClient>() {
#Override
public void onChanged(ListChangeListener.Change<? extends IClient> change) {
System.out.println("list change detected");
//is any of the followin three lines really necessary to update the ListView content?
serverClientsObservableList.setAll(server.getClients());
clientListView.setItems(null);
clientListView.setItems(serverClientsObservableList);
}
});
clientListView.setItems(serverClientsObservableList);
}
/*some more (irrelevant) code*/
}
EDIT:
I don't want to refresh the ListView when something in the IClients changes, nothing changes in them. I want to refresh the ListView when a NEW IClient is ADDED to the server's client list. The ListView should show the NEW IClient
EDIT2:
According to the suggested duplicate I tried the following, however I don't really understand what it's doing and how it works. This did not solve the problem, it gives me an error when I try to create the new Observable[]
Callback<IClient, Observable[]> extractor = new Callback<IClient, Observable[]>() {
#Override
public Observable[] call(IClient c) {
return new Observable[] {c.getNameProperty()};
}
};
ObservableList<IClient> clientOList = FXCollections.observableArrayList(extractor);
Additionally: the code where I add the clients to the server.
Long story short, this is an assignment where we have to user RMI in an inverse way, the server commands the clients. Clients register themselves to the server's list and that's where they're added to the IClient list.
package serviceImplementation;
import commandService.ICommand;
import commandServiceImplementation.CommandResult;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import service.IClient;
import service.IServer;
public class ServerService extends UnicastRemoteObject implements IServer {
private ArrayList<IClient> clients;
public ServerService() throws RemoteException {
clients = new ArrayList<>();
}
#Override
public boolean register(IClient client) throws RemoteException {
if(!clients.contains(client)) {
clients.add(client);
return true;
}
return false;
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
if(clients.contains(client)) {
clients.remove(client);
return true;
}
return false;
}
#Override
public String ping() throws RemoteException {
long arrival = System.currentTimeMillis();
System.out.println("Got pinged at [" + arrival + "]");
return ("server ponged [" + arrival + "]");
}
#Override
public CommandResult sendCommand(ICommand command, IClient targetClient) throws RemoteException {
if(clients.contains(targetClient)) {
return clients.get(clients.indexOf(targetClient)).executeCommand(command);
}
return null;
}
#Override
public ArrayList<IClient> getClients() {
return clients;
}
}
Your observable list is created as a wrapper for the underlying list in the ServerService using FXCollections.observableList(...). The observable list that is returned by this just wraps the underlying list, so it always contains the same elements as the underlying list. However, as noted in the documentation:
mutation operations made directly to the underlying list are not reported to observers of any ObservableList that wraps it.
When clients are registered or unregistered in the server service, you add them to the underlying list. Since the underlying list is not an observable list, no notifications are fired, and so the ListView does not know to refresh itself.
One possible solution may be to use an ObservableList in the ServerService:
public class ServerService extends UnicastRemoteObject implements IServer {
private ObservableList<IClient> clients;
public ServerService() throws RemoteException {
clients = FXCollections.observableArrayList();
}
// ...
#Override
public ObservableList<IClient> getClients() {
return clients;
}
}
and then you do
private void initClientListView() {
clientListView.setItems(server.getClients());
}
Note that this couples your ServerService to the JavaFX API; this is probably not too bad as the JavaFX Collections API does not rely on any UI elements at all.
However, the code above will not work if your clients are registered/unregistered on a background thread (i.e. not on the FX Application Thread), which is almost certainly the case. Because of this, you need to make the following changes to make this work:
#Override
public boolean register(IClient client) throws RemoteException {
FutureTask<Boolean> register = new FutureTask<>(() ->
if(!clients.contains(client)) {
clients.add(client);
return true;
}
return false;
);
Platform.runLater(register);
return register.get();
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
FutureTask<Boolean> unRegister = new FutureTask<>(() ->
if(clients.contains(client)) {
clients.remove(client);
return true;
}
return false;
);
Platform.runLater(unRegister);
return unRegister.get();
}
Now your ServerService has a much stronger dependency on JavaFX, because it assume the FX Application Thread is running. You didn't make any specifications about how this is being used, but there's a good chance you don't want this coupling.
An alternative is to support callbacks in the ServerService. You can represent these pretty simply using a Consumer<IClient>. This looks something like:
public class ServerService extends UnicastRemoteObject implements IServer {
private ArrayList<IClient> clients;
private Consumer<IClient> registerCallback = client -> {} ;
private Consumer<IClient> unregisterCallback = client -> {} ;
public ServerService() throws RemoteException {
clients = new ArrayList<>();
}
public void setRegisterCallback(Consumer<IClient> registerCallback) {
this.registerCallback = registerCallback ;
}
public void setUnregisterCallback(Consumer<IClient> unregisterCallback) {
this.unregisterCallback = unregisterCallback ;
}
#Override
public boolean register(IClient client) throws RemoteException {
if(!clients.contains(client)) {
clients.add(client);
registerCallback.accept(client);
return true;
}
return false;
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
if(clients.contains(client)) {
clients.remove(client);
unregisterCallback.accept(client);
return true;
}
return false;
}
#Override
public String ping() throws RemoteException {
long arrival = System.currentTimeMillis();
System.out.println("Got pinged at [" + arrival + "]");
return ("server ponged [" + arrival + "]");
}
#Override
public CommandResult sendCommand(ICommand command, IClient targetClient) throws RemoteException {
if(clients.contains(targetClient)) {
return clients.get(clients.indexOf(targetClient)).executeCommand(command);
}
return null;
}
#Override
public ArrayList<IClient> getClients() {
return clients;
}
}
and now in your UI code you do
private void initClientListView() {
System.out.println("clientListView");
serverClientsObservableList = FXCollections.observableArrayList(server.getClients());
server.setRegisterCallback(client -> Platform.runLater(() ->
serverClientsObservableList.add(client)));
server.setUnregisterCallback(client -> Platform.runLater(() ->
serverClientsObservableList.remove(client)));
clientListView.setItems(serverClientsObservableList);
}

Categories

Resources