-2

I'm working on a Game project with JavaFX and a MVC Architecture

Here are one of my views

package fr.arnoux23u.javano.mvc.views;

import fr.arnoux23u.javano.mvc.*;
import javafx.fxml.*;
import javafx.scene.control.TextArea;

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

/**
 * @author arnoux23u
 */
public class ServerView implements Initializable, Observer {

    @FXML
    private TextArea clientsList;

    @Override
    public synchronized void update(Model m) {
        System.out.println("[UPDATE] "+Thread.currentThread().getName());
        clientsList.setText("test");
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        System.out.println("[INITIALIZE] "+Thread.currentThread().getName());
        clientsList.setText("Aucun joueur ici");
    }
}

And my FXML File

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.arnoux23u.javano.mvc.views.ServerView">
    <Label text="JavaNo - Server">
        <font>
          <Font size="23.0" />
        </font>
    </Label>
    <Label alignment="CENTER" text="Connected Clients">
        <font>
          <Font size="23.0" />
        </font>
    </Label>
    <TextArea fx:id="clientsList" editable="false" maxWidth="200.0" prefHeight="200.0" prefWidth="200.0" style="-fx-focus-color: -fx-control-inner-background;" text="&#10;&#10;">
      <VBox.margin>
         <Insets top="20.0" />
      </VBox.margin>
      <font>
         <Font size="18.0" />
      </font></TextArea>
</VBox>

When I load the app, the text of the TextArea clientsList is correctly set to "Aucun joueur ici" but when I want to update the text (with MVC), I've a NullPointerException on clientsList

First, I thought it was a Thread Error because the Thread who runs the update method was not the "JavaFX Application Thread"

So in my MVC Controller, I use the Platform.runlater() method.

@Override
    public void notifyObservers() {
        observers.forEach(o -> {
            System.out.println("[FOREACH] "+Thread.currentThread().getName());
            Platform.runLater(() -> {
                System.out.println("[PLATFORM] "+Thread.currentThread().getName());
                o.update(this);
            });
        });
    }

The output when I call the update is

[INITIALIZE] JavaFX Application Thread
[FOREACH] Thread-3
[PLATFORM] JavaFX Application Thread
[UPDATE] JavaFX Application Thread

But even with the runLater method, I've a NullPointerException

Here is the complete stack trace

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot invoke "javafx.scene.control.TextArea.setText(String)" because "this.clientsList" is null
    at fr.arnoux23u.javano/fr.arnoux23u.javano.mvc.views.ServerView.update(ServerView.java:34)
    at fr.arnoux23u.javano/fr.arnoux23u.javano.mvc.Game.lambda$notifyObservers$1(Game.java:54)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    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)

I hope someone can help me.

Thank's

kleopatra
  • 51,061
  • 28
  • 99
  • 211
guillaumearnx
  • 142
  • 3
  • 12
  • 4
    Your error is in the code, you don’t provide. How did you register your observer instances and what are they? They are not the instances created by the fxml loader. If you want assistance you will likely need to edit the question to provide a [mcve]. – jewelsea Nov 28 '21 at 22:47
  • 1
    With regards to the above (yes) but you're most likely doing `Game#addObserver(new ServerVIew())` or something like that, instead of actually using the `ServerView` created in the application (99.9% of problems where the Controller isn't passed through the @FXML processing). – kendavidson Nov 29 '21 at 02:39
  • @jewelsea I add the observer in my Server class ```java game = new Game(); ServerView sv = new ServerView(); game.addObserver(sv); ``` – guillaumearnx Nov 29 '21 at 17:54
  • @kendavidson no, same instances lol – guillaumearnx Nov 29 '21 at 17:55
  • 2
    “no, same instances lol” -> what does this mean? How could the instance you create and the instance the fxml loader creates be the same? Are you setting the controller in the fxml loader to the instance you created like in [one part of this answer](https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml). I don’t think so. And your code also shows you are not getting the controller from the loader which would be the other way to do this. – jewelsea Nov 29 '21 at 18:06
  • 1
    You literally just laughed at my comment, and then posted your code showing that you're doing exactly what you laughed at. `ServerView sv = new ServerView();` This will NOT call through FXML and not create the `clientsList`. I'm not sure what to say. – kendavidson Nov 29 '21 at 18:13
  • Laugh at your comment? I may be stupid but in any case I do not see where. I am new to JavaFX and cannot speak fluent English, so please accept that some things escape me. It is time to stop looking down on people. I just thought you thought I declared two ServerView with different instances in my code. – guillaumearnx Nov 29 '21 at 18:29
  • See step 3 of this description of [FXMLoader operation](https://stackoverflow.com/questions/69077854/how-to-create-an-array-of-existing-javafx-objects/69081174#69081174) -> “If there is a fx:controller attribute on the root element, the FXMLLoader creates an instance of the specified class”. So if you create a new instance and the loader creates a new instance, then only the loader instance will have fxml values injected. I guess, with a high degree of certainty, that is what is happening with your code. Without a [mcve], this is speculation. – jewelsea Nov 29 '21 at 19:08
  • Thank's for the explaination. I think that's what is happening. – guillaumearnx Nov 29 '21 at 19:24
  • 2
    When you write "lol" it means laughs out loud. So when you wrote, "no, same instances lol" you were dismissing the advice offered to you and laughing about it as though they had made a ridiculous suggestion. "It is time to stop looking down on people." You can also follow this advice and not ridicule suggestions that you don't understand or find helpful. – matt Nov 30 '21 at 10:40
  • "no, same instances lol" does not show contempt at all, sorry if we do not have the same meanings concerning a word between two languages – guillaumearnx Nov 30 '21 at 12:15
  • 1
    If `lol` in your primary language means something other than what @matt explained, then I apologize (and accept your apology) for the confusion. I'm glad that you were able to work out the issue. – kendavidson Nov 30 '21 at 19:32
  • thank you for explaining the problem to me – guillaumearnx Nov 30 '21 at 19:56

1 Answers1

2

See step 3 of this description of FXMLoader operation:

If there is a fx:controller attribute on the root element, the FXMLLoader creates an instance of the specified class.

So, if you create a new instance explicitly in your code, and the loader also creates a new instance, then only the loader instance will have fxml values injected.

I guess, with a high degree of certainty, that is what is happening with your code.

jewelsea
  • 150,031
  • 14
  • 366
  • 406