Search code examples
javajavafxmodel-view-controllercontrollerfxml

JavaFX - Main controller calls sub controller's function which causes Null Pointer Exception


I'm relatively new to JavaFX but I try to be clear.

The issue: When my main controller calls my sub controller's function, it runs completely fine as long as it do not need to manipulate the fxml file's content.

Surprisingly, the code don't cause any error if the fxml annotation components are manipulated in the initialize function. But whenever I want to use them in other functions it will throw a NullPointerException.

So to be a bit clearer under fxml annotation components I mean for example this: @FXML private Label exampleLbl.

I created a simplified example:

Main Controller class:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Tab;
import javafx.scene.layout.AnchorPane;

public class MainController implements Initializable {

    @FXML SubController subController;

    @FXML private Tab Tab1;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        subController = new SubController();

        FXMLLoader loader = new FXMLLoader();

        try {
            AnchorPane anch1 = loader.load(getClass().getResource("SubView.fxml"));
            Tab1.setContent(anch1);
        }
        catch(Exception e) {
            System.out.println("unable to load tab1");
            e.printStackTrace();
        }
    }

    public void fooFunction() {
        if(subController != null) {
            subController.testFunction();   
        }
    }
}

Sub controller class:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class SubController implements Initializable {

    @FXML private Label exampleLbl;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        if(exampleLbl != null) {
            System.out.println("It is printed, so it is not null");
            exampleLbl.setText("So I can manipulate this as I want.");
        }

    }

    public void testFunction() {
        if(exampleLbl == null) {
            System.out.println("It is printed, so it is null now.");
            exampleLbl.setText("But not here. It will throw an error.");
        }
    }
}

The nullptrexception occurs on SubController's class in testFunction() method, line 26.

Line: exampleLbl.setText("But not here. It will throw an error.");

So it works fine in initialize function, but not in testFunction

Here are my FXML files:

MainView fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
   <center>
   <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
            <Tab fx:id="Tab1" text="Test">
               <content>

               </content>
            </Tab>
        </tabs>
   </TabPane>
   </center>

   <bottom>
        <Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

SubView FXML:

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SubController">
      <Label fx:id="exampleLbl" layoutX="5.0" layoutY="128.0" prefHeight="17.0" prefWidth="188.0" text="Label" />
</AnchorPane>

Here is the error message and the console output if it helps:

It is printed, so it is not null
It is printed, so it is null now.

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8411)
    at javafx.scene.control.Button.fire(Button.java:185)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$358(GlassViewEventHandler.java:432)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$152(WinApplication.java:177)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
    ... 48 more
Caused by: java.lang.NullPointerException
    at application.SubController.testFunction(SubController.java:26)
    at application.MainController.fooFunction(MainController.java:36)
    ... 58 more

So I would be thankful if someone could explain what am I missing here and provide me simple solution.


Solution

  • When you use subController = new SubController(), the instance you just created has no relation to the FXML file. None of the fields will be injected because the controller instance was not managed by an FXMLLoader. In your case, using the following should solve the problem:

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("SubView.fxml"));
    
        try {
            AnchorPane anch1 = loader.load();
            subController = loader.getController();
            Tab1.setContent(anch1);
        } catch (Exception e) {
            System.out.println("unable to load tab1");
            e.printStackTrace();
        }
    }
    

    Note two things:

    1. I set the location of the FXMLLoader during construction. When you were using loader.load(/* URL */) you were actually using the static FXMLLoader#load(URL) method. You need to use the instance load() method.
    2. I assign the subController field (which needn't be annotated with @FXML, by the way) with loader.getController(). That method will return the instance the FXMLLoader created from the class name you specified via the fx:controller attribute in the FXML file. Important: You must call getController() after load().

    You may also want to consider using fx:include.

    MainView.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Tab?>
    <?import javafx.scene.control.TabPane?>
    <?import javafx.scene.layout.BorderPane?>
    
    <BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
       <center>
       <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
            <tabs>
                <Tab fx:id="Tab1" text="Test">
                   <content>
                       <!-- replace the value of source as needed -->
                       <fx:include source="SubView.fxml" fx:id="sub"/>
                   </content>
                </Tab>
            </tabs>
       </TabPane>
       </center>
    
       <bottom>
            <Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
       </bottom>
    </BorderPane>
    

    This makes it no longer necessary to load the SubView.fxml file manually inside the MainController#initialize method. Also, since I assigned an fx:id to the included file, the SubController instance will be injected for you. When it comes to injecting controllers the FXMLLoader looks for an appropriately typed (and annotated) field with the name <fx:id>Controller (see Nested Controllers).