I have written a piece of code for downloading a file from internet (in background service) and showing the progress of download in a popup stage. The code compiles successfully and there is no runtime error. However no download takes place and progress indicator remains indeterminate.
The code is tailored for illustrating my point. Please have a look at it and let me understand where I have gone wrong.
Thanks!
public class ExampleService extends Application {
URL url;
Stage stage;
public void start(Stage stage)
{
this.stage = stage;
stage.setTitle("Hello World!");
stage.setScene(new Scene(new StackPane(addButton()), 400, 200));
stage.show();
}
private Button addButton()
{
Button downloadButton = new Button("Download");
downloadButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
FileChooser fileSaver = new FileChooser();
fileSaver.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "pdf"));
File file = fileSaver.showSaveDialog(stage);
getDownloadService(file).start();
}
});
return downloadButton;
}
private Service getDownloadService(File file)
{
Service downloadService = new Service()
{
protected Task createTask()
{
return doDownload(file);
}
};
return downloadService;
}
private Task doDownload(File file)
{
Task downloadTask = new Task<Void>()
{
protected Void call() throws Exception
{
url = new URL("http://www.daoudisamir.com/references/vs_ebooks/html5_css3.pdf");
// I have used this url for this context only
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
return null;
}
};
showPopup(downloadTask);
return downloadTask;
}
Popup showPopup(Task downloadTask)
{
ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.progressProperty().bind(downloadTask.progressProperty());
Popup progressPop = new Popup();
progressPop.getContent().add(progressIndicator);
progressPop.show(stage);
return progressPop;
// I have left out function to remove popup for simplicity
}
public static void main(String[] args)
{
launch(args);
}}
The line:
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
...doesn't provide you any information about the progress of your download (there is no callback or any other indication of its progress). It just downloads something without giving you feedback.
You will have to use something else that gives you feedback on the progress.
Take a look at this questions answers for solutions with feedback (it is for Swing, but you should be able to adapt them for JavaFX): Java getting download progress
You bind the ProgressIndicator's progress property to the Task's progress property, so that changes in the latter will be reflected in the former. However you never actually update your Task's progress.
If you want the progress indicator to show something, you're going to have to call updateProgress(workDone, max) within your task's body (or elsewhere). And that might be tricky if the download logic you're using doesn't give you any progress callbacks. (You could, perhaps, spawn a thread to repeatedly check the size of the file on the filesystem and use that as your current workDone; but you'd need to know what the eventual/complete size of the file would be in order to turn this into a percentage, which may or may not be easy.)
Related
There are a lot of questions and answer around concurrency, and mine could be similar to others, but for me it's not a duplicate as for some reason I must be missing something and hope to get some advice...
My question is more one where I need a second pair of eyes to point out what I'm doing incorrectly to enable my code to run in a background thread, but also updated the GUI, without freezing it.
Initially, a PDF file is uploaded to the application, using a task in a thread.
This works fine.
A progress bar is displayed, which animates without issue:
uploadFile()
public void uploadFile(File fileToProcess) {
fileBeingProcessed = fileToProcess;
Task<Parent> uploadingFileTask = new Task<Parent>() {
#Override
public Parent call() {
try {
progressBarStackPane.setVisible(true);
pdfPath = loadPDF(fileBeingProcessed.getAbsolutePath());
createPDFViewer();
openDocument();
} catch (IOException ex) {
java.util.logging.Logger.getLogger(MainSceneController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
uploadingFileTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
fileHasBeenUploaded = true;
progressBarStackPane.setVisible(false);
uploadFilePane.setVisible(false);
tabPane.setVisible(true);
/* This is where I am getting issue, more so in createThumbnailPanels() */
setupThumbnailFlowPane();
createThumbnailPanels();
/****** ^^^^^^^^^^^^ ******/
}
});
uploadingFileTask.setOnFailed(evt -> {
uploadingFileTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(uploadingFileTask.getException().getSuppressed()));
});
Thread uploadingFileThread = new Thread(uploadingFileTask);
uploadingFileThread.start();
}
Once the document has been uploaded, it is displayed in a tab which allows the user to view the document.
There is a secondary tab, which, after upload, is disabled, until the completion of another task called createThumbnailPanelsTask;
However, before this task is ran, the FlowPane for the Thumbnail Panels is created. This seems to work without issue, and doesn't appear to be the cause of the GUI hanging (this is clearly a loop in createThumbnailPanelsTask, but for clarity I will show setupThumbnailFlowPane()):
setupThumbnailFlowPane()
public void setupThumbnailFlowPane() {
stage = model.getStage();
root = model.getRoot();
secondaryTabScrollPane.setFitToWidth(true);
secondaryTabScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
/**
This will be removed from here when refactored but for now it is here,
I don't think this is anything to do with my issue
**/
Set<Node> nodes = secondaryTabScrollPane.lookupAll(".scroll-bar");
for (final Node node : nodes) {
if (node instanceof ScrollBar) {
ScrollBar sb = (ScrollBar) node;
if (sb.getOrientation() == Orientation.VERTICAL) {
sb.setUnitIncrement(30.0);
}
if (sb.getOrientation() == Orientation.HORIZONTAL) {
sb.setVisible(false);
}
}
}
secondaryTab = new FlowPane();
secondaryTab.setId("secondaryTab");
secondaryTab.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, new CornerRadii(0), new Insets(0))));
secondaryTab.prefWidthProperty().bind(stage.widthProperty());
secondaryTab.prefHeightProperty().bind(stage.heightProperty());
secondaryTab.setPrefWrapLength(stage.widthProperty().intValue() - 150);
secondaryTab.setHgap(5);
secondaryTab.setVgap(30);
secondaryTab.setBorder(new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.NONE, CornerRadii.EMPTY, new BorderWidths(8, 10, 20, 10))));
secondaryTab.setAlignment(Pos.CENTER);
}
Finally, createThumbnailPanels() is called, which is where I believe I am getting the problem.
What is suppose to happen is, after the document has uploaded, the upload file pane is hidden, revealing the Viewer Tab, and also the Secondary Tab.
The secondary tab is disabled at this point, and also has a loading image (a gif) on the left side of it.
The intended behaviour, is that the createThumbnailPanels() task will run in the background, and until it is complete, the tab will remain disabled, however, during this time, the gif image will be rotating, giving the impression there is some loading occurring.
Once the loading has completed, the gif is removed, and the tab is enabled, allowing the user to navigate to it, and see the generated thumbnail panels.
This all works, however, as mentioned, the task is hanging the GUI:
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex = 0; thumbIndex < numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
model.setThumbnailAt(tn, thumbIndex);
eventHandlers.setMouseEventsForThumbnails(tb);
/*
I have added this in as I am under the impression that a task runs in a background thread,
and then to update the GUI, I need to call this:
*/
Platform.runLater(() -> {
secondaryTab.getChildren().add(tb);
});
}
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
/*
Further GUI modification run in setOnSucceeded so it runs on main GUI thread(?)
*/
secondaryTabScrollPane.setContent(secondaryTab);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
Everything, bar the GUI hanging while it creates the panels, works fine.
Once they've been created, the GUI can be controlled again, the loading gif has been removed, the tab is enabled and the user can navigate to it and view the panels.
Clearly, there is something I am missing about concurrency here.
As mentioned, I was under the impression that a Task runs in a background thread, so I'm a little confused by why it doesn't appear to be doing this. Again, clearly something I am missing.
I have read, and read, and read about concurrency, but just can't seem to work out where in my approach I have gone wrong. I am tempted to try using a Service, however, I feel that I am just over complicating things by considering that, and that there is clearly a simply way to do what I want to achieve.
Any help will be greatly appreciated... a push in the right direction, or some clarification on where I have gone wrong in my understanding.
Thanks in advance, no doubt it's something obvious that once sorted will help me avoid this issue in future!
UPDATED CODE
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex= 0; thumbIndex< numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
eventHandlers.setMouseEventsForThumbnails(tb);
model.setThumbnailAt(tn, thumbIndex);
model.setThumbnailPanels(tb);
}
setThumbnailPanelsToScrollPane();
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
// setThumbnailPanelsToScrollPane();
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
setThumbnailPanelsToScrollPane()
public void setThumbnailPanelsToScrollPane() {
Task<Void> setThumbnailPanelsToScrollPaneTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
Platform.runLater(() -> {
secondaryTab.getChildren().addAll(model.getThumbnailPanels());
secondaryTabScrollPane.setContent(main.informationExtractionPanel);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
});
return null;
}
};
setThumbnailPanelsToScrollPaneTask.setOnFailed(evt -> {
setThumbnailPanelsToScrollPaneTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(setThumbnailPanelsToScrollPaneTask.getException().getSuppressed()));
});
Thread setThumbnailPanelsToScrollPaneThread = new Thread(setThumbnailPanelsToScrollPaneTask);
setThumbnailPanelsToScrollPaneThread.start();
}
FYI: If I call setThumbnailPanelsToScrollPane(); in the setOnSucceeded, it doesn't appear to work.
getChildren().add is running on the JavaFX GUI thread(thats what Platform.runLater does), but its only required to run it in a Platform.runLater if the parent that you add children to is connected to the root of the shown gui, that means you should be able to add children to a parent that is not connected to any root, and add the whole parent to the root at the end of the children addition process, if you're doing Platform.runLater in any asynchronous code it will run on the gui thread in your case it is in your asynchronous for loop adding ThumbnailPanels and if the number of them is large the gui will hang.
I'm using a recursive method which implements the use of the SwingWorker class to do a research in one folder and all its subfolders - in the local hard drive.
Basically works fine but I'm stuck when I want to stop the SwingWorker method: when the user change the 'source folder' (I'm using a JTree - JAVAFX - to show all the folders in the local hard drive), I want to stop the current 'SwingWorker research' in that folder and start a new one, with the newest 'source path' results choosed from the user.
All the results of the research are stored in a private ObservableList - and updated everytime in the done() method, just by filling one TableView - JavaFX: so, when the user change the 'source path' I have to clean the results of the previous research.
Start method:
private static ObservableList<msg> data = FXCollections.observableArrayList();
private static SwingWorker<Void, Void> worker;
private static String currentFolder;
#Override
public void start(Stage primaryStage) throws Exception {
// TODO Auto-generated method stub
stage = primaryStage;
primaryStage.setScene(new Scene(createContent()));
styleControls();
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setFullScreen(false);
primaryStage.show();
msgp = new MsgParser();
}
createContent() method- recursive function its called here:
public Parent createContent() {
tree.getSelectionModel().selectedItemProperty().addListener( new ChangeListener<Object>() {
#Override
public void changed(ObservableValue observable, Object oldValue,
Object newValue) {
TreeItem<File> selectedItem = (TreeItem<File>) newValue;
currentFolder = selectedItem.getValue().getAbsolutePath();
// I want to stop here the previous SwingWorker call : the tree
// ChangeListener event is called when the user change the
// source folder of the research, by selecting one TreeItem on it.
if(worker!= null)
worker.cancel(true);
//Here I clean previous results
data.clear();
TV.setItems(data);
//And I call again the method with the new source Folder
ListMail(new File(currentFolder));
}
});
}
ListMail() method: [recursive SwingWorker]
private void ListMail(File dir) {
worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
if(!worker.isCancelled()) {
if(child != null){
if(!child.isDirectory()) {
if(child.getAbsolutePath().substring(child.getAbsolutePath().lastIndexOf('.')+1).equals("msg")) {
Message message = msgp.parseMsg(child.getPath());
String percorsoMail = child.getAbsolutePath().toUpperCase();
if(message != null) {
String fromEmail = message.getFromEmail();
String fromName = message.getFromName();
String subject = message.getSubject();
String received = message.getDate().toString();
String name;
if(fromEmail != null)
name = fromName + "(" + fromEmail + ")";
else name = fromName;
msg Message = new msg(name, subject, received);
if(!data.contains(Message))
data.add(Message);
//I use the Platform.runLater to
// take count of the number of results found
//It updates the GUI - works fine
Platform.runLater(new Runnable() {
#Override public void run() {
if(data != null && data.size() > 0)
setStatusLabel(data.size());
else
setStatusLabel(0);
}
});
}
}
} else {
/**
* Recursive call here : I do the research
* for the subfolders
*/
ListMail(child);
}
} else {
}
}
}
}
return null;
}
// Update GUI Here
protected void done() {
// I refresh here the TableView: works fine on-the-fly added results
TableView.setItems(data);
TableView.refresh();
}
};
//This doesn't do anything
if(!worker.isCancelled())
worker.execute();
}
Basically, the issue is that the SwingWorker thread never stop, I'm thinking because of the recursive calls which creates new pid process at every run or something ?
Also by using a dedicated external button, which I prefer to avoid, gives no results:
refreshBtn.setOnAction(e -> {
//Handle clicks on refreshBtn button
worker.cancel(true);
});
After I click on TreeItem to change source-folder, it just delete all the ObservableList elements created at that moment, but the previous research don't stop.
Everything works fine instead if I wait the research its finished - but this can works only when I'm in a deep-level folder, while I can't obviously wait when the research start with the "C:\" folder.
Ok so that's here how I managed this by using javafx.concurrent.
Just to point my experience with this, it seems using a recursive background Task for potentially long computations, such as scanning the Whole local drive like in my example, it's very memory consuming - also because I stored some results of this background computation in static local variables to access them faster: the result was a data-structure (ObservableList) with over 5000+ instances of a custom class to represent that specific data computed and then the OutOfMemoryError message or the background thread just going like in 'stand-by' without any advice after running for long time (waiting for garbage collection?).
Anyway here's the code that sum up how I solved: the threads are correctly closed. By the way, sometimes, there's a little 'GUI delay' due to cleaning the GUI on the isCancelled() method check: the GUI swing between clear/not clear, because in my opinion it keeps get filled by the results of the previous tasks in the recursion.
private static BackgroundTask backgroundTask;
private static Thread thread;
tree.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(final ObservableValue observable, final Object oldValue, final Object newValue) {
//I close previous running background tasks if there's any
if (backgroundTask != null) {
while (backgroundTask.isRunning()) {
backgroundTask.cancel(true);
// reset GUI nodes here used to show results of the previous thread
}
}
backgroundTask = new BackGoundTask();
thread= new Thread(backgroundTask);
thread.setDaemon(true);
thread.start();
//This will be called only when latest recursion is finished, not at every run
backgroundTask.setOnSucceeded(e -> {});
}
});
BackgroundTask class:
public static class BackgroundTask extends Task<Object> {
// .. variables used by the task here
//constructor: initialize variables at every run of the Task
public BackgroundTask() {
}
#Override
protected Object call() throws Exception {
if (!isCancelled()) {
// ... Do all background work here
Platform.runLater(new Runnable() {
#Override
public void run() {
// GUI progress can goes here
}
});
//recursion here
if(something) {
//...
} else {
call();
}
} else {
//user want to cancel task: clean GUI nodes
}
return null;
}
}
So I am using JavaFX and want to set up an experiment where I have to play many videos and interact with the user. For this setup I would like to write a function that receives a filename and plays the video with that name. But it should only return when the video is over, and not before. So something like this:
private void play(URI name){
MediaPlayer mediaPlayer = new MediaPlayer(new Media(name));
mediaPlayer.setAutoPlay(true);
view.setMediaPlayer(mediaPlayer);
}
However this function returns before the video is over, making the execution of the program a complete mess. I know MediaPlayer has a setOnEndOfMedia(Runnable) function but I am not sure how I could potentially use that to achieve my goal.
This is the code I have at the moment. But it does not work. The problem being that both videos are played at the same time, likely because the play function returns as soon as it has started the video. I have also tried just adding a while(true) loop at the end of the play function so that it gets stuck there until the return; in .setOnEndOfMedia is callled, that prevented any video's from being played at all.
CODE:
static MediaView view;
#Override
public void start(Stage stage) throws IOException, URISyntaxException, InterruptedException {
// Create full screen scene
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
stage.setFullScreen(true);
Scene scene = new Scene(new Group(), dim.getWidth(), dim.getHeight());
stage.setScene(scene);
stage.show();
// Create the view and add it to the Scene.
view = new MediaView();
((Group)scene.getRoot()).getChildren().add(view);
play("videol");
play("video2");
}
private static void play(String name) throws InterruptedException {
Media media = new Media(uri(name));
MediaPlayer player = new MediaPlayer(media);
player.setOnReady(() -> {
player.play();
player.setOnEndOfMedia(() -> {
player.dispose();
return;
});
});
view.setMediaPlayer(player);
}
private static String uri(String name) {
return Paths.get("clips/" + name + ".mp4").toUri().toString();
}
public static void main(String[] args) {
launch(args);
}
Keeping my original stuff since it is useful for others.
Anyways the problem with your code is that you are calling the method twice, right after itself. You need to call the method from within the EndOfMedia part. If you wish you can create a list and for the parameter when using recursion you can use list.poll() to take and remove the first string in the list. Then when the list is null you can just return to the interface. Just some ideas.
What I would suggest you use is to create a method for playing your music which takes a String as a parameter, this string would be then used to find its equivalent in a hashmap, say HashMap<String, MediaPlayer>. Then to the part where you actually need help. I suggest you write something like this:
mediaPlayer.setOnReady(() -> {
mediaPlayer.play();
mediaPlayer.setOnEndOfMedia(() -> {
mediaPlayer.dispose();
MusicPlayer();
});
});
Basically, you are playing the media when ready and then at the end disposing of it and calling the method again and in where my code uses recursion and calls the method MusicPlayer you can instead 'return'. Sorry if this is poorly explained, if you need further help just comment below.
(note, what i newbie in java)
I a little bit stuck in solving the problem of pass object between javafx scenes and classes.
For example, i have class, which waits for data by server; main class and 2 javafx windows.
Let it looks like this:
Listener.java.
Let it work at another thread. When we got "nelo" from server, then it'll means, what user not logged it, and then, we should open Login Window
// some package
// some imports
public class Listener extends Thread {
public void run() {
System.out.println("[INF] Wait for server...");
while(true) {
handle();
}
}
public void handle()
{
try {
byte[] token = new byte[6];
DataInputStream src = new DataInputStream(in);
src.read(token);
String token_val = new String(token);
switch (token_val) {
case "_nelo_":
System.out.println("[INF] Auth required");
break;
}
} catch (IOException e) {
}
}
}
Okay, there nothing weird. Just simple class for listening.
But here my troubles started. I try to explain it. (previosly, Sorry for bad english, i still learn this language)
Lets create login window (imagine that fxml file and controller created :) ):
// some package
// some imports
public class WindowLogin extends Application{
private Stage stage;
#Override
public void start(Stage primaryStage) throws Exception {
stage = new Stage();
try {
URL location = getClass().getResource("../views/WindowLogin.fxml");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(location);
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
Parent root = (Parent) fxmlLoader.load(location.openStream());
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void show(){
launch();
}
}
So, when user open application, client will try connect to server.
If connection success, then server ask client for auth (Show login window). Next - user enter login and pass and click "Login" button. (Login Window show some indication of proccess). If auth success - Hide login, else - show some info in window.
As a result, i need access from Listener to Login window Controller. i.e. as i wrote before - different answer from server - different elements displayed.
How i can realize access to LoginWindowController ?
Thanks in advance
I will give you basic guidance for each task:
Connection
If connection with the server is successful then:
Platform.runLater(new Runnable() {
public void run() {
try {
WindowLogin login = new WindowLogin();
login.start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
Login
Error
You set a blank label on the WindowController and if the user cound't autheticate, than you fill this label.
Succesful login
You could use the same Platform.runLater as I used it before, or you could do something using stackpane and changing it's order ( I strongly recommend to use the Platform.runLater).
I'm trying to write a simple application to display chart data. I want to display some data as soon as the user loads the page, so I'm getting data & drawing tables inside of the Runnable as described in the gwt-visualization Getting Started.
Things seem to work alright, except charts tend to get loaded more than once. Below is my onModuleLoad().
private final StatisticsServiceAsync statisticsService = GWT.create(StatisticsService.class);
GWTBeanFactory factory = GWT.create(GWTBeanFactory.class);
DataTable locationData;
AnnotatedTimeLine atl;
GeoMap usMap;
TextBox storeField;
Button log10Button;
DateRange durationChartRange;
String eusrJson = null;
Button b;
HTML last1000Html;
public void onModuleLoad() {
storeField = new TextBox();
storeField.setText("Enter a store");
storeField.addKeyDownHandler(new MyKeyHandler());
b = new Button("Get Stats!");
log10Button = new Button("Show Log10 Scale");
log10Button.addClickHandler(new Log10ClickHandler());
b.addClickHandler(new MyClickHandler());
last1000Html = new HTML();
getLast1000Avg();
Runnable onLoadCallback = new Runnable() {
public void run() {
storeDurationData = DataTable.create();
storeDurationDataLog10 = DataTable.create();
RootPanel.get("storeDurationDiv").add(storeField);
RootPanel.get("storeDurationDiv").add(b);
RootPanel.get("storeDurationDiv").add(log10Button);
RootPanel.get("storeDurationDiv").add(last1000Html);
log10Button.setVisible(false);
// Get initial Data
getAvgByRegion();
getLast1000Avg();
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
#Override
public boolean execute() {
getLast1000Avg();
return true;
}
}, 5000);
}
};
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback, AnnotatedTimeLine.PACKAGE);
VisualizationUtils.loadVisualizationApi(onLoadCallback, GeoMap.PACKAGE);
}
All of the "simple" elements seem to get populated correctly, as the Button, HTML, and TextBox all get placed appropriately (which used to be inside of run, they're where they are now as a result of debugging previous errors). However, the GeoMap gets placed twice, and looking at the logging you can tell that the Runnable's run is being executed at least twice, which seems reasonable, but I don't know how to keep it from adding twice.
I'm probably screwing up something with the Async stuff, but I'm new and confused. Below is my getAvgByRegion() method:
private void getAvgByRegion() {
statisticsService.getEusrForRegion(new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
System.out.println(":(");
}
#Override
public void onSuccess(String result) {
createLocTable();
DataTable dt = parseEusrLocations(result);
usMap = new GeoMap(dt, createGeoMapOptions());
usMap.setSize("800px", "600px");
RootPanel.get("storeDurationDiv").add(usMap);
}
});
}
Any advice on how best to work with GWT is welcome.
So, you call VisualizationUtils.loadVisualizationApi twice, so the onLoadCallback will be run twice (I don't know GWT Google Apis, this is a supposition).
onLoadCallback calls getAvgByRegion, so that one will get called twice too; and it gets data and in the callback creates a new GeoMap and adds it to the RootPanel.get("storeDurationDiv"), so you get two GeoMaps on the screen.
The other widgets(storeField, etc.) are created only once, so adding them repeatedly is not a problem (except performance-wise), as they'll first be removed from their current parent before being added to the new one (which in this case is the same)