2

Im trying to pass an object from one controller to another. The rowData-object which I am passing into BookViewController is crucial for the initialize-method to work, if the data isn't set there's going to be a NullPointerException which indeed is happening in my case.

It seems that the initialize-method runs BEFORE the setRowData-method. And as I said, the initialize-method is dependent that the setRowData-method has executed.

What am I missing out on here?

Main.java

public class Main extends Application {

    private static Stage primaryStage;
    private static BorderPane mainLayout;

    @Override
    public void start(Stage primaryStage) throws Exception {
        Main.primaryStage = primaryStage;
        Main.primaryStage.setTitle("BookingApp");
        showMainView();
    }

    private void showMainView() throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("MainView.fxml"));
        mainLayout = loader.load();
        Scene scene = new Scene(mainLayout);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void showBookView(Trip rowData) throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("BookView.fxml"));
        BorderPane bookTrip = loader.load();

        BookViewController bvc = loader.getController();
        // The below method never runs.. why?
        bvc.setRowData(rowData);       

        Stage bookStage = new Stage();                
        bookStage.setTitle("BookingApp");
        bookStage.initModality(Modality.WINDOW_MODAL);
        bookStage.initOwner(primaryStage);
        Scene scene = new Scene(bookTrip);
        bookStage.setScene(scene);
        bookStage.showAndWait();
    }

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

BookViewController.java

public class BookViewController implements Initializable {

    private String dbUrl = "jdbc:postgresql://localhost:5432/BookingApp";

    private String dbUsername = "postgres";

    private String dbPassword = "secret";

    @FXML
    private TextField personnr;

    @FXML
    private TextField name;

    @FXML
    private TextField email;

    @FXML
    private TextField telnr;

    @FXML
    private Button addTraveler;

    @FXML
    private Button removeTraveler;

    @FXML
    private TableView<Traveler> travelers;

    @FXML
    private TableColumn<?, ?> personnrColumn;

    @FXML
    private TableColumn<?, ?> nameColumn;

    @FXML
    private TableColumn<?, ?> emailColumn;

    @FXML
    private TableColumn<?, ?> telnrColumn;

    @FXML
    private TableView<Trip> trips;

    @FXML
    private TableColumn<?, ?> originColumn;

    @FXML
    private TableColumn<?, ?> destinationColumn;

    @FXML
    private TableColumn<?, ?> departureColumn;

    @FXML
    private TableColumn<?, ?> arrivalColumn;

    @FXML
    private TableColumn<?, ?> driverColumn;

    @FXML
    private TableColumn<?, ?> priceAmountColumn;

    @FXML
    private TableColumn<?, ?> seatsColumn;

    @FXML
    private Button bookTrip;

    private Trip rowData;

    private ObservableList<Trip> tripData;

    public void setRowData(Trip rowData) {
        this.rowData = rowData;
        // The below println is never printed to the console..
        System.out.println("Test");
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        addTraveler.setDisable(true);
        removeTraveler.setDisable(true);
        populateTravelPlan();
    }

    @FXML
    private void populateTravelPlan() {
        try {
            Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
            tripData = FXCollections.observableArrayList();
            ResultSet rs = null;
            System.out.println(rowData.getTrip1()); // Here it breaks
            System.out.println(rowData.getTrip2());
            System.out.println(rowData.getTrip3());
            if(rowData.getTrip1() != 0 && rowData.getTrip2() == 0 && rowData.getTrip3() == 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            } else if(rowData.getTrip1() != 0 && rowData.getTrip2() != 0 && rowData.getTrip3() == 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip2() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            } else if(rowData.getTrip1() != 0 && rowData.getTrip2() != 0 && rowData.getTrip3() != 0) {
                rs = conn.createStatement().executeQuery(
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip1() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip2() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "union\r\n" + 
                        "select trip.tripid, connexion.origin, connexion.destination, trip.departure, trip.arrival, trip.driverpnr, trip.priceamount, trip.seats\r\n" + 
                        "from trip, connexion\r\n" + 
                        "where trip.tripid = " + rowData.getTrip3() + "\r\n" + 
                        "and trip.connexionid = connexion.connexionid\r\n" + 
                        "order by departure;");
            }
            tripData.add(new Trip(rs.getInt(1), rs.getString(2), rs.getString(3), ""+rs.getTimestamp(4), ""+rs.getTimestamp(5), rs.getString(6), ""+rs.getInt(7), ""+rs.getInt(8)));
            while(rs.next()) {
                tripData.add(new Trip(rs.getInt(1), rs.getString(2), rs.getString(3), ""+rs.getTimestamp(4), ""+rs.getTimestamp(5), rs.getString(6), ""+rs.getInt(7), ""+rs.getInt(8)));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        trips.setItems(tripData);
    }
} 

The Exception

javafx.fxml.LoadException: 
/C:/Users/David/Desktop/eclipse/BookingApp1/bin/booking/BookView.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at booking.Main.showBookView(Main.java:35)
    at booking.MainViewController$1.handle(MainViewController.java:227)
    at booking.MainViewController$1.handle(MainViewController.java:1)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3470)
    at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3766)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:381)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:417)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:416)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NullPointerException
    at booking.BookViewController.populateTravelPlan(BookViewController.java:109)
    at booking.BookViewController.initialize(BookViewController.java:100)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
    ... 39 more 
Slamdunk
  • 424
  • 1
  • 8
  • 20
  • 3
    Haven't you answered your own question? The `initialize()` method is invoked by the `FXMLLoader`, as part of the execution of `load()`, so as you observe, it's called before you get a chance to call `setRowData()`. Your options are either to simply avoid the initialize method depending on the call to `setRowData()` (just move the call to `populateTravelPlan()` to the `setRowData()` method), or use a controller factory. – James_D Apr 04 '18 at 13:18
  • 3
    See also https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml – fabian Apr 04 '18 at 13:22
  • Ah.. why didnt I just thought of that.. Thanks =) – Slamdunk Apr 04 '18 at 13:31

1 Answers1

2

It seems that the initialize-method runs BEFORE the setRowData-method.

This is indeed correct. The initialize() method is called by the FXMLLoader as part of the execution of load(), which necessarily happens before you have a chance to call setRowData(...).

What am I missing out on here?

Nothing, really.

There are a couple of possible fixes. One is simply to avoid having the initialize() method depend on the data you pass to setRowData(), and move any code that does depend on those data to the latter method. This is pretty trivial in this case:

public void setRowData(Trip rowData) {
    this.rowData = rowData;
    populateTravelPlan();
}

@Override
public void initialize(URL arg0, ResourceBundle arg1) {
    addTraveler.setDisable(true);
    removeTraveler.setDisable(true);
    // don't do this here:
    // populateTravelPlan();
}

If you don't like that solution, one other option is to set the controller in code instead of in the FXML. Briefly, you would remove the fx:controller attribute from the FXML file, and then when you load the FXML, create a controller instance, call setRowData() (or otherwise initialize the row data, e.g. via a constructor), and call setController() on the FXMLLoader. This solution is described in detail in Passing Parameters JavaFX FXML.

One disadvantage of that solution is that if you use Scene Builder for the FXML design, Scene Builder will lose some functionality (as it's no longer aware of the class used to create the controller). If you want to keep that functionality, you can use a controller factory.

For that solution, I would modify the controller so that the row data were passed to a constructor:

public class BookViewController implements Initializable {

    // @FXML-annotated fields omitted...

    private final Trip rowData;

    public BookViewController(Trip rowData) {
        this.rowData = rowData ;
    }

    // remove this, it is now initialized in the constructor
    // public void setRowData(Trip rowData) {
    //     this.rowData = rowData;
    //     // The below println is never printed to the console..
    //     System.out.println("Test");
    // }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        addTraveler.setDisable(true);
        removeTraveler.setDisable(true);
        populateTravelPlan();
    }

    // etc...
}

This constructor will prevent the FXMLLoader from creating a controller instance using its default method, so supply a controller factory to the loader to override how the controller is created:

public static void showBookView(Trip rowData) throws IOException {
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(Main.class.getResource("BookView.fxml"));
    loader.setControllerFactory(type -> {
        if (type == BookViewController.class) {
            return new BookViewController(rowData);
        }
        // default behavior: need this in case there are <fx:include> in the FXML
        try {
            return type.getConstructor().newInstance();
        } catch (Exception exc) {
            // fatal...
            throw new RuntimeException(exc);
        }
    });
    BorderPane bookTrip = loader.load();

    Stage bookStage = new Stage();                
    bookStage.setTitle("BookingApp");
    bookStage.initModality(Modality.WINDOW_MODAL);
    bookStage.initOwner(primaryStage);
    Scene scene = new Scene(bookTrip);
    bookStage.setScene(scene);
    bookStage.showAndWait();
}
James_D
  • 201,275
  • 16
  • 291
  • 322