JavaFX wait for Webview fully load or cancel everything after 15 seconds

Viewed 323

I am writing on a nfcreader programm in JavaFX. Using ACR122U nfcreader and the nfcreader class found on github. It works as expected, for now I am holding the card to the reader and after the card uid is read it loads a webpage from our server and shows information.

Problem:

  • the NFC reader is running in a background task and always fires if a card is hold on the reader. I need to prevent this - while the webpage is loading.

What I did:

  • if the nfcread service succeeds it calls a method "loadZeitdatenToWebview(nfcuid)"
  • in this method a variable is set to true to tell Java that the webview still loads data
  • I am setting this variable to false if the Workerstate of the webview succeeds otherwise its true... for a amount of 15 seconds checked by a pausetransition - if I get no webview "success" within 15 seconds it cancels the whole nfcread and tells the user there is no connection to the server - so far
  • it works as it should if I have no connection to the server it stops the nfcread and everything is fine
  • but if I get a webview success - it works only once ... I have to wait for 15 seconds (from the pause transition) till it reads again and shows me the webpage. So pause transition isn't the best way to achieve whar I need :( - but what else can be used ? in short: readnfc -> get uid from card -> webview calls a page dep. on the uid -> no other nfcreader should be possible till webpage is fully loaded and shwon to the user -> if there is no connection to the server after 15 seconds stop everything ...

Code for the nfcread succeeded... :

 nfcread.setOnSucceeded(new EventHandler<WorkerStateEvent>() { //if service succeeded

        @Override
        public void handle(WorkerStateEvent event) {
            labelReaderStatus.setText("NFC Readerstatus: warte auf NFC");
            labelReaderStatusInfo.setStyle("-fx-background-color: WHITE; -fx-text-fill: black;");
            labelReaderStatusInfo.setText("warte...");

            System.out.println("NFC UID Card: " + nfcread.getValue()); //return value from service nfcread
            if (nfcread.getValue() != "" && nfcread.getValue() != "Error" && nfcread.getValue() != null && nfcread.getValue() != "Error no Card") {
                uidRead = nfcread.getValue();

                labelReaderStatusInfo.setText("NFC Chip wurde gelesen - beginne mit Auswertung ");
                labelReaderStatusInfo.setStyle("-fx-background-color: GREEN; -fx-text-fill: white;");
                //chekc if webengine still loads data if not loadmethod
                //loadData to webview

                if (!waitForData) {
                loadZeitdatenToWebview(uidRead);
                } else {
                    System.out.println("Still loading data for "+uidRead);
                    nfcread.reset();

                }


            } //wenn uidRead keinen Fehler und nicht leer ist
            else if (nfcread.getValue() == "Error") {...

and here the code for wait for webview or stop ...

//start webengine
private void loadZeitdatenToWebview(String uid) {


    //wait till page loaded:
    engine = webviewer.getEngine();
    engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {


        if (newState == Worker.State.SUCCEEDED) {
            System.out.println("Workerstate Succeeded");
            System.out.println("waitforData: " +waitForData );
            // new page has loaded, process:
            this.waitForData = false;
            zeiterfassungErfolgreich();

            System.out.println("waitforData done reset: " +waitForData );

                }
        if (engine.getLoadWorker().getState() != Worker.State.RUNNING) {
            System.out.println("Workerstate Running ");
            System.out.println("load state while data request:" + engine.getLoadWorker().getState()); //SCHEDULED
            this.waitForData = true;
            zeiterfassungRunning();
        }

    });

    engine.load(httpHostforZeiterfassung + uid);
    //engine.load("http://10.0.0.11/nfcread.php?uid=" + uid);
    System.out.println("here: " + engine.getLoadWorker().getState());
}


private void zeiterfassungErfolgreich() {
    System.out.println("zeiterfassungErfolgreich");
    System.out.println("Fertig:" +waitForData);
    labelReaderStatusInfo.setText("Zeiterfassung erfolgreich...");
    //nfcread.reset();
}

private void zeiterfassungRunning() {
    System.out.println("zeiterfassungRunning check - for data within 15 seconds");
    //engine.getLoadWorker().cancel();
    PauseTransition pause = new PauseTransition(Duration.seconds(15));

    if (engine.getLoadWorker().getState() != Worker.State.SUCCEEDED) pause.setOnFinished(event -> {
        if (engine.getLoadWorker().getState() == Worker.State.SUCCEEDED) {pause.stop(); waitForData=false; }
        else {
            engine.executeScript("window.stop()");
            waitForData = false;
            labelReaderStatusInfo.setStyle("-fx-background-color:red;  -fx-text-fill: white;");
            labelReaderStatusInfo.setText("Error keine Verbindung zum Server!");
            nfcread.cancel();
        }
        //System.out.println("Running:" +waitForData);
    });
    pause.play();
    System.out.println("here2: " + engine.getLoadWorker().getState());


}


//end webengine

If I can use the pausetransition this way how can I prevent the waiting time if webview succeeded or better what should I use to make this work ? Thanks to @all here !!

Additional Info: my actual code gives me this output if I try to nfcread a card within those 15 seconds:

  • Waiting for a card..
  • Failed OFF
  • Event: 0BDF8D69
  • load state while data request:SCHEDULED
  • zeiterfassungRunning check - for data within 15 seconds
  • here2: SCHEDULED
  • here: RUNNING
  • Waiting for a card..
  • zeiterfassungErfolgreich
  • Fertig:true
  • load state while data request:SUCCEEDED
  • zeiterfassungRunning check - for data within 15 seconds
  • here2: SUCCEEDED
  • Failed OFF
  • Event: 0BDF8D69
  • Still loading dataWaiting for a card. << last line shows me that it is not allowed to load new content ...
2 Answers

There's at least a couple problems in your code that may be contributing to your overall problem.

  1. You don't stop the PauseTransition when the load succeeds (or fails).

    It looks like there's an attempt to implement this, but you do it in the onFinished handler of the animation. That handler is invoked when the animation has finished and is thus already stopped/stopping. To call stop() inside that handler is basically a no-op.

    It would probably be easier to reuse a single PauseTransition instance and simply start and stop it based on the running state of the load worker (e.g. by observing the worker's running property). You can also probably use the running status of the worker instead of your own waitForData flag.

  2. You add a new listener to the worker's state property every time you load a new web page, but you don't remove the old listeners.

    This means the work inside the listener is repeated, where the number of times it's repeated increases over time. You should only add the listener once when you are first initializing everything.

In some ways I believe you're thinking too linearly. Typically GUI programming is event-driven which is inherently asynchronous.

Here's a skeleton example of what I was hinting at above and in the question comments. You'll obviously need to rewrite things in order to integrate it into your application, but it should help you understand what's happening and hopefully find a solution.

import javafx.animation.PauseTransition;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.web.WebView;
import javafx.util.Duration;

public class Foo {

  private final String baseUrl;
  private final PauseTransition timeout;
  private final WebView webView;

  public Foo(String baseUrl) {
    this.baseUrl = baseUrl;
    timeout = new PauseTransition(Duration.seconds(15));
    webView = new WebView();

    Worker<?> worker = webView.getEngine().getLoadWorker();
    // observe 'running' property to start/stop timeout
    worker.runningProperty().addListener(this::loaderRunningChanged);
    // observe 'state' property to handle success/failure
    worker.stateProperty().addListener(this::loaderStateChanged);

    timeout.setOnFinished(
        e -> {
          worker.cancel(); // stop trying to load
          handleTimeout();
        });
  }

  public void loadData(String uid) {
    if (!webView.getEngine().getLoadWorker().isRunning()) {
      webView.getEngine().load(baseUrl + uid);
    }
  }

  private void loaderRunningChanged(
      ObservableValue<? extends Boolean> obs, boolean wasRunning, boolean isRunning) {
    if (isRunning) {
      System.out.println("Loading...");
      timeout.playFromStart(); // restart timeout countdown
    } else {
      timeout.stop(); // load completed, stop timeout countdown
    }
  }

  private void loaderStateChanged(
      ObservableValue<? extends State> obs, State oldState, State newState) {
    // switch expressions standardized in Java 14 (preview feature in Java 12 and 13)
    switch (newState) {
      case SUCCEEDED -> handleSuccess();
      case FAILED -> handleFailure();
    }
  }

  private void handleSuccess() {
    System.out.println("Load succeeded.");
    // ...
  }

  private void handleFailure() {
    System.out.println("Load failed.");
    // ...
  }

  private void handleTimeout() {
    System.out.println("Load timeout.");
    // ...
  }
}

Here is an example of what I think you should do.

  1. Try to load the engine.
  2. Create a global Worker.State variable that will be used to keep up with the state of the stateProperty listener. Use a Timeline to check the value of the Worker.State variable every second for 15 seconds.
/*
Code found here -> http://tutorials.jenkov.com/javafx/webview.html
*/
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Duration;

public class App extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    Worker.State workerState;
    Timeline oneSecondWonder;
    
    public void start(Stage primaryStage) {
        primaryStage.setTitle("JavaFX WebView Example");

        WebView webView = new WebView();

        webView.getEngine().getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
            workerState = newState;
        });        
        webView.getEngine().load("http://google5.com");//Use to see what happens after 15 seconds.
        //webView.getEngine().load("http://google.com");//Use to see what happens before 15 seconds.
        
        oneSecondWonder = new Timeline(
                 new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
                     System.out.println("Worker State: " + workerState);
                     switch(workerState)
                     {
                         case FAILED:
                             System.out.println("FAILED");
                             break;
                         case SUCCEEDED:
                             System.out.println("SUCCEEDED");
                             oneSecondWonder.stop();
                             System.out.println("I am stopping the Timeline and moving forward with code here!");
                             oneSecondWonder.stop();
                             //Next set of code here!
                             break;
                         case CANCELLED:
                             //Code here if needed!
                             break;
                         //Other cases if needed here!
                     }
        }));
        oneSecondWonder.setOnFinished((t) -> {
            System.out.println("The Timeline ended. I am alerting the user about his/her next options");
        });        
        oneSecondWonder.setCycleCount(15);
        oneSecondWonder.play();
        
        VBox vBox = new VBox(webView);
        Scene scene = new Scene(vBox, 960, 600);

        primaryStage.setScene(scene);
        primaryStage.show();

    }
}
Related