1

I am trying to make a login page in JavaFX with a little progress bar. But I everytime I want to check the credentials the Task fails and throws me into setOnFailed() method. I have no idea what makes it change it's state or how to make it work.

Here is my code so far:

public class Login extends Base_controller implements Initializable{
   private ProgressBar pb = new ProgressBar();
    private TextField username;
    private PasswordField password;

    public void start(Stage primaryStage) throws Exception {

        // Setting structure and components
        Label username_lbl = new Label();
        Label password_lbl = new Label();
        username_lbl.setText("Username:");
        password_lbl.setText("Password:");

        username = new TextField();
        password = new PasswordField();

        //some code

        VBox root = new VBox();
        root.setAlignment(Pos.CENTER);
        root.getChildren().addAll(container0, container1, container2, container3);
        root.setSpacing(30);


        // button action listener
        test.setOnAction(e->{
            Platform.runLater(() -> {
                root.getChildren().add(pb);
            });

            Task<Void> validatePassword = new Task<Void>(){
                @Override
                protected Void call() throws Exception {
                    validatePassword(password, username);
                    return null;
                }
            };

            validatePassword.setOnSucceeded(ee->{ // when it finishes successfully
                try {
                    root.getChildren().remove(pb); // remove the progress bar
                } catch (Exception e1) {
                    e1.printStackTrace();
                }

                User = dbHandler.getPersonByUsername(username.getText()); // Set up global variable
                Role = dbHandler.getPersonRole(User.getEmail());
                MainStage = (Stage) anchorPane.getScene().getWindow();
                Parent parent = null;
                try {
                    parent = FXMLLoader.load(getClass().getResource("/menu.fxml"));
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                Scene scene = new Scene(parent);
                MainStage.setScene(scene);
                MainStage.setTitle("Menu | Administration of Musical Courses");
                MainStage.show();
            });

            validatePassword.setOnFailed(eee->{ // if it fails (that will be indicated by the returned alert message)
                System.out.println("Failed");
                try {
                    root.getChildren().remove(pb); // remove it anyway but inform your user
                    Alert alert = new Alert(Alert.AlertType.ERROR, "Wrong Password", ButtonType.OK);
                    alert.showAndWait();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            });

            new Thread(validatePassword).start(); // add the task to a thread and start it
        });

        Scene scene = new Scene(root, 600,400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Login | Administration of Musical Courses");

        primaryStage.show();
    }

    // validate here in this method
    public void validatePassword(TextField password, TextField username)
        if (checkLogin()) {
            new Alert(Alert.AlertType.ERROR);
        }
    }

    public boolean checkLogin(){
        return BCrypt.checkpw(password.getText(), dbHandler.getLoginByUsername(username.getText()).getPassword());
    }
}

It always fails in the validatePassword method when getting the data from database. Is it possible that calling the database somehow makes the Task fail? How do I change the code to make it work?

Tomáš Zajda
  • 147
  • 1
  • 15

1 Answers1

5

A task enters the failed state if and only if an exception is thrown from the call() method. You can put validatePassword.getException().printStackTrace() in the onFailed handler to see the stack trace of the exception, which you can then read to see what went wrong:

validatePassword.setOnFailed(eee->{ // if it fails (that will be indicated by the returned alert message)
    System.out.println("Failed");
    validatePassword.getException().printStackTrace();
    try {
        root.getChildren().remove(pb); // remove it anyway but inform your user
        Alert alert = new Alert(Alert.AlertType.ERROR, "Wrong Password", ButtonType.OK);
        alert.showAndWait();
    } catch (Exception e1) {
        e1.printStackTrace();
    }
});

I do not really understand your validatePassword() method:

if (checkLogin()) {
    new Alert(...);
}

If checkLogin() returns true, this will create an Alert, but will not actually use it (what are you really intending here...?). Note that this method is called from the background thread (because it is called from the task's call() method), and it is illegal to create an Alert if you are not on the FX Application Thread. So I suspect this is causing the exception to be thrown, which is sending you into the onFailed handler: checking the stack trace will confirm this.

A more natural approach would be to have the task return a Boolean representing whether or not the login was successful, which you could then handle in the onSucceeded handler:

test.setOnAction(e->{
    Platform.runLater(() -> {
        root.getChildren().add(pb);
    });

    Task<Boolean> validatePassword = new Task<Boolean>(){
        @Override
        protected Boolean call() throws Exception {
            return checkLogin();
        }
    };

    validatePassword.setOnSucceeded(ee->{ // when it finishes successfully
        // this cannot throw an exception, there is no need for a try-catch;
        root.getChildren().remove(pb); // remove the progress bar

        if (validatePassword.getValue()) {
            User = dbHandler.getPersonByUsername(username.getText()); // Set up global variable
            Role = dbHandler.getPersonRole(User.getEmail());
            MainStage = (Stage) anchorPane.getScene().getWindow();
            Parent parent = null;
            try {
                parent = FXMLLoader.load(getClass().getResource("/menu.fxml"));
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            Scene scene = new Scene(parent);
            MainStage.setScene(scene);
            MainStage.setTitle("Menu | Administration of Musical Courses");
            MainStage.show();
        } else {
            // handle failed login... this is on the FX Application Thread so 
            // you can show an Alert...
            Alert alert = new Alert(Alert.AlertType.ERROR, "Wrong Password", ButtonType.OK);
            alert.show();
        }
    });

    validatePassword.setOnFailed(eee->{ 
        // exception was thrown trying to validate password
        validatePassword.getException().printStackTrace();
        // inform user something went wrong...
        Alert alert = new Alert(Alert.AlertType.ERROR, "Something went wrong", ButtonType.OK);
        alert.show();
    });

    new Thread(validatePassword).start(); // add the task to a thread and start it
});
James_D
  • 201,275
  • 16
  • 291
  • 322
  • &James_D Thank you, I am just learning multithreading and I thought the Task class worked different. I changed my code and it works now. – Tomáš Zajda May 23 '17 at 19:54
  • @TomášZajda See [documentation for the superclass, `Worker`](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Worker.html) for a description of the lifecycle. – James_D May 23 '17 at 19:55