1

I realise that this question has been asked before but none of the solutions have worked for me. I am starting a thread from my controller, and from there the thread gets some data from my database. The thread sends the data to my interface which is implemented in the controller. I get a null pointer exception when trying to access any of my JavaFX elements from there.

Here is my controller:

public class SettingsPage implements PrizeReceiver{

    @FXML
    AnchorPane settingsAnchor;
    @FXML
    ListView<String> prizeList;
    @FXML
    TextField prizeField;
    @FXML
    Button load;

    void init(AnchorPane rootPane){

       BasicConfigurator.configure();

        AnchorPane root = null;

        try {

            File file = new File(System.getProperty("user.dir") + "/src/main/res/settings.fxml");

        root = FXMLLoader.load(file.toURI().toURL());
    } catch (IOException e) {
        e.printStackTrace();
    }

    if (root != null) {
        rootPane.getChildren().setAll(root);
    }

    initPrizeList();

}

private void initPrizeList() {

    GetPrizesThread getPrizesThread = new GetPrizesThread(Database.firebaseDatabase, this);
    getPrizesThread.getThread().start();

}

@FXML
void addPrize(){

    Database.createPrize(prizeField.getText());

}

@Override
public void receivePrizes(ArrayList<String> prizes) {

    ObservableList<String> items = FXCollections.observableArrayList();
    items.addAll(prizes);

    //Iv'e tried this if else with every fxml object and always prints null
    if(prizeField==null){
        System.out.println("NULL");
    }else {
        prizeList.getItems().addAll(items);
    }

}
}

Here is the Thread:

public class GetPrizesThread implements Runnable {

private boolean finished = false;

private final Thread thread;
private FirebaseDatabase firebaseDatabase;
private PrizeReceiver prizeReceiver;

GetPrizesThread(FirebaseDatabase firebaseDatabase, PrizeReceiver prizeReceiver){
    this.firebaseDatabase = firebaseDatabase;
    this.prizeReceiver = prizeReceiver;
    thread = new Thread(this);
}

@Override
public void run() {

    ArrayList<String> prizes = new ArrayList<>();

    DatabaseReference databaseReference = firebaseDatabase.getReference("prizes");

    databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {

            HashMap map = (HashMap) snapshot.getValue();

            Set keySet = map.keySet();

            ArrayList<String> listOfKeys = new ArrayList<String>(keySet);

            prizes.clear();
            prizes.addAll(listOfKeys);

            finished = true;

        }

        @Override
        public void onCancelled(DatabaseError error) {
            finished = true;
        }
    });

    while(!finished) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    prizeReceiver.receivePrizes(prizes);

}

Thread getThread() {
    return thread;
}

}

Here is the FXML file(built with scene builder):

<AnchorPane fx:id="settingsAnchor" prefHeight="454.0" prefWidth="700.0" 
xmlns="http://javafx.com/javafx/8.0.161" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="SettingsPage">
   <children>
  <GridPane layoutX="-2.0" layoutY="-1.0" prefHeight="400.0" prefWidth="600.0">
    <columnConstraints>
      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
    </columnConstraints>
    <rowConstraints>
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    </rowConstraints>
     <children>

        <ListView fx:id="prizeList" prefHeight="400.0" prefWidth="246.0" GridPane.rowSpan="3">
           <GridPane.margin>
              <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
           </GridPane.margin>
        </ListView>

        <Button mnemonicParsing="false" onAction="#addPrize" text="Add Prize" GridPane.columnIndex="1" GridPane.rowIndex="2">
           <GridPane.margin>
              <Insets left="8.0" top="40.0" />
           </GridPane.margin>
        </Button>
        <TextField fx:id="prizeField" promptText="Prize Name" GridPane.columnIndex="1" GridPane.rowIndex="2">
           <GridPane.margin>
              <Insets bottom="15.0" left="8.0" right="8.0" />
           </GridPane.margin>
        </TextField>
     </children>
  </GridPane>

When I don't have the if/else in place this is my error:

Exception in thread "Thread-5" java.lang.NullPointerException
at SettingsPage.receivePrizes(SettingsPage.java:74)
at GetPrizesThread.run(GetPrizesThread.java:59)
at java.lang.Thread.run(Thread.java:748)

line 74 is

prizeList.getItems().addAll(items);

Thanks for any help.

7
  • Which line is line 26 of GetPrizesThread? Commented Oct 19, 2018 at 1:08
  • Oops, I just realized that I posted the wrong error, I'll post the correct one Commented Oct 19, 2018 at 1:20
  • Check that you didn't import java.awt.TextField instead of javafx.scene.control.TextField in SettingsPage class. Commented Oct 19, 2018 at 1:24
  • Err, is it prizeList or prizeField that is null? Anyways, check that your controller imported the correct classes for the controls. Commented Oct 19, 2018 at 1:25
  • Also show us how that FXML is being loaded. Commented Oct 19, 2018 at 1:33

1 Answer 1

3

A call to SettingsPage.init results in

root = FXMLLoader.load(file.toURI().toURL());

creating a new instance of SettingsPage. FXMLLoader automatically uses the constructor not taking parameters to create a controller instance, if the fx:controller attribute is present in the fxml. Objects in the fxml are injected to this new instance. However GetPrizesThread uses the old SettingsPage instance.

You can fix this by passing the controller to FXMLLoader before loading the fxml:

FXMLLoader loader = new FXMLLoader(file.toURI().toURL());
loader.setController(this);
root = loader.load();

You need to remove the fx:controller attribute from the fxml for this to work.


There's a different issue in your code however:

The GUI must not be accessed by threads other than the JavaFX application thread, but GetPrizesThread runs on a different thread. You need to do the GUI updates using Platform.runLater (or via some other mechanism resulting in updates running on the application thread):

@Override
public void receivePrizes(ArrayList<String> prizes) {
    // do update on the javafx application thread
    Platform.runLater(() -> {
        prizeList.getItems().addAll(prizes);
    });
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.