Issue
I am working on a client-server messenger application for practice. In my client module there is the Client class, the ClientController class, the ClientGUI class and fxml file, the Message class, a CSS file and the Main class. I have a method in my ClientGUI class and ClientController class, display(String message) that adds a Message object to the VBox that has the id #messages.
Here is my problem: inside my ClientController class is a display() method that calls the ClientGUI's display() method (I know that seems redundant, but it's not the issue). When the display() method is called within my ClientGUI class, for example inside the setOnMouseClick() method, it works fine. When this method is called from my ClientController class however, I get a NullPointerException pointing to my messages variable of type VBox inside the display() method of my ClientGUI class. I have tried replacing messages.getChildren().add(new Message(message));
with VBox test = messages;
The latter works fine while the former throws an exception. I have also tried calling other methods on my messages variable, all of which throw the same error. Why does this happen?
(It's still in the process of development so there may be additional unrelated issues)
Client:
package client;
import java.io.*;
import java.net.Socket;
/**
* Gabe
* 9/26/2016
* Description: Represents client and its socket
*/
public class Client implements Serializable {
private static Client instance;
private Socket clientSocket;
private BufferedReader in;
private PrintWriter out;
private ObjectOutputStream objectOutputStream;
private boolean connected = false;
private static final long serialVersionUID = 0L;
private Client() {}
public void connect(String targetAddress, String username_, int port_) {
try {
clientSocket = new Socket(targetAddress, port_);
clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out = new PrintWriter(clientSocket.getOutputStream(), true);
objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
objectOutputStream.writeObject(username_);
ClientController.getInstance().display("Attention: Successfully connected to server");
connected = true;
} catch (IOException e) {
ClientController.getInstance().display("Attention: Unable to connect to server");
}
}
public void disconnect() {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
connected = false;
}
public boolean connected() {
return connected;
}
public static Client getInstance() {
if (instance == null) {
instance = new Client();
}
return instance;
}
public void sendMessage(String message) {
out.println(message);
}
}
ClientController:
package client;
import javafx.scene.layout.VBox;
/**
* Gabe Castelli
* 9/28/2016
* Description:
*/
public class ClientController implements Controller {
private static ClientController instance;
private ClientController() {}
public static ClientController getInstance() {
if (instance == null) {
instance = new ClientController();
}
return instance;
}
// Controls
public void display(String message) {
ClientGUI.getInstance().display(message);
}
public void displayAndSend(String message) {
display(message);
Client.getInstance().sendMessage(message);
}
public void connect(String address_, String username_, int port_) {
Client.getInstance().connect(address_, username_, port_);
}
public void disconnect() {
Client.getInstance().disconnect();
}
}
ClientGUI:
package client;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* Gabe Castelli
* 9/26/2016
* Description: GUI for client-side using JavaFX
*/
public class ClientGUI extends Application {
private Scene scene;
private TextField targetAddress;
private TextField fieldUsername;
private TextField fieldPort;
private VBox messages;
private TextField input;
private Button btnConnect;
private String username = "Anonymous";
private int port = 2000;
private static ClientGUI instance;
public static ClientGUI getInstance() {
if (instance == null) {
instance = new ClientGUI();
}
return instance;
}
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml"));
scene = new Scene(root, 480, 360);
stage.setTitle("MyMessenger");
stage.setScene(scene);
stage.setMinWidth(660);
stage.setMinHeight(495);
stage.setMaxWidth(990);
stage.show();
targetAddress = (TextField) scene.lookup("#targetAddress");
fieldUsername = (TextField) scene.lookup("#fieldUsername");
fieldPort = (TextField) scene.lookup("#fieldPort");
messages = (VBox) scene.lookup("#messages");
input = (TextField) scene.lookup("#input");
btnConnect = (Button) scene.lookup("#btnConnect");
input.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
if (Client.getInstance().connected()) {
ClientController.getInstance().displayAndSend(input.getText());
input.setText("");
} else {
display("Attention: Not connected to server");
input.setText("");
}
}
});
btnConnect.setOnMouseClicked(event -> {
if (!Client.getInstance().connected()) {
if (!targetAddress.getText().equals("")) {
if (!fieldUsername.getText().equals("")) {
username = fieldUsername.getText();
}
if (!fieldPort.getText().equals("")) {
port = Integer.valueOf(fieldPort.getText());
}
ClientController.getInstance().connect(targetAddress.getText(), username, port);
btnConnect.setText("Disconnect");
} else {
display("Attention: Address required");
}
} else {
ClientController.getInstance().disconnect();
display("Attention: Disconnecting...");
btnConnect.setText("Connect");
}
});
}
public void display(String message) {
messages.getChildren().add(new Message(message));
}
}
Stack trace:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at client.ClientGUI.display(ClientGUI.java:96)
at client.ClientController.display(ClientController.java:26)
at client.Client.connect(Client.java:34)
at client.ClientController.connect(ClientController.java:35)
at client.ClientGUI.lambda$start$1(ClientGUI.java:81)
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.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$ClickGenerator.postProcess(Scene.java:3470)
at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3766)
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:352)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
Solution
The null pointer exception is occurring because the instance of ClientGUI
you get from ClientGUI.getInstance()
is not the instance that was created as part of the JavaFX launch process, and on which the start()
method was invoked. Since messages
was initialized in start()
, the instance you get from ClientGUI.getInstance()
does not have messages
initialized. So when you call ClientGUI.getInstance().display(message)
, it calls messages.getChildren()...
: messages
is null in that ClientGUI
instance and you get a null pointer exception.
The underlying problem here is the overall structure. You really cannot make these singletons - especially the Application
subclass - since they are instantiated by the framework. ClientGUI
is instantiated by the JavaFX launch process, and with the way you are using the FXMLLoader
, ClientController
is instantiated by FXMLLoader.load()
(so ClientController.getInstance()
is probably not giving you the instance you hope it is either).
It is not really clear, and certainly makes no sense, as to why you are trying to make these classes singletons anyway. My guess is that you are trying to work around the structure you have in place, which is completely non-standard. The methods you have defined are pretty much all in classes which are not supposed to have the responsibility for the functionality implemented by those methods.
In particular: the Application
subclass is responsible for application life-cycle. It should have a start
method which simply loads the initial view (FMXL file), puts it in a window, and displays it. It might also start other services if you have/need them. It could optionally override init()
and stop()
if you need.
The controller class is the one that is responsible for updating the view. This is the only place you should be referencing the UI controls defined in the FXML file, and the only place you should be modifying the display.
The model class (Client
) should have no knowledge at all of the view or controller. It should solely represent the state or data that are being presented. It is the responsibility of the controller (really a presenter, in some other architectures this can be done by the view) to update the view depending on the data represented by the model.
So you probably want to restructure the application along the following lines. There are variations on this depending on other things you might need, but this should at least get you on a workable track:
ClientGUI:
public class ClientGUI extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml"));
scene = new Scene(root, 480, 360);
stage.setTitle("MyMessenger");
stage.setScene(scene);
stage.setMinWidth(660);
stage.setMinHeight(495);
stage.setMaxWidth(990);
stage.show();
}
}
ClientController:
public class ClientController implements Controller {
@FXML
private TextField targetAddress;
@FXML
private TextField fieldUsername;
@FXML
private TextField fieldPort;
@FXML
private VBox messages;
@FXML
private TextField input;
@FXML
private Button btnConnect;
private String username = "Anonymous";
private int port = 2000;
private Client client ;
public void initialize() {
client = new Client();
}
@FXML
private void connect() {
if (!client.connected()) {
if (!targetAddress.getText().equals("")) {
if (!fieldUsername.getText().equals("")) {
username = fieldUsername.getText();
}
if (!fieldPort.getText().equals("")) {
port = Integer.valueOf(fieldPort.getText());
}
try {
client.connect(targetAddress.getText(), username, port);
btnConnect.setText("Disconnect");
display("Attention: Successfully connected to server");
} catch (IOException exc) {
display("Attention: Unable to connect to server");
}
} else {
display("Attention: Address required");
}
} else {
client.disconnect();
display("Attention: Disconnecting...");
btnConnect.setText("Connect");
}
}
@FXML
private void message() {
if (client.connected()) {
displayAndSend(input.getText());
input.setText("");
} else {
display("Attention: Not connected to server");
input.setText("");
}
}
public void display(String message) {
messages.getChildren().add(new Message(message));
}
public void displayAndSend(String message) {
display(message);
client.sendMessage(message);
}
}
and then
// Note: why on earth is this Serializable? What state are you intending to serialize???
public class Client implements Serializable {
private Socket clientSocket;
private BufferedReader in;
private PrintWriter out;
private ObjectOutputStream objectOutputStream;
private boolean connected = false;
private static final long serialVersionUID = 0L;
public void connect(String targetAddress, String username_, int port_) throws IOException {
clientSocket = new Socket(targetAddress, port_);
clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out = new PrintWriter(clientSocket.getOutputStream(), true);
objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
objectOutputStream.writeObject(username_);
connected = true;
}
public void disconnect() {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
connected = false;
}
public boolean connected() {
return connected;
}
public void sendMessage(String message) {
out.println(message);
}
}
For the controller to work, you need the root element to define a fx:controller="client.ClientController"
attribute, and the controls in ClientGUI.fxml
to define fx:id
attributes matching the field names in the controller class, and for the event handler methods to be specified, e.g.:
<!-- ... -->
<TextField fx:id="targetAddress" />
<TextField fx:id="fieldUserName" />
<TextField fx:id="fieldPort" />
<VBox fx:id="messages" />
<TextField fx:id="input" onAction="#message" />
<Button text="Connect" fx:id="btnConnect" onAction="#connect" />
<!-- ... -->
(Note that text field fire an ActionEvent
when enter is pressed, so there is no need to mess with key pressed handlers, etc.)
You may need to modify this somewhat to make the rest of the application functionality work, but as long as you stick to the basic principles: only the controller should modify the UI; the Client
class should have no knowledge of the presentation details, etc, then you will be on the right path. If you make anything static
, and certainly if you try to make any of these singletons, you are doing things wrong.
Answered By - James_D
Answer Checked By - Gilberto Lyons (JavaFixing Admin)