Front End — Back End Interactions As Events In Java 8+ | Method References, Functional Interfaces
This article is a continuation of a previous one on modelling UI Interactions with events. If you are interested in applying Event Driven Programming to your code, please read that article first for a more complete picture: Simplify Your UI Interactions With Events | Java, Kotlin, Any Language
The picture above is from my early notes (circa 2017) on software architecture, asynchronous data streams, and event driven programming. I was in the midst of struggling to implement RxJava 2 in a multi-module clean architecture Android application; while at the same time trying desparately trying to practice test driven development in my code.
If that sounds quite esoteric, rest assured that the general principles and small patterns I will discuss in this article are much simpler. Keep that picture in mind though; all of these concepts can work together.
In this article, we will look at:
- How to model front end to back end communication with a Continuation Interface (a pattern we see in tools like Kotlin Coroutines or RxJava)
- How to combine some powerful features of Java 8+ (we can now use them in Android too; keep reading) such as Functional Interfaces, Method References, and Lambda Expressions to achieve the same goal
Functions Instead Of Models
As I mentioned in the previous article, a simple approach to modelling events is to use some kind of a fixed data structure, such as classes and enums…
public class TaskViewEvent {private final Event event;
private final Object value;//...public enum Event {
ON_COLOR_BUTTON_CLICK,
ON_COLOR_SELECTED,
ON_DONE_CLICK,
ON_ICON_SELECTED,
ON_START
}
}
…or purpose built language features like Kotlin’s sealed classes:
sealed class MovementEvent {
object OnShowVideoClick : MovementEvent()
object OnImageClick : MovementEvent()
data class OnStart(val movementId: String?): MovementEvent()
}
I find this pattern particularly useful in modelling front end UI interactions, but when it comes to interactions between the back end and front end (FE-BE), I like to do things differently.
With some notable exceptions which might require paging or managing buffered data streams, most of the FE-BE code that we write is pretty straight forward:
- Ask one or more IO devices for some data (local databases or a remote REST APIs are all IO devices)
- Successfully receive the data requested or fail with an exception
This is a different problem than UI events, which implies that different patterns might help.
Continuation (Callback) Interface
A continuation is just another word for a callback. To achieve this simple approach, define some kind of interface which is capable of representing FE-BE interactions:
public interface Continuation<T> {
public void onSuccess(T result); public void onException(Exception e);
}
Using this approach is pretty straight forward. Assuming you understand interfaces and Generic Types (what the T is). If any of this is fuzzy, I cover all of this and much more in my comprehensive introduction to Java programming.
Here we have an IO device…
public class TaskStorageImpl implements ITaskStorage {
private ApplicationExecutors exec;
private final File pathToStorageFile;
//...
}
…and the interface that it implements, which uses Continuation as a method parameter:
public interface ITaskStorage {
public void getTasks(Continuation<Tasks> continuation);
public void getTask(int taskId, Continuation<Task> continuation);
public void updateTask(Task task, Continuation<Void> continuation);
}
When we want to actually execute a FE-BE request, our client/caller, which is probably a front end logic class (could be a Controller, Presenter, ViewModel etc.), calls the interface function and supplies a Continuation:
public class TaskViewLogic extends BaseViewLogic<TaskViewEvent> { private ITaskViewContract.View view;
private ITaskViewContract.ViewModel vm;
private ITaskStorage storage;
//...private void onStart() {
int taskId = vm.getTask().getTaskId(); storage.getTask(taskId,
new Continuation<Task>() {
@Override
public void onSuccess(Task result) {
vm.setTask(result);
view.setButtonColor(result.getTaskColor());
view.setName(result.getTaskName());
view.setIconSelection(result.getTaskIcon());
} @Override
public void onException(Exception e) {
view.showMessage(e.getMessage());
view.goToTaskListActivity();
}
}
);//...
}
As you can see here, the two events we are concerned about, whether we got the data from the IO Device(s) or not, are modelled as functions instead of data structures (if you will allow me some liberty with definitions).
In this example, I used the Java Executors framework to manage background/mainThread concurrency; let us see how this works on the other end:
public class TaskStorageImpl implements ITaskStorage {
private ApplicationExecutors exec;
//...@Override
public void getTask(int taskId, Continuation<Task> continuation) {
exec.getBackground().execute(
() -> {
//jump into a background thread Object data; try {
Tasks tasks = loadTasks(); data = tasks.getTaskById(taskId); } catch (Exception e) {
data = e;
} final Object finalData = data; exec.getMainThread().execute(
() -> { //jump back onto the main thread
if (finalData instanceof Task)
continuation.onSuccess(
(Task) finalData
);
else continuation.onException(
(Exception) finalData
); }
);
});
}//...
}
Technical details of concurrency tools aside, the point is that we “continue” the execution of the program in the front end, by having the back end call back to it via this continuation.
Java Functional Interfaces And References
Java 8+ has some cool features that did not play nice with Android for a number of years. If you have avoided using lambda expressions, functional interfaces, method references, and useful types like Optional, this article will help you sort that problem out.
In this case, we will combine functional interfaces with method references to achieve a very similar outcome; that arguably looks nicer. I did not have any real source code handy for this, so this will be bordering on pseudocode but still compilable.
First, our interface now uses the Consumer<T> functional interface for success and failure events:
interface IStorage {
public void getData(
Consumer<String> onSuccess,
Consumer<Exception> onError
);
}
You will need to pick the correct functional interface for the problem; but this can be worked out by reading the documentation. A Consumer<T> represents “an operation that accepts a single input argument and returns no result.” If it needed an argument like a taskId, then we would want to use Function<T,R>.
On the back end, our storage device calls the appropriate function based on whatever events occur:
public class StorageImpl implements IStorage {
@Override
public void getData(Consumer<String> onSuccess,
Consumer<Exception> onError) {
try {
String someData;
//call some I/O device
onSuccess.accept(someData);
} catch (Exception e){
onError.accept(e);
}
}
}
Finally, in our calling class, we make use of method reference syntax to supply our functions:
public class ViewLogic {
private IStorage storage;
public void onStart(){
storage = new StorageImpl();
storage.getData(
this::onSuccess,
this::onException
);
}
private void onSuccess(String data) {
//bind to view
}
private void onException(Exception exception) {
//handle error
}
}
If you are fond of lambda expressions, you could of course use them instead:
public void onStart(){
storage = new StorageImpl();
storage.getData(
//onSuccess
someData -> {
//bind to view
},
//onError
exception -> {
//handle error
}
);
}
At the end of the day, the differences between these approaches are pretty minimal, but Java is a HORRENDOUSLY VERBOSE language, so anything we can do to fix that is a bonus.
Or just switch to Kotlin.
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