1

In my JavaFx application I want to update my BarChart whenever calculate button is clicked. The problem is that I am getting:

java.lang.NullPointerException at application.StatController.setActivityData(StatController.java:47)

It is always pointing to:`xAxis.setCategories(optionsNames); But it has elements on the list (See printscreen:https://image.ibb.co/b9YO8Q/Capture.png

In my StatController class I have setActivityData which is called from FormController class.

StatController class:

public class StatController {

    @FXML
    private BarChart<String, Double> barChart;
    @FXML
    private CategoryAxis xAxis;
    Activities activities = new Activities();
    private Map<String, List<Double>> uniqueActivityOptions = new HashMap<>();      
    private ObservableList<String> optionsNames = FXCollections.observableArrayList();      

    public StatController(){}

    @FXML
    private void initialize() { 
    }

    public void setActivityData(Activities activitiesList) {
        for(Activity activity : activities.getActivityList()) {
            String optionName = activity.getOption();
            if(uniqueActivityOptions.containsKey(optionName)) {
                uniqueActivityOptions.get(optionName).add((double) activity.getNumber());
            } else {
                List<Double> activityOptionList = new ArrayList<>();
                activityOptionList.add((double) activity.getNumber());
                uniqueActivityOptions.put(optionName, activityOptionList);
            }
        }

        for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
            optionsNames.add(entry.getKey());
        }

        xAxis.setCategories(optionsNames);

        XYChart.Series<String, Double> series = new XYChart.Series<>();
        for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
            Double average = calculateAverage(entry.getValue());
            series.getData().add(new XYChart.Data<>(entry.getKey().toString(), average));
        }   
        barChart.getData().add(series); 
    }
    private double calculateAverage(List<Double> values) {
        double result = 0;
        for (Double value : values) {
            result += value;
        }
        return result / values.size();
    }
}

FormController class:

public class FormController {

    Activities act = new Activities();
    List<Activity> activities = act.getActivityList();

    private ObservableList<String> opt = FXCollections.observableArrayList(
                "Option 1",
                "Option 2",
                "Option 3"
            );
    @FXML
    private Button calculateButton;
    @FXML
    private TextField numberField;
    @FXML
    private ComboBox<String> options;
    private String selectedOption;

    @FXML
    private void initialize() {

        options.getItems().addAll(opt);
        options.getSelectionModel().selectedItemProperty()
        .addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                selectedOption = newValue;  
            }

        }); 
    }
    @FXML
    public void calculateButtonClicked(){   
        activities.add(new Activity(selectedOption, Integer.parseInt(numberField.getText())));
        StatController  sc = new StatController();
        sc.setActivityData(act);
    }
}

I tested setActivityData method in StatsController and it is working correctly when I am passing Activities.

Please advise what to change in the code to pass and update BarChart. I know that this is something trivial but I really don't know how to do it.

` Thank you very much for the help!

erni
  • 45
  • 1
  • 10
  • 1
    It's the exact same as every other NPE question asked. Where do you initialize xAxis variable in the code that throws the NPE? Where do you ever assign to it a viable reference? – Hovercraft Full Of Eels Jun 28 '17 at 21:41
  • I know what is nullpointerexception but the strings in ObservableList are passed to xAxis.setCategories(optionsNames). xAxis is initialized by @FXML which is used in JavaFX. You can see that list of observables are passed from ObservableList to xAxis. – erni Jun 28 '17 at 22:21

1 Answers1

0

Your problem is in FormController#calculateButtonClicked(). You create a new instance of StatController manually and then call the setActivityData() method. This is not how JavaFX works and will result in the xAxis member being null, hence your NullPointerException.

Controller members annotated with the @FXML annotation will be injected by the FXMLLoader class when you call the FXMLLoader#load() method. You will need to use the FXMLLoader class to load the .fxml file that corresponds to the StatController class, which will then create an instance of your StatController object for you and will also inject your xAxis and barChart instances.

Here's a quick (not production ready) example that you will have to adapt for your specific scenario:

FXMLLoader loader = new FXMLLoader();
loader.setLocation(filePath); // your path to the .fxml file
loader.load();
StatController controller = (StatController) loader.getController();
controller.setActivityData(activities); // your activities data

How and when you do that depends on how your scene graph is setup. Looking at your source code, I would suggest the following changes:

MainController.java

public class MainController
        implements Initializable {


    @FXML
    private TabPane tabPane;
    @FXML
    private Tab formTabPage;
    @FXML
    private FormController formTabController; // change name to this
    @FXML
    private Tab statsTabPage;
    @FXML
    private StatController statsTabController; // change name to this

    private MainApp mainApp;


    public void setMainApp(MainApp mainApp) {

        this.mainApp = mainApp;
    }


    @Override // rename your init() method to this
    public void initialize(URL location, ResourceBundle resources) {

        // add this line
        formTabController.setStatController(statTabController);
    }
}

FormController.java

public class FormController {


    Activities act = new Activities();
    List<Activity> activities = act.getActivityList();

    private ObservableList<String> opt = FXCollections.observableArrayList(
            "Option 1",
            "Option 2",
            "Option 3"
    );
    @FXML
    private Button calculateButton;
    @FXML
    private TextField numberField;
    @FXML
    private ComboBox<String> options;
    private String selectedOption;
    private StatController statController; // add this member


    @FXML
    private void initialize() {

        options.getItems().addAll(opt);
        options.getSelectionModel().selectedItemProperty()
                .addListener(new ChangeListener<String>() {
                    @Override
                    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {

                        selectedOption = newValue;
                    }
                });
    }


    @FXML
    public void calculateButtonClicked() {
        // change this method here
        statController.setActivityData(act);
    }

    // add this method here
    public void setStatController(StatController statController) {

        this.statController = statController;
    }
}

There are other problems that occur, but this fixes your null pointer exception. The reason this works and your previous code didn't, is because you were manually creating a new instance of StatController and not using the instance that the FXMLLoader had already loaded and mapped to the user interface node for you. All I have done above, is capture a reference to the controller you needed, and provided it to the other through a setter method.

Cypher
  • 2,608
  • 5
  • 27
  • 41
  • I am not sure how to implement this in my project. Do you think you could check my git repository[link](https://github.com/ErniMJ/JavaFx/tree/master/src/application) and give me a hint what to change? – erni Jun 28 '17 at 23:53
  • @erni I have updated my answer after looking at your source code. I had some trouble copying/pasting in the changes I made, so if I messed something up, let me know. – Cypher Jun 29 '17 at 00:48
  • 1
    just tested and it is working!!! Thanks for showing me how I can use it in future!. – erni Jun 29 '17 at 02:00