1

I'm creating a simple chat application(Desktop Application) for my own study and I'm using netty library for my Client and Server.

I'm starting the client from Thread: new Thread(new Client()).start();, I do this from my Helper Class. When the Client become Connected to the Server, I want to access the MainController and set the Label on it to Connected. I'm using Guava Eventbus to accomplish this.

I do the following code to implement it.

From my MainController where I subscribe the function that will change the Text of the Label:

public class MainController implements Initializable{

    @FXML Label label_status;

    public MainController(){}

    @Override
    public void initialize(URL location, ResourceBundle resources) {
            /**Some Code Here...**/
    }

    /**Subscribe Eventbus function**/
    @Subscribe
    public void changeLabelStatus(String status) {
        try{
            label_status.setText(status);
        }catch (Exception e){
            System.out.println(TAG + "Failed to Change the status of Label. >> " + e.toString());
        }
    }
}

From the Handler of Client where I want to post the Status of the Client:

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    EventBus eventBus;
    MainController mainController;

    public ClientHandler(){
        eventBus = new EventBus();
        mainController = new MainController();
        eventBus.register(mainController);
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        eventBus.post("Connected"); /**Post here**/
    }
}

To check if this implementation of EventBus will work, I tried to println from the Subscribe function and it works, but when I tried to label_status.setText(status); to change the Text of Label I get java.lang.NullPointerException error.

I have no idea why, this is my first time of using both library, I read the guide and example for EventBus and from my understanding this how I do it. What's wrong with my code? How can I achive what I want?

Note: I'm using JavaFX for this application.

UPDATE:

I give up using Guava Eventbus, I used greenrobot/EventBus with it's latest jar now.

Polar
  • 3,327
  • 4
  • 42
  • 77
  • Are you importing `javafx.scene.control.Label` instead of `java.awt.Label`? – Törpetestű Nov 27 '17 at 10:08
  • @Törpetestű - Yes, should I use `java.awt.Label`? – Polar Nov 27 '17 at 10:16
  • No, just it could have been a trivial answer. – Törpetestű Nov 27 '17 at 10:18
  • aww, I been searching for an hour, can't find a solution... – Polar Nov 27 '17 at 10:19
  • I have 2 more ideas: 1. your label is not null in the initialize method, right? 2. what if you put the setText into a runLater? https://stackoverflow.com/questions/13784333/platform-runlater-and-task-in-javafx – Törpetestű Nov 27 '17 at 10:21
  • The Label is not null. Let me try the second. – Polar Nov 27 '17 at 10:23
  • @Törpetestű - still, java.lang.NullPointerException using runLater. I use runLater inside the Subscribe function. – Polar Nov 27 '17 at 10:29
  • Sorry, in this case I'm not gonna know the answer. Hopefully someone will arrive. – Törpetestű Nov 27 '17 at 11:43
  • @Törpetestű - It's OK, I manage to make it work by passing the instance of `MainController class` to `Client class` and from `Client Class ` to `ClientHandler class`, but this doesn't make any sense, what is the use of `Eventbus` if I will need to pass the instance of `MainController`. If this is how it should be done then I rather make a function inside my `MainController` and pass the instance and of it then call it like `mainController.changeLabelStatus("Connected")`. But still, I want to make it done using `Eventbus`, hope someone has an answer. – Polar Nov 27 '17 at 12:22
  • `@FXML`-injected fields (such as `label_status`) are only initialized in the controller. They are not initialized in arbitrary objects of the same class, such as the controller instance you create in your `ClientHandler` class. I don't use Event Bus, but isn't the point to create a single `EventBus` instance and pass it to both the client handler and the controller; then the former can post to it and the latter can register with it? – James_D Nov 27 '17 at 12:36
  • @James_D - Then what should I do? I really want to do it using `Eventbus`. As I said from the comment, I can do so like `mainController.changeLabelStatus("Connected")` from the `ClienHandler`, but what if the `Window` is not present? I'm planning to make the `Client` works in the background like a `Service` and then if the Window is present then change the `Label Status` if not then leave it. This maybe an stupid idea, sorry to bother you but I really want to make it work. – Polar Nov 27 '17 at 12:44
  • Again, I'm not an expert (by any stretch) on the event bus pattern, but what's wrong with what I suggested in my previous comment? – James_D Nov 27 '17 at 12:46

1 Answers1

0

@FXML-injected fields are initialized by the FXMLLoader in the controller when the FXML file is loaded and parsed. The object you are registering with the event bus is not the controller (it is just an instance of the same class that you created), so label_status will not be initialized in the object registered with the event bus.

You need to register the actual controller with the event bus, and post to that event bus from your client handler. You should also not have a reference to the controller (or its class) in the client handler: the whole point of using an event bus in the first place is to allow you to decouple these parts of the application.

So your client handler should look something like

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    private final EventBus eventBus;

    public ClientHandler(EventBus eventBus){
        this.eventBus = eventBus;
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        eventBus.post("Connected"); /**Post here**/
    }
}

Then at the point where you assemble your application you would do something along the following lines:

EventBus eventBus = new EventBus();
ClientHandler clientHandler = new ClientHandler(eventBus);
// ...
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml/file"));
Parent root = loader.load();
MainController controller = loader.getController();
eventBus.register(controller);
Scene scene = new Scene(root);
// put scene in stage and show stage, etc...

Getting the event bus to the client handler may be a little more complex than the code above, but it should give you the basic idea. (If things get too complicated here, you might consider using a dependency injection framework such as Spring or Guice to inject the event bus into the client handler, and create controllers which are automatically registered with the event bus.)

If you like, you can even go one step further and decouple the client handler from the event bus, just using standard Java API classes (the point here is that all that ClientHandler needs is "something that processes a String"):

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    private final Consumer<String> statusUpdate ;

    public ClientHandler(Consumer<String> statusUpdate) {
        this.statusUpdate = statusUpdate ;
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        statusUpdate.accept("Connected"); /**Post here**/
    }
}

and then

EventBus eventBus = new EventBus();
ClientHandler clientHandler = new ClientHandler(eventBus::post);
// etc ...

Finally, note that since your client handler appears to be running on a background thread, you need to schedule the update to the label on the FX Application Thread:

public class MainController implements Initializable{

    @FXML Label label_status;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
            /**Some Code Here...**/
    }

    /**Subscribe Eventbus function**/
    @Subscribe
    public void changeLabelStatus(String status) {
        Platform.runLater(() -> label_status.setText(status));
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322