Simplify Your UI Interactions With Events | Java, Kotlin, Any Language
In this article, I will show you a simple pattern that you can use in just about any programming language or platform that supports enums, dictionaries, sealed classes, etc. Our source code examples for today are Java & Kotlin, but the idea remains the same across all similar languages.
The Main Idea: Event Driven Programming
Every time your user interacts meaningfully with your application, that interaction can be considered as an Event. In fact, any interaction with your application, even ones that are fired internally such as when the device reconnects to a network (i.e. system services), can be considered events. Today we will look mostly at UI events, but understand that I use this pattern in many ways throughout the back end as well. In the next article, I will talk about that in detail, along with how to use a functional approach to event handling.
Events can be modeled in many ways, but a common and easy object oriented approach is to use some kind of data structure such as an enum or sealed class. In either case, these tools are designed to create a restricted and specific set of values/types, which makes them appropriate for modelling a finite set of events.
Modelling Events In Java
Below we have a feature of an application:
Based on all the different ways the user can interact with this feature, I have created a Java class with a nested enum to represent said events:
public class TaskViewEvent {
private final Event event;
private final Object value;
public TaskViewEvent(Event event, Object value){
this.event = event;
this.value = value;
}
public Event getEvent() {
return event;
}
public Object getValue() {
return value;
}
public enum Event {
ON_COLOR_BUTTON_CLICK,
ON_COLOR_SELECTED,
ON_DONE_CLICK,
ON_ICON_SELECTED,
ON_START
}
}
Now, you would probably want to create an abstract class and then subclass it for each feature, but this application was small enough that I did not really care.
If you don’t know how to use interfaces, abstract classes, or even how to use inheritance as a mechanism for abstraction, then you will want to watch my comprehensive introduction to Java programming.
When it comes time to actually unpack these events and handle them, we can set up a single entry point which cleans up your code and makes it easier to test. In this case, I did create an abstract class which will be implemented by any class which has the duty of handling events (obviously):
public abstract class BaseViewLogic<T> { //you can remove this and use an interface instead if you do not need cancellation
private boolean isCancelled = false;
/**
* If the View indicates that it is to be destroyed, we want to inform the Logic class
* so that it can avoid calling back to a null fragment/activity.
*/
public void onCancel(){
isCancelled = true;
}
public abstract void onViewEvent(T event);
}
Next, we have the class which actually handles the events. This would be your Controller, Presenter, ViewModel or whatever else you want really.
I use the generic name Logic, which is a result of the fact that I have transcended three layer architecture dogma and built my own architecture to suit the project requirements at hand.
public class TaskViewLogic extends BaseViewLogic<TaskViewEvent> {
private ITaskViewContract.View view;
private ITaskViewContract.ViewModel vm;
private ITaskStorage storage;
public TaskViewLogic(ITaskViewContract.View view, ITaskViewContract.ViewModel vm, ITaskStorage storage) {
this.view = view;
this.vm = vm;
this.storage = storage;
}
@Override
public void onViewEvent(TaskViewEvent event) {
//code like this make me miss Kotlin syntax...
switch (event.getEvent()) {
case ON_START:
onStart();
break;
case ON_COLOR_BUTTON_CLICK:
view.showColorPickerSheet();
break;
case ON_COLOR_SELECTED:
onColorSelected((COLOR) event.getValue());
break;
case ON_DONE_CLICK:
updateStorage();
break;
case ON_ICON_SELECTED:
onIconSelected(
(ICON) event.getValue()
);
break;
}
}
//...
}
There are several things happening in that code:
Firstly, note that we have specified our TaskViewEvent class as the Generic of our abstract class:
public class TaskViewLogic extends BaseViewLogic<TaskViewEvent>
That is very important, and if you do not understand what we did here, I must once again suggest my Java course; you will not be disappointed with it.
Secondly, we are using a switch statement which selects for different enum values. This is how we actually fork the execution of the program based on what event has occured.
Thirdly, when we want to get the value out of our event class (assuming that event needs a value), then we simply call event.getValue(), and cast it to whatever the value is supposed to be. Unfortunately because Java is annoying this way, you have to be careful about NullPointerException and ClassCastException.
Finally, you may wonder how I actually forward events to my event handler class. Each View holds a reference to an abstract EventHandler class:
class TaskView extends Fragment {
private final BaseViewLogic<TaskViewEvent> logic;
private ImageButton doneButton;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_manage_task, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
doneButton.setOnClickListener(
v -> {
logic.onViewEvent(new TaskViewEvent(
TaskViewEvent.Event.ON_DONE_CLICK,
null
));
}
);
/*for those who cannot read lambda expressions, this is just a simpler way of writing
doneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
...
}
});
*/
}
}
Want To Watch Me Build The App That These Samples Are From?
Modelling Events In Kotlin
Now that you have the general idea, we will just talk implementation here. This is all much easier to do in Kotlin, as is typically the case. Sealed Classes are pretty much designed for this kind of purpose:
sealed class MovementEvent {
object OnShowVideoClick : MovementEvent()
object OnImageClick : MovementEvent()
data class OnStart(val movementId: String?): MovementEvent()
}
No need to mess around with creating an enum and a wrapper class around it; if your event needs a value, use a data class; otherwise use an object.
Again, handling this event is much prettier and more concise than in Java. Kotlin automatically casts it for you, and the syntax is just better overall:
override fun handleEvent(eventType: MovementEvent) {
when (eventType){
is MovementEvent.OnStart -> getMovement(eventType.movementId)
is MovementEvent.OnImageClick -> onImageClick()
is MovementEvent.OnShowVideoClick -> showYoutubePlayer()
}
}
Again, we use either an interface or an abstract class in the View in order to forward our events:
class MovementFragment : Fragment(), MovementContract.View {
var logic: BaseLogic<MovementEvent>? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_movement, container, false)
}
override fun onStart() {
super.onStart()//in this example we retrieve some arguments via the Navigation Library from Jetpack
logic?.handleEvent(
MovementEvent.OnStart(
MovementFragmentArgs.fromBundle(arguments!!).movementId
)
)
}
}
What About LiveData, Coroutines, RxJava…?
If you think about it for a moment, you should realize than any of these tools will allow you to pass some kind of class/enum through a single method that can represent multiple different events or states. Some of these tools will also allow you to model events in a functional style, or you may also use Function references in Kotlin, Java 8+, or any language that supports that feature.
We will look at some samples for that in the next article.
Happy coding, my cousin.
Social Media | Support
This article was written by Ryan Michael Kay. I am a self-taught programmer/engineer who creates educational content for on a wide variety of topics, on a wide variety of platforms. The best way to support me is to follow me on various platforms and join in with my developer community (we have hundreds of members!):
Announcements:
https://www.facebook.com/wiseassblog
https://twitter.com/wiseAss301
Tutorials & Courses:
Free Tutorials, Live Q&A, Live Coding:
https://www.youtube.com/channel/UCSwuCetC3YlO1Y7bqVW5GHg
Java Desktop Programming w/ JavaFX (Intermediate) — https://skl.sh/31pzCa1
Complete Beginner Introduction To Java Programming (Beginner — Intermediate) — https://skl.sh/3fZbjos
Android Apps With Kotlin & Android Studio (Beginner) — https://skl.sh/2ZU6ZT9
Material Design Android Programming w/ Kotlin (Intermediate) — https://skl.sh/2OrwrYZ
Connect:
Discord Group — https://discord.gg/2XVvCW
Slack Group — https://join.slack.com/t/wiseass/shared_invite/zt-3p7p67p1-ZEvEhlrznbksmvzS0Qtunw