user6535530 user6535530 - 3 months ago 24
Java Question

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);
}

}

Answer

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.