1

What I'm trying to do is have a single class that maintains a static ObservableList of countries. I want to display these countries in a ComboBox. I've got this part working fine. Now, I also want to enable the user to add new countries to the list. So, there is a button beside the combo box that will show another dialog allowing entry of another country name. After the user enters the country name and clicks save, I would like the single static ObservableList to be updated with the new country and then it show up in the ComboBox. This part is not happening.

I'll show what DOES work, and what does not.

Saving a reference to the static list and updating that works. Like so:

public class CustomerController implements Initializable {

    private ObservableList<Country> countryList;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        countryList = Country.getCountryList();
        comboCountry.setItems(countryList);
    }

    ...

    // Fired when clicking the "new country" button
    @FXML
    void handleNewCountry(ActionEvent event) {
        Country country = new Country();
        country.setCountry("Austria");
        countryList.add(country);
    }
}

This is what I would like to do, however it does not work:

public class CustomerController implements Initializable {

    @FXML
    private ComboBox<Country> comboCountry;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        comboCountry.setItems(Country.getCountryList());
    }

    @FXML
    void handleNewCountry(ActionEvent event) {
        showScene("Country.fxml", "dialog.newCountry");
    }

    private void showScene(String sceneResource, String titleResource) {
        try {
            FXMLLoader loader = new FXMLLoader(
                    getClass().getResource(sceneResource),
                    resourceBundle
            );
            Scene scene = new Scene(loader.load());
            getNewStage(resourceBundle.getString(titleResource), scene).showAndWait();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Stage getNewStage(String title, Scene scene) {
        Stage stage = new Stage();
        stage.setTitle(title);
        stage.setResizable(false);
        stage.setScene(scene);
        stage.initOwner(rootPane.getScene().getWindow());
        stage.initModality(Modality.APPLICATION_MODAL);
        return stage;
    }
}

The Country class:

public class Country extends BaseModel {
    private int countryID;
    private StringProperty country;
    private static ObservableList<Country> countryList; // The static observable list

    public Country() {
        countryList = FXCollections.observableArrayList();
        country = new SimpleStringProperty();
    }

    public int getCountryID() {
        return countryID;
    }

    public void setCountryID(int countryID) {
        this.countryID = countryID;
    }

    public StringProperty countryProperty() {
        return this.country;
    }

    public String getCountry() {
        return this.country.get();
    }

    public void setCountry(String country) {
        this.country.set(country);
    }

    public boolean equals(Country country) {
        if (this.getCountry().compareToIgnoreCase(country.getCountry()) != 0) {
            return false;
        }
        return true;
    }

    public static ObservableList<Country> getCountryList() {
        if (countryList.size() < 1) {
            updateCountryList();
        }
        return countryList;
    }

    public static void updateCountryList() {
        countryList.clear();
        ArrayList<Country> daoList = CountryDao.listCountries();
        for (Country country : daoList) {
            countryList.add(country);
        }
    }

    @Override
    public String toString()  {
        return this.getCountry();
    }
}

And the dialog for entering a new country:

public class CountryController implements Initializable {
    @FXML
    private TextField textCountry;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }    

    @FXML
    void handleSave(ActionEvent event) {
        Country country = new Country();
        country.setCountry(textCountry.getText().trim());
        CountryDao.insert(country); // Insert the country into the database
        Country.updateCountryList(); // Update the static ObservableList
        close();
    }

    @FXML
    void handleCancel() {
        close();
    }

    void close() {
        final Stage stage = (Stage) textCountry.getScene().getWindow();
        stage.close();        
    }
}

So, my theory is that somehow the ComboBox is creating a new instance of the ObservableList when setItems is called. I'm really not sure though. A static object should only have one instance, so updating it from anywhere should update that ComboBox. Anyone know what's up with this?

nybblesAndBits
  • 273
  • 2
  • 9

1 Answers1

2

You're creating a new ObservableList instance every time the Country constructor is invoked. This way a list different to the one used with the ComboBox is modified.

If you really need to keep the list of countries in a static field (this is considered bad practice), you should make sure to only create a single ObservableList:

private static final ObservableList<Country> countryList = FXCollections.observableArrayList();

(Remove the assignment of this field from the constructor too.)

fabian
  • 80,457
  • 12
  • 86
  • 114
  • I knew it was something small biting me. Thanks for pointing it out! Though I do wonder why keeping a list like this in a single static list would be considered bad practice. The general idea I had was to have a single source of truth when it comes to the list of countries. – nybblesAndBits Aug 17 '18 at 17:27
  • @nybblesAndBits https://stackoverflow.com/questions/7026507/why-are-static-variables-considered-evil – fabian Aug 17 '18 at 18:10