0

I am making an IRC client in a javafx fxml project.
I am having difficulties understanding the structure of the project. I get the MVC pattern however i don't know where i have to position the main code part of the client(object initialisation and communication start).
I want to be able to update a textarea with a String from a serverConnection object when the server sends a message.
So where do i position the code and how do i notify my controller that it has to update its text passing the String simultaneously?

This is the application class:

package jircclient;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.concurrent.*;
public class JircClient extends Application
{   

@Override
public void start(Stage stage) throws Exception
{
    Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

    Scene scene = new Scene(root);
    stage.setTitle("jircClient");
    stage.setScene(scene);
    stage.show(); 
    System.out.println("Stage SET!");

    //This is where i thought that i could write the main part of the program
    //but the stage will not load unless the start method finishes
    //Creating a serverConnetion is not a problem since it's only an object
    //but starting the communication does not let the method we are in to exit
    //since it sticks in a while loop forever until i probably cancel it using a 
    //button (not implemented yet)
    //serverConnection server1 = new serverConnection();
    //server1.startCommunication();

}

public static void main(String[] args) throws Exception
{   
    //In this method i cannot go further unless i close the stage-window
    //The purpose of it is to launch the start method
    Application.launch(args);
}
}

This is the controller class:

package jircclient;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextArea;

public class FXMLDocumentController implements Initializable
{  
@FXML
private TextArea txt;

@FXML
private void handleButtonAction(ActionEvent event)
{
    //In here i will be handling events, however i will need to have
    //access to serverConnection objects
    System.out.println("You clicked me!");
}

@Override
public void initialize(URL url, ResourceBundle rb)
{

}
}

This is the serverConnection class:

package jircclient;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

public class serverConnection 
{
//VARIABLES
private clientInfo client;
private String server_to_connect = "someserver";
private String channel_to_connect = "#somechannel";
private String serv_resp;
private int port = 6667;
private Socket socket;
private BufferedWriter writer;
private BufferedReader reader;
private ArrayList<channel> channels;

//DEFAULT CONSTRUCTOR
public serverConnection() {client = new clientInfo("test1", "test1", "test1");}

//FULL CONSTRUCTOR
public serverConnection(String server_to_connect, int port, String channel_to_connect) throws IOException
{
    client = new clientInfo("test1", "test1", "test1");
    this.server_to_connect = server_to_connect;
    this.port = port;
    this.channel_to_connect = server_to_connect;

    try
    {    
        //Creating socket connection
        this.socket = new Socket(this.server_to_connect,port);

        //Socket output writer
        writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream()));

        //Socket input writer
        reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));

        serv_resp = null;

        System.out.println("Connection established.");
    }
    catch(UnknownHostException exc)
    {
        System.out.println("ERROR: "+ exc.toString());
    }
    catch(IOException exc)
    {
        System.out.println("ERROR: "+ exc.toString());
    }
    finally
    {
        System.out.println("Closing connection.");
        socket.close();
    }
}

//server response getter
public String getServerResponse()
{
    return serv_resp;
}

//Introduction to server and listen
public void startCommunication() throws IOException
{

    this.socket = new Socket(this.server_to_connect,port);
    writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));  
    reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
    serv_resp = null;

    writer.write("NICK " + client.getClientNickname() + "\r\n");
    writer.write("USER " + client.getClientLogin() + " 0 * : " + 
    client.getClientRealName() + "\r\n");
    while ((serv_resp = reader.readLine()) != null)
    {
        System.out.println(serv_resp);
        //FXMLDocumentController.txt.setText(serv_resp);
        if (serv_resp.indexOf("004") >= 0)
        {
            break;
        } 
        else if (serv_resp.indexOf("433") >= 0)
        {
            System.out.println("Nickname is already in use.");
            return;
        }
    }

    //Get channel list
    writer.write("LIST \r\n");
    writer.flush();

    //Join desired client
    writer.write("JOIN " + channel_to_connect + "\r\n");
    writer.flush();


    //keep listening
    while ((serv_resp = reader.readLine()) != null)
    {
        //FXMLDocumentController.txt.setText(serv_resp);
        if (serv_resp.startsWith("PING "))
        {
            this.pingPong();
        } else
        {
            System.out.println(serv_resp);

        }
    }
}

//Ping respond
public void pingPong() throws IOException
{
    writer.write("PONG " + serv_resp.substring(5) + "\r\n");
    writer.flush();
}
}

I believe there is no need to add the fxml document since it's big and there is no need. I also have to state that oracle tutorials were not helpful as they only use the event handling in the controller and they don't implement any other logic.

  • Here's a [general document](http://www.oracle.com/technetwork/articles/java/javafxinteg-2062777.html) that may be of help. – James_D Dec 10 '14 at 00:28

1 Answers1

0

Here is one possible way to do this:

Give your ServerConnection class a callback to call when it receives a message:

public class ServerConnection {

    private Consumer<String> messageCallback ;

    public void setMessageCallback(Consumer<String> messageCallback) {
        this.messageCallback = mesasgeCallback ;
    }

    // other fields and methods as before...

    public void startCommunication() throws IOException {

        // code as before ...


        while ((servResp = reader.readLine()) !=null) {
            if (messageCallback != null) {
                messageCallback.accept(servResp);
            }
            // etc....
        }

        // etc
    }
}

Now instantiate your ServerConnection class from the controller and pass it a callback:

public class FXMLDocumentController {

    ServerConnection serverConnection ;

    // ...

    public void initialize() {
        serverConnection = new ServerConnection();
        serverConnection.setMessageCallback(message -> 
            Platform.runLater(() -> txt.appendText(message+"\n")));
        Thread serverThread = new Thread(() -> serverConnection.startListening());
        serverThread.setDaemon(true); // thread will not stop application from exiting
        serverThread.start();
    }

    // ...
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Now i get many NullpointerExceptions in the 'Platform.runLater(() -> txt.appendText(message+"\n")));' line as the server sends data. I have set the fx.id of the textfield to txt in scenebuilder. The textfield is deep inside the hierarchy but that not seems the problem to me. I think that the thread runs first even it is set to runLater while the textfield has not been initialised yet. However that does not fully explain the exceptions because they keep on going so the textfield is never initialised although i can see it in the window. – user1693061 Dec 10 '14 at 02:21
  • All the `@FXML`-injected fields are guaranteed to be injected before `initialize()` is called, so if `txt` is null, something is wrong between the FXML file and the controller. You can check with a `System.out.println(txt);` in the `initialize()` method. If it is `null`, make sure you have none of the common errors (typos in the `fx:id`, `id="..."` instead of `fx:id="..."`, accidentally making `txt` `static`, etc). (And I assume you wrote `fx.id` instead of `fx:id` there just as a typo...?) – James_D Dec 10 '14 at 02:33
  • fx.id was just a typo, i had set the txt as static, it now works! Why is a static textfield being a problem though? Also if you could give me some insight on how the Consumer works i would be grateful. I don't understand how the messageCallback accepts since it's null in the beggining. Thank you for your help James. – user1693061 Dec 10 '14 at 02:53
  • Question on [static fields in controllers](http://stackoverflow.com/questions/23105433/javafx-8-compatibility-issues-fxml-static-fields/23109125#23109125). In the code I provided, `messageCallback` is set before `startCommunication()` is called, so it is not `null` at that stage. [`Consumer`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html) is just a simple interface with an `accept(...)` method, the code supplies a [lambda expression](http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html) for its value. – James_D Dec 10 '14 at 02:58