Search code examples
javafxnullpointerexceptionfxmlpie-chartinvocationtargetexception

Javafx PieChart doesn't apply Data (sometimes NullPointerError)


I'm trying to write a simple program including a javafx PieChart. The purpose of the program is that I want to show the size of every subfolder in a directory in a PieChart. Problem:

I want to simply display some data from another class in a PieChart. I transfer the data through an object of the controller class in the calc class and the other way around using matching parameters in each class. But instead of applying the data nothing happens. If I just feed some Data directly into the PieChartData List everything is working fine.

I've already tried to apply the data via endless ways but this just gave me a NullPointerError or an InvocationTargetException.

Controller:


package sample;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

import java.io.File;
import java.nio.file.Path;

public class Controller {
    Calc calc = new Calc();

    @FXML
    public PieChart pieChart;
    public Button browse;
    public Button apply;
    public TextField pathField;

    String[] locs = new String[1000];
    double[] sizes = new double[1000];
    int count = 0;

    ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();

    public void handleBrowse(){
        pathField.setText("C:\\Users\\user\\Desktop\\Test");
    }
    public void apply(){
        System.out.println(pathField.getText());
        String strings = new String(pathField.getText());
        calc.main(strings);
        System.out.println("2");
        pieChart.setData(pieChartData);
    }
    public void setData(String loc, long size, int i, int aim){
        pieChartData.add(new PieChart.Data(loc,size));
        System.out.println("1");
    }


}
//alt+Enter = Import


Calc:


package sample;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Calc {
    public int totalFolder=0;
    public int totalFile=0;
    public static int counter = 0;


    public void main(String args) {

        File nativeFile = new File(args);
        File file = new File(nativeFile.toString());

        String[] files = file.list();
        Path path;
        Main main = new Main();

        if(file.isDirectory()) {
            for(int i=0; i<=files.length-1; i++) {

                path = Paths.get(files[i]);
                file = path.toFile();
                System.out.println(file + " (source: Main.java)");
                counter ++;
            }
            String[] paths = new String[counter];

            for(int i=0; i<=files.length-1; i++) {

                path = Paths.get(files[i]);
                file = path.toFile();
                paths[i] = file.toString();
            }
            System.out.println("");
            for(int i=0; i!=counter; i++) {

            }
            for(int i = 0; i+1 <= paths.length; i++) {
                try {

                    Calc size = new Calc();
                    long fileSizeByte = size.getFileSize(new File(nativeFile.toString() + "\\" + paths[i]));
                    System.out.println("Folder Size of " + paths[i] + " is: " + fileSizeByte / 1073741824 + " GB" + " or " + fileSizeByte + " bytes");
                    add(paths[i],fileSizeByte,i,paths.length);
                } catch (Exception e) {
                    System.out.println("failure");
                    e.printStackTrace();
                }
            }
        }

    }

    public void add(String loc, long size, int i, int aim){
        Controller controller = new Controller();
        controller.setData(loc,size,i,aim);
    }



    public long getFileSize(File folder) {
        long foldersize = 0;

        totalFolder++;
//          System.out.println("Folder: " + folder + "  (Source: getFileSize)");
        File[] filelist = folder.listFiles();
//          System.out.println(folder.listFiles());

        for (int i = 0; i < filelist.length; i++) {
            if (filelist[i].isDirectory()) {
                foldersize += getFileSize(filelist[i]);
            } else {
                totalFile++;
                foldersize += filelist[i].length();
            }
        }

        return foldersize;

    }

    public int getTotalFolder() {
        return totalFolder;
    }

    public int getTotalFile() {
        return totalFile;
    }
}


sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.chart.PieChart?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <children>
      <PieChart fx:id="pieChart" layoutX="348.0" layoutY="50.0" prefHeight="500.0" prefWidth="500.0" title="Subfolders" />
      <Separator layoutX="316.0" layoutY="-18.0" orientation="VERTICAL" prefHeight="633.0" prefWidth="0.0" />
      <TextField fx:id="pathField" focusTraversable="false" layoutX="31.0" layoutY="115.0" prefHeight="26.0" prefWidth="188.0" promptText="Type Path                                 or" />
      <Button fx:id="browse" layoutX="222.0" layoutY="115.0" mnemonicParsing="false" onAction="#handleBrowse" prefHeight="26.0" prefWidth="65.0" text="Browse" />
      <Label layoutX="116.0" layoutY="41.0" text="Options">
         <font>
            <Font size="27.0" />
         </font>
      </Label>
      <Button fx:id="apply" layoutX="31.0" layoutY="159.0" mnemonicParsing="false" onAction="#apply" prefHeight="55.0" prefWidth="256.0" text="Apply" />
   </children>
</AnchorPane>

I expect the code to display the double fileSizeByte and the String path from the controller class.


Solution

  • When the .fxml file is loaded, an instance of Controller is constructed.
    The main issue with the solution posted is that by

    public void add(String loc, long size, int i, int aim){
         Controller controller = new Controller();
         controller.setData(loc,size,i,aim);
    }
    

    you actually setData to a new Cotroller instance, and not the one use by he controller.

    A better solution would be to let Calc have a reference of the ObservableList you want to update with the calculation results:

    import java.io.File;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import javafx.collections.ObservableList;
    import javafx.scene.chart.PieChart;
    
    public class Calc {
    
        private int totalFolder=0, totalFile=0;
        private static int counter = 0;
        private final ObservableList<PieChart.Data> pieChartData;
    
        //added a constructor to receive a reference of the Observable list
        public Calc(ObservableList<PieChart.Data> pieChartData) {
            this.pieChartData = pieChartData;
        }
    
        /*
         * This method is copied from the original method called main.
         * The use of the name main is confusing.
         * The calculation done in this method has some issues, but it is not relevant
         * to the question asked.
         */
        public void calcSubfoldersSize(String sPath) { //replaces public void main(String args)
    
            File nativeFile = new File(sPath);
            File file = new File(nativeFile.toString());
    
            String[] files = file.list();
            Path path;
    
            if(file.isDirectory()) {
                for(int i=0; i<=files.length-1; i++) {
                    path = Paths.get(files[i]);
                    file = path.toFile();
                    counter ++;
                }
    
                String[] paths = new String[counter];
                for(int i=0; i<=files.length-1; i++) {
                    path = Paths.get(files[i]);
                    file = path.toFile();
                    paths[i] = file.toString();
                }
    
                for(int i=0; i!=counter; i++) {
    
                }
                for(int i = 0; i+1 <= paths.length; i++) {
                    try {
                        Calc size = new Calc(pieChartData); //the only line changed in the method
                        long fileSizeByte = size.getFileSize(new File(nativeFile.toString() + "\\" + paths[i]));
                        add(paths[i],fileSizeByte,i,paths.length);
                    } catch (Exception e) {
                        System.out.println("failure");
                        e.printStackTrace();
                    }
                }
            }
        }
    
        //let add update the observable list
        public void add(String loc, long size, int i, int aim){
            pieChartData.add(new PieChart.Data(loc,size));
        }
    
        //no change in other methods 
    }
    

    And use Calc in the controller like so:

    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.fxml.FXML;
    import javafx.scene.chart.PieChart;
    import javafx.scene.control.TextField;
    
    public class Controller {
    
        @FXML
        private PieChart pieChart;
    
        @FXML
        private TextField pathField;
    
        private ObservableList<PieChart.Data> pieChartData;
        private Calc calc;
    
        @FXML
        void initialize(){
            pieChartData = FXCollections.observableArrayList();
            calc = new Calc( pieChartData);
            pieChart.setData(pieChartData);
        }
    
        @FXML
        private void handleBrowse(){
            pathField.setText("C:/Users/user/Desktop/Test");
        }
    
        @FXML
        private void apply(){
            String strings = new String(pathField.getText());
            calc.calcSubfoldersSize(strings);
        }
    }