user6535530
user6535530

Reputation: 113

JavaFX accessing controller gives nullpointerexception

What i'm trying to achieve is getting a controller instance from the main method, so i can call methods of the controller from another class and update the fxml. Anyways here's my code:

Main class:

public class Main extends Application {

Controller controller;
@Override
public void start(Stage primaryStage) throws Exception{

    FXMLLoader fxmlLoader = new FXMLLoader();

    Parent root = fxmlLoader.load(getClass().getResource("sample.fxml"));
    controller = fxmlLoader.getController();

    primaryStage.setTitle("uPick Smart Service");
    primaryStage.setScene(new Scene(root, 1600, 600));

    primaryStage.show();

    ConnectionHandling connectionHandling = new ConnectionHandling();
    Thread X = new Thread (connectionHandling);
    X.start();

}

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

}

public Controller getController(){
    return controller;
}


}

My controller class:

public class Controller {

public HBox billbox;

public int childnr;


public void createBill() {
    System.out.println("Creating");
    TableView<Item> bill = new TableView<>();

    DropShadow dropShadow = new DropShadow();
    dropShadow.setRadius(5.0);
    dropShadow.setOffsetX(3.0);
    dropShadow.setOffsetY(3.0);
    dropShadow.setColor(Color.color(0.4, 0.5, 0.5));

    VBox fullbill = new VBox();
    fullbill.setPadding(new Insets(1, 1, 1, 1));
    fullbill.getStyleClass().add("fullbill");

    TableColumn<Item, String> nameColumn = new TableColumn<>("Emri");
    nameColumn.setMinWidth(200);
    nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));

    TableColumn<Item, String> quantsColumn = new TableColumn<>("Sasia");
    quantsColumn.setMinWidth(50);
    quantsColumn.setCellValueFactory(new PropertyValueFactory<>("quants"));

    double tablewidth = nameColumn.getWidth() + quantsColumn.getWidth();

    Label tablenrlabel = new Label("Table 5");
    tablenrlabel.getStyleClass().add("tablenr-label");
    tablenrlabel.setMinWidth(tablewidth);

    Button closebutton = new Button("Mbyll");
    closebutton.setMinWidth(tablewidth);
    closebutton.getStyleClass().add("red-tint");

    bill.setItems(getItem());
    bill.setMinWidth(256);
    bill.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
    bill.getColumns().addAll(nameColumn, quantsColumn);

    fullbill.setEffect(dropShadow);
    fullbill.getChildren().addAll(tablenrlabel, bill, closebutton);

    billbox.getChildren().addAll(fullbill);

    childnr += 1;

    //Loops over every button in every vbox and gives it a seperate listener (the index of the button is hardcoded so it can cause problems if you add more items)
    for (int i = 0; i < childnr; i++) {
        VBox box = (VBox) billbox.getChildren().get(i);
        Button btn = (Button) box.getChildren().get(2); //if sudden issues change this
        btn.setId(Integer.valueOf(i).toString());
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                int index = billbox.getChildren().indexOf(btn.getParent());
                billbox.getChildren().remove(index);
                childnr -= 1;
                System.out.println(btn.getId());
            }
        });
    }
    System.out.println("done");
}

}

And the class trying to call controller method:

public class TakeOrder implements Runnable {
Socket SOCK;
Controller controller;

//OrderIndexes: "Order","Waiter","Payment"

private int NonConnectedDatas = 2;

public TakeOrder(Socket X){
    this.SOCK = X;
}

public void CheckConnection() throws IOException{
    System.out.println("Checking connection");
    if(!SOCK.isConnected()){
        System.out.println("Dissconectiong");
        for(int i = 0; i < ConnectionHandling.ConnectionArray.size(); i++){
            if(ConnectionHandling.ConnectionArray.get(i) == SOCK){
                ConnectionHandling.ConnectionArray.remove(i);
            }
        }
    }
}

public void run(){
    try{
        try{
            CheckConnection();


            ObjectInputStream ob = new ObjectInputStream(SOCK.getInputStream());

            String[] structuredArray = (String[])ob.readObject();

            String tablenr = structuredArray[0];
            String index = structuredArray[1];

            ArrayList<String> names = new ArrayList<>();
            ArrayList<String> quants = new ArrayList<>();

            int a = 0;
            int b = 0;
            switch (index) {
                case "Order":
                    for (int i = NonConnectedDatas; i < structuredArray.length; i++) {
                        if (i % 2 == 0) {
                            names.add(a, structuredArray[i]);
                            System.out.println(names.get(a));
                            a++;
                        } else {
                            quants.add(b, structuredArray[i]);
                            System.out.println(quants.get(b));
                            b++;
                        }
                    }
                    break;
            }

            Platform.runLater(new Runnable() {
                @Override
                public void run() {

                }
            });
        }finally{
            SOCK.close();
        }
    }catch(Exception X){
        System.out.print(X);
    }
}



}

And here's my error message:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at sample.TakeOrder$1.run(TakeOrder.java:80)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)

The ConnectionHandling class:

public class ConnectionHandling implements Runnable{

public static ArrayList<Socket> ConnectionArray = new ArrayList<Socket>();


public void run(){
    System.out.println("Starting");
    try{
        final int PORT = 60123;
        ServerSocket SERVER = new ServerSocket(PORT);
        System.out.println("Waiting for clients");
        while(true){
            Socket SOCK = SERVER.accept();
            ConnectionArray.add(SOCK);

            TakeOrder ORDER = new TakeOrder(SOCK);
            Thread X = new Thread(ORDER);
            X.setDaemon(true);
            X.start();
        }
    }catch(Exception x){
        System.out.print(x);
    }

}

Upvotes: 1

Views: 2206

Answers (2)

James_D
James_D

Reputation: 209225

First, you are using the static FXMLLoader.load(URL) method. Because it's static, the controller property of the FXMLLoader instance you created isn't initialized by calling this load method. So controller in Main will be null.

Instead, set the location and use the instance method load():

@Override
public void start(Stage primaryStage) throws Exception{

    FXMLLoader fxmlLoader = new FXMLLoader();
    fxmlLoader.setLocation(getClass().getResource("sample.fxml"));

    Parent root = fxmlLoader.load();
    controller = fxmlLoader.getController();

    primaryStage.setTitle("uPick Smart Service");
    primaryStage.setScene(new Scene(root, 1600, 600));

    primaryStage.show();



}

This doesn't entirely solve your problem, though. When you launch the application, JavaFX will create an instance of Main and call start() on that instance. With the changes above, the controller field of that instance will be properly initialized.

However, in TakeOrder.run() you create another instance of Main:

Main main = new Main();

The controller field for that instance won't be initialized (and even if you do initialize it, it's not the same as the instance you want). So you really need to arrange for TakeOrder to access the controller instance created in the start method.

Here is the most straightforward fix to your code to make that work:

public class ConnectionHandling implements Runnable{

    private final Controller controller ;

    public ConnectionHandling(Controller controller) {
        this.controller = controller ;
    }

    // ...

    public void run(){

        // existing code ...

        TakeOrder ORDER = new TakeOrder(SOCK, connection);

        // ...
    }
}

and

public class TakeOrder implements Runnable {

    Socket SOCK;
    Controller controller;

    //OrderIndexes: "Order","Waiter","Payment"

    private int NonConnectedDatas = 2;

    public TakeOrder(Socket X, Controller controller){
        this.SOCK = X;
        this.controller = controller ;
    }

    // ...

    public void run() {

        // ...

        Platform.runLater(controller::createBill);

        // ...
    }

}

and finally

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{

        FXMLLoader fxmlLoader = new FXMLLoader();

        Parent root = fxmlLoader.load(getClass().getResource("sample.fxml"));
        Controller controller = fxmlLoader.getController();

        primaryStage.setTitle("uPick Smart Service");
        primaryStage.setScene(new Scene(root, 1600, 600));

        primaryStage.show();

        ConnectionHandling connectionHandling = new ConnectionHandling(controller);
        Thread X = new Thread (connectionHandling);
        X.start();

    }

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

    }

    public Controller getController(){
        return controller;
    }


}

In general for applications such as this, you probably want to think about using a MVC or MVP approach (i.e. you need a model class, which would hold the services, such as your ConnectionHandler).

You might also be interested in this article on integrating services in JavaFX.

Upvotes: 5

Ezekiel Baniaga
Ezekiel Baniaga

Reputation: 953

If you manually instantiate your Application which in this case is the Main class, then the start() function won't be called. Try launching your application with

Application.launch

instead of

Main main = new Main()

Also, the launch method does not return until the application has exited. Thus, you may want to somewhat reorder your call to getController()

There's also another way to manage the instance of your controller and that is to use setController(Object controller) from your FXMLLoader.


Docs:

https://docs.oracle.com/javase/8/javafx/api/javafx/application/Application.html#launch-java.lang.Class-java.lang.String...-

https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/FXMLLoader.html#setController-java.lang.Object-

Upvotes: 0

Related Questions