Hot questions for Using EventBus in javafx

Question:

I'm writing JavaFX application in order to send from one controller to other controller. I use EventBus which was written by developer. I download it from github.But When I try to recall from one controller to other controller. First time it works once. Second time it works twice. Third time it works three times and so on. What might be reason of behaving this eventbus?

MainController

here Event bus was registered like static

public class Main extends Application
{
    public static EventBus eventBus = new FxEventBus();

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();

    }


    public static void main(String[] args) {
        launch(args);
    }
}

This controller class which was fired event

Controller

private    AddClickedEvent addClickedEvent;
    @Override
    public void initialize(URL location, ResourceBundle resources)
    {

        id.setOnAction(event ->
        {
            try
            {
                FXMLLoader loader = new FXMLLoader();
                Parent parent = loader.load(getClass().getResource("ask.fxml").openStream());
                Stage stage = new Stage();
                stage.setScene(new Scene(parent));
                if(addClickedEvent == null){
                    addClickedEvent = new AddClickedEvent(AddClickedEvent.ANY);
                }
                Main.eventBus.fireEvent(addClickedEvent);
                stage.showAndWait();
            } catch (IOException e) {
                e.printStackTrace();
            }

        });

    }

Here is Controller other Controller that should show up something after fire

  @Override
    public void initialize(URL location, ResourceBundle resources)
    {

        Main.eventBus.addEventHandler(AddClickedEvent.ANY,event -> {
            System.out.println("uyondan bosilib galdi");
            System.out.println(yes);
            yes = true;
        });
        id1.setOnAction(event -> {
            System.out.println(yes);
        });
        id2.setOnAction(event -> {

            Stage stage = (Stage)((Node) (event).getSource()).getScene().getWindow();
            stage.close();
        });

    }

AddClicked Event class

public class AddClickedEvent extends Event
{
    public static final EventType<AddClickedEvent> ANY =
            new EventType<>(Event.ANY, "ADD_CLIENT_EVENT");

    public AddClickedEvent(@NamedArg("eventType") EventType<? extends Event> eventType) {
        super(eventType);
    }

}

Answer:

EventHandler should be created only once in process of whole application then it will react only once rather than multiple times. I come up with solutions to it declare for every controller class with static int variable which helps me to register events only once when the value of int is zero.

      private static int check;
      @Override
        public void initialize(URL location, ResourceBundle resources)
        {
          if(check == 0)
{

            Main.eventBus.addEventHandler(AddClickedEvent.ANY,event -> {
                System.out.println("uyondan bosilib galdi");
                System.out.println(yes);
                yes = true;
            });
check ++;
// Then it will react only once We registered event here
}
            id1.setOnAction(event -> {
                System.out.println(yes);
            });
            id2.setOnAction(event -> {

                Stage stage = (Stage)((Node) (event).getSource()).getScene().getWindow();
                stage.close();
            });

        }

Question:

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.


Answer:

@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));
    }
}