-1

I'm writing a messaging application in java 17 using JavaFX (with fxml) and java sockets. I managed to establish a communication with the server and in my Client.java i want to send whatever is in the text area (to write the message) of the gui. This gui is controlled by the AppController class. When I invoke the appController.getMessageArea().getText() I get a null pointer exception (error log below)

Here iy my CLient.java

public class Client {

    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;
    private String username;
    private AppController appController;

    public Client(Socket socket, String username) throws IOException {
        this.appController = new AppController();
        try {
            this.socket = socket;
            this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            this.out = new PrintWriter(socket.getOutputStream(), true);
            this.username = username;
            out.println(username);
        } catch (IOException e) {
            closeAll();
        }
    }

    public void send() {
        String message;
        try {
            while(socket.isConnected()){
                message = appController.getMessageArea().getText();
                out.println(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //closeAll(socket, in, out);
        }
    }

    public void closeAll(Closeable... objects){
        for(Closeable c : objects){
            try {
                c.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void listen(){
        new Thread(() -> {
            String receivedMessage;
            while(socket.isConnected()){
                try{
                    receivedMessage = in.readLine();
                    Label received = new Label(receivedMessage);
                    appController.getMessageContainer().getChildren().add(received);
                    appController.getScrollPane().setVvalue(1.0);

                }catch(Exception e){
                    closeAll(socket, in, out);
                }

            }
        }).start();
    }

    public Socket getSocket() {
        return socket;
    }

    public String getUsername() {
        return username;
    }
}

Here is the AppController.java

public class AppController implements Initializable {

    @FXML
    private TextArea messageArea;
    @FXML
    private VBox messageContainer;
    @FXML
    private ScrollPane scrollPane;
    @FXML
    private Label nameTag;
    @FXML
    private Button sendButton;

    private Client client;


    @Override
    public void initialize(URL url, ResourceBundle resources){
        try{
            System.out.println(getMessageArea());
            messageArea.clear();
            sendButton.disableProperty().bind(messageArea.textProperty().isEmpty());
            nameTag.setText(LoginController.user.getName());
            client = new Client(new Socket("localhost", 1234), LoginController.user.getName());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public void send(){
       //String message = messageArea.getText();
       Label sent = new Label(client.getUsername() + "> " + messageArea.getText());
       messageContainer.getChildren().add(sent);
       scrollPane. setVvalue(1.0);
       client.send();
       messageArea.clear();

    }

    public VBox getMessageContainer() {
        return messageContainer;
    }

    public ScrollPane getScrollPane() {
        return scrollPane;
    }

    public TextArea getMessageArea() {
        return messageArea;
    }
}

And here is the error log

java.lang.NullPointerException: Cannot invoke "javafx.scene.control.TextArea.getText()" because the return value of "messageapp.AppController.getMessageArea()" is null
    at messageapp/messageapp.util.Client.send(Client.java:35)
    at messageapp/messageapp.AppController.send(AppController.java:47)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:77)
    at jdk.internal.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at javafx.base/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:84)
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1854)
    at javafx.fxml/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1724)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Node.fireEvent(Node.java:8792)
    at javafx.controls/javafx.scene.control.Button.fire(Button.java:203)
    at javafx.controls/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:208)
    at javafx.controls/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3897)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1878)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2623)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:557)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:943)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)

Process finished with exit code 0

If you need anything else tell me ! Thank you

Anthrax
  • 3
  • 1
  • 3
    `@FXML`-annotated fields (such as `messageArea`) will only be initialized in the controller. They won't be initialized in arbitrary objects just because they happen to be of the same class. Your `Client` class needs a reference to the actual controller (the one created by the `FXMLLoader` when you load and display the FXML file). (Or, and probably better, use a MVC approach and get the data from the model, so you don't expose the UI elements outside the presentation layer.) – James_D May 18 '23 at 15:24
  • you are only instanciating **this.appController = new AppController();**, messageArea must be instanciatet too before calling **appController.getMessageArea()** – DEV May 18 '23 at 15:25
  • 3
    @DEV No. It is always an error to instantiate `@FXML`-annotated fields. The annotation explicitly means the field will be initialized by the `FXMLLoader`. – James_D May 18 '23 at 15:26
  • 3
    Also *"even though getter's return value is not null"*. But it clearly ***is*** null. Put `System.out.println("From getMessageArea, returning: "+messageArea);` in the `getMessageArea()` method, right before the `return` statement, and you will see that it is returning `null`. – James_D May 18 '23 at 15:40
  • 3
    Off-topic, but your `listen()` method is modifying the scene graph from a background thread, which is [explicitly prohibited](https://openjfx.io/javadoc/20/javafx.graphics/javafx/application/Application.html) – James_D May 18 '23 at 15:59

1 Answers1

3

Fields annotated @FXML are initialized in the controller, which is (usually) an object created by. the FXMLLoader each time the corresponding FXML file is loaded. They won't magically be initialized in arbitrary objects of the same class (and even if they were, what would they be initialized to?).

If you wanted to reference the controller in your Client class, you would need to give it a reference to the actual controller instance created by the FXMLLoader.

The design here is ugly anyway. Your Client class has no business knowing that the controller has a TextArea and should not ever be accessing it. This is a clear violation of separation of concerns, and makes it difficult to, say, change the UI design (to use a different control) without changing arbitrary other parts of your application code.

Instead, define your Client.send() method to take a parameter representing the message to send, and then invoke it accordingly from the controller. This avoids the null pointer exception and fixes the ugly design.

public class Client {

    // ...

    public void send(String message) {

        // this implementation looks wrong. Why are you repeatedly sending the message?

        try {
            while(socket.isConnected()){
                out.println(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //closeAll(socket, in, out);
        }
    }

}

and then

public class AppController implements Initializable {

    @FXML
    private TextArea messageArea;
    @FXML
    private VBox messageContainer;

    // ...

    public void send(){
       String message = messageArea.getText();
       Label sent = new Label(client.getUsername() + "> " + message);
       messageContainer.getChildren().add(sent);
       scrollPane. setVvalue(1.0);
       client.send(message);
       messageArea.clear();

    }

}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Yes this is what I did first (putting the message to send as a parameter of the send method in Client), but that didn't seem work either. I'll try again, I certainly did something wrong. I'll also give a try to the MVC architecture as you suggested, this should fix other issues and make the code more readable. Thank you for your time – Anthrax May 18 '23 at 18:12
  • 1
    @Anthrax There's obviously a ton of other errors in your code. I just addressed the one you explicitly asked about. The first thing that will happen is that your `send()` method enters an essentially-infinite loop, and appears to be called on the FX Application Thread, so the application will hang and will repeatedly send the message over and over again (as I hinted at in the comments in the code). – James_D May 18 '23 at 18:16