0

I am creating a money saving program for school that will have a goal of how much you would like to save, calculate how much you make in a week and based on how much you spend and save should edit your goal. I am currently trying to get that goal in a textfield to save in a txt file when the program is closed. However no matter how I try to fix it I get either a NullPointerException or the value of the textfield saves as "null"

Below is the main (Where I try to save the goal variable in the stop() method

package sample;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.*;


public class Main extends Application  {

@FXML
public TextField finalGoal;

@Override
public void start(Stage primaryStage)throws Exception  {
    File f = new File("Goal.txt");
    boolean bool = false;
    if (f.exists() )
    {
        Parent root = FXMLLoader.load(getClass().getResource("MainPage.fxml"));
        primaryStage.setTitle("Money Saver Program");
        primaryStage.setScene(new Scene(root, 600, 400));
        primaryStage.show();


    }
    else
    {
        bool = f.createNewFile();
        Parent root = FXMLLoader.load(getClass().getResource("OpeningPage.fxml"));
        primaryStage.setTitle("Money Saver Program");
        primaryStage.setScene(new Scene(root, 638, 400));
        primaryStage.show();

    }

    primaryStage.setOnCloseRequest(e -> closeProgram());

}

public static void main(String[] args) throws Exception {
    launch(args);
}

public void closeProgram(){

    Platform.exit();
}

@Override
public void stop()throws Exception{
    PrintWriter write = new PrintWriter("Goal.txt");
    String end;
    end = finalGoal.getAccessibleText();
    write.write(end);
    write.close();
}
}

This is the controller of the fxml that the textfield is in

package sample;

  import javafx.application.Platform;
  import javafx.event.ActionEvent;
  import javafx.fxml.FXML;
  import javafx.fxml.FXMLLoader;
  import javafx.scene.Node;
  import javafx.scene.Parent;
  import javafx.scene.Scene;
  import javafx.scene.control.TextField;
  import javafx.stage.Stage;
  import java.io.*;
  import java.io.FileReader;
  import javax.annotation.PostConstruct;

  public class MainPageController {

@FXML
public TextField finalGoal;

@FXML
private Stage myStage;

//Method that reads the current goal saved in an external textfile.
@PostConstruct
public void initialize() throws Exception{
    //Initialize Reader
    FileReader reader = new FileReader("Goal.txt");
    BufferedReader br = new BufferedReader(reader);

    //Store goal in the textField
    finalGoal.setText(br.readLine());

    //Close Reader
    reader.close();


}

//Method that handles close request and handles the current scene and controller
@PostConstruct
public void start(Stage stage1)throws Exception{
    myStage = stage1;
    FXMLLoader loader = new FXMLLoader(getClass().getResource("MainPage.fxml"));
    Parent root = loader.load();
    loader.getController();
    loader.load();
    loader.getController();
    stage1.getScene().getWindow();
    myStage.setOnCloseRequest(e -> onEnd());
}

public void openPage1(ActionEvent event) throws Exception {
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MandatoryCostCollector.fxml"));
    Parent root = fxmlLoader.load();
    Stage stage = new Stage();
    stage.setScene(new Scene(root));
    stage.setTitle("Money Saver Program");
    stage.show();
    ((Node) event.getSource()).getScene().getWindow().hide();
}

public void openPage2(ActionEvent event) throws Exception {
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("PayDataCollector.fxml"));
    Parent root = fxmlLoader.load();
    Stage stage = new Stage();
    stage.setScene(new Scene(root));
    stage.setTitle("Money Saver Program");
    stage.show();
    ((Node) event.getSource()).getScene().getWindow().hide();
}

public void resetGoal(ActionEvent event) throws Exception {
    finalGoal.setText("");
}

public void onEnd(){
    Platform.exit();
}

}

This is the fxml file

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

<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:id="BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.MainPageController">
   <top>
  <VBox prefHeight="65.0" prefWidth="600.0" BorderPane.alignment="CENTER">
     <children>
        <TextField alignment="CENTER" editable="false" prefHeight="68.0" prefWidth="600.0" text="Welcome to the Money Saving Program">
           <font>
              <Font name="Arial Rounded MT Bold" size="26.0" />
           </font>
        </TextField>
     </children>
  </VBox>
   </top>
   <left>
  <VBox prefHeight="335.0" prefWidth="159.0" BorderPane.alignment="CENTER">
     <children>
        <Button fx:id="openPage1" mnemonicParsing="false" onAction="#openPage1" prefHeight="88.0" prefWidth="167.0" text="1. Open Mandatory Cost Collector" wrapText="true">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </Button>
        <Button fx:id="openPage2" mnemonicParsing="false" onAction="#openPage2" prefHeight="60.0" prefWidth="173.0" text="2. Open Pay Data Collector" wrapText="true">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </Button>
     </children>
  </VBox>
   </left>
   <right>
  <VBox prefHeight="335.0" prefWidth="166.0" BorderPane.alignment="CENTER">
     <children>
        <TextField fx:id="finalGoal" promptText="\$">
           <font>
              <Font name="Arial" size="40.0" />
           </font>
        </TextField>
        <Button fx:id="resetGoal" contentDisplay="RIGHT" mnemonicParsing="false" onAction="#resetGoal" prefHeight="33.0" prefWidth="173.0" text="Reset Goal" wrapText="true">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </Button>
     </children>
  </VBox>
   </right>
   <bottom>
  <HBox prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
     <children>
        <Button fx:id="generateNewGoal" mnemonicParsing="false" prefHeight="63.0" prefWidth="161.0" text="3. Generate your new goal" translateY="36.0" wrapText="true">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </Button>
        <TextField alignment="CENTER" editable="false" prefHeight="75.0" prefWidth="221.0" text="Money saved this week" translateX="38.0" translateY="23.0">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </TextField>
        <TextField fx:id="moneySaved" editable="false" prefHeight="75.0" prefWidth="180.0" promptText="\$" translateX="38.0" translateY="23.0">
           <font>
              <Font name="Arial" size="40.0" />
           </font>
        </TextField>
     </children>
  </HBox>
   </bottom>
   <center>
  <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
     <children>
        <TextField editable="false" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="68.0" prefWidth="167.0" text="Your current goal" translateX="108.0">
           <font>
              <Font name="Arial" size="18.0" />
           </font>
        </TextField>
     </children>
  </VBox>
   </center>
</BorderPane>

and finally the error I am getting

Exception in Application stop method
Exception in thread "main" java.lang.RuntimeException: Exception in  Application stop method
at  com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:922)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at sample.Main.stop(Main.java:59)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$163(LauncherImpl.java:882)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
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)

The problem seems to be with the code end = finalGoal.getAccessibleText(); in the main. I have also tried end = finalGoal.getText(); however that returns NullPointerand I have also tried end = String.valueOf(finalgoal.getText); and that returns the value as "null"

Dylan52
  • 61
  • 2
  • 10
  • You cannot inject from FXML to an Applcation only into the controller of the FXML file (`@FXML public TextField finalGoal;`). Variable `finalgoal` is null as it never gets initialized. What you could do to get the `TextField` from the controller. For that, after you have loaded the FXML file call: `controller = loader.getController();` while having `MainPageController controller;` as class member. In the controller class you could expose a getter to the `TextField` then. I also don't understand the purpose of `start` method in the controller (and why do you load the same FXML several times?). – DVarga Jul 26 '16 at 07:06

2 Answers2

0

You need to get a reference to the finalGoal variable from the MainPageController class instead of defining a variable with the same name in the Main class.

Titus
  • 22,031
  • 1
  • 23
  • 33
0

The FXMLLoader injects the finalGoal Node to the controller class. It doesn't have any information abut your Main class and does not inject the Node there too.

Therefore the default value is still assigned to the fieldGoal field when stop is called.

Solutions

  1. Access the controller after loading the fxml file using FXMLLoader.getController. You could use a getter in the controller class to access the value (=fieldGoal). or
  2. You could also use a controller factory to register listeners for shutdown and remove some of the code duplication (i.e. the loading of the fxmls).
    This allows you to place the shutdown handlers where your data is:

    // factory for controllers also allowing to load fxmls and unregistering listeners
    public class Factory implements Callback<Class<?>, Object> {
    
        public Factory(List<InvalidationListener> listeners) {
            this.listeners = listeners;
        }
    
        private final List<InvalidationListener> listeners;
    
        @Override
        public Object call(Class<?> param) {
            Object result = null;
            try {
                result = param.newInstance();
            } catch (InstantiationException | IllegalAccessException ex) {
                Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException("Could not create instance of "+param.getName(), ex);
            }
    
            if (result instanceof ShutdownObserver) {
                InvalidationListener listener = ((ShutdownObserver) result).getObserver();
                if (listener != null) {
                    listeners.add(listener);
                }
            }
    
            if (result instanceof FactoryAccessController) {
                ((FactoryAccessController) result).setFactory(this);
            }
    
            return result;
        }
    
        public void removeShutdownListener(InvalidationListener listener) {
            listeners.remove(listener);
        }
    
        public <T> T loadFXML(URL url) throws IOException {
            if (url == null) {
                throw new IllegalArgumentException();
            }
            FXMLLoader loader = new FXMLLoader(url);
            loader.setControllerFactory(this);
            return loader.load();
        }
    
    }
    
    public interface ShutdownObserver {
    
        InvalidationListener getObserver();
    
    }
    
    public class FactoryAccessController {
    
        private Factory factory;
    
        void setFactory(Factory factory) {
            this.factory = factory;
        }
    
        public Factory getFactory() {
            return factory;
        }
    
    }
    
    public class MainPageController extends FactoryAccessController implements ShutdownObserver {
    
        // keep reference to listener in case it should be removed
        private final InvalidationListener listener = o -> {
            // write data from fieldGoal to file
        };
    
        @Override
        public InvalidationListener getObserver() {
            return listener;
        }
    }
    
    private final List<InvalidationListener> shutdownListeners = new ArrayList<>();
    private final Factory factory = new Factory(shutdownListeners);
    
    @Override
    public void start(Stage primaryStage) throws IOException {
        primaryStage.setTitle("Money Saver Program");
        File f = new File("Goal.txt");
        Scene scene;
    
        if (f.exists()) {
            Parent root = factory.loadFXML(getClass().getResource("MainPage.fxml"));
            scene = new Scene(root, 600, 400);
        } else {
            boolean bool = f.createNewFile();
            Parent root = factory.loadFXML(getClass().getResource("OpeningPage.fxml"));
            scene = new Scene(root, 638, 400);
        }
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    @Override
    public void stop() throws Exception {
        for (InvalidationListener listener : shutdownListeners) {
            try {
                listener.invalidated(null);
            } catch (Exception ex) {
                Logger.getLogger(getClass().getName())
                        .log(Level.WARNING,
                                MessageFormat.format("Shutdown listener {0} yielded exception.", listener),
                                ex);
            }
        }
    }
    

See also Passing Parameters JavaFX FXML

fabian
  • 80,457
  • 12
  • 86
  • 114