Event Handling in Java
When working in Java on client or server side you'll be using event handlers quite often, to manage responses and events from the other end.
There are different ways to register events in SmartFoxServer, for example using classes or making use of Java's functional style, which we usually recommend and use throughout the documentation. However there are a few important details that need to be discussed to avoid potential pitfalls.
The basics: classes
By now you've probably already learned the basics and you've seen a few examples of how events are generally handled. Let's take look at a simple client side example:
Note:
Keep in mind: the concepts discussed here are valid for both client and server side events.
sfs = new SmartFox();
sfs.addEventListener(SFSEvent.ROOM_JOIN, new OnRoomJoinHandler());
// ...
class OnRoomJoinHandler implements IEventListener
{
@Override
public void dispatch(ApiEvent evt) throws SFSException
{
var room = (Room) evt.getParam(EventParam.Room);
log.info("I am joined in: " + room.getName());
}
}
The simplest approach here is to use classes as handlers by following the signature of the addEventListener method, which looks like this:
Since the 2nd argument requires an implementation of the IEventListener interface we provide one via our OnRoomJoinHandler class. Classic pre-version 8 Java style.
Functional style
IEventListener is also a functional interface and as such we can provide a lambda or method reference, instead of a full blown class.
This looks much better as we don't have to define a bunch of classes for each event we want to listen to. However this approach can have side effects when it comes to removing the same event handler just added.
In fact the client side API allow to register multiple handlers for the same event, which is sometimes useful when different parts of the client application need to react to the same event.
Now, let's suppose at some point in the game we need to stop listening to a specific event. It would seem only reasonable to do the following:
What you may later find out is that the listener was not removed at all! The reason for this is that Java does not guarantee that method references created at different times refer to the same object. If this happens an equality check will fail to match the new reference with the old one and thus the removal will fail.
To avoid this issue we highly recommend storing a single reference to a method and reusing the same one everytime you need it. Let's update the previous example:
sfs = new SmartFox();
IEventListener roomJoinHandler = this::onRoomJoin;
sfs.addEventListener(SFSEvent.ROOM_JOIN, roomJoinHandler);
private void onRoomJoin(ApiEvent evt)
{
var room = (Room) evt.getParam(EventParam.Room);
log.info("I am joined in: " + room.getName());
}
private void someGameMethod()
{
sfs.removeEventListener(SFSEvent.ROOM_JOIN, roomJoinHandler);
}
Lambda Expressions as Members
Another safe way to pass methods around is to define them with this syntax:
sfs = new SmartFox();
sfs.addEventListener(SFSEvent.ROOM_JOIN, onRoomJoin);
IEventListener onRoomJoin = evt ->
{
var room = (Room) evt.getParam(EventParam.Room);
log.info("I am joined in: " + room.getName());
};
private void someGameMethod()
{
sfs.removeEventListener(SFSEvent.ROOM_JOIN, onRoomJoin);
}
Here we're defining a method reference directly (no obj::methodName syntax) and assigning a lambda expression to a variable that can be reused as many times as needed, without risks of ambiguity.
Anonymous lambdas
For the sake of completeness we want to mention one last option which is using anonymous lambdas:
Here we're passing an anonymous function directly in the 2nd argument, without having to declare anything else. This can be useful for short methods but not so much for longer sections of code.
Takeaway concepts
We can now summarize the main concepts, which can be applied to both client and server side projects:
- Classes as event handlers are a good option albeit a bit verbose, especially when you need lots of them
- Functional style is another option. It looks simpler and more readable but:
- if you're passing method references (such as this::myHandler) and you need to remove them manually, make sure to declare a single reference in you main class so that you always pass the same object around
- if you don't need manual removal method references are fine. Even anonymous lambdas can be used
- another option is to declare each handler as members of your class, using their functional interface as the type