Blog

Don't let deep linking code clutter your UI logic

By  
Matti Tahvonen
Matti Tahvonen
·
On Jan 24, 2023 5:39:42 PM
·

I recently did an exercise to clean up one of the most used view templates in the start.vaadin.com tool, the MasterDetailView. I implemented almost a dozen tricks to make the code readable and maintainable. I plan to cover some of those in the upcoming weeks, so I decided to start cleaning up the deep linking (aka routing) code, as that topic is coming up periodically in our Vaadin community chat.

diagram-1Components in the original code template effectively use browser URLs to communicate.

The MasterDetailView template and our documentation, by default, suggest using Vaadin's Router for pretty much everything where the URL parameters would need to be maintained. This approach can quickly start to clutter the UI code. As the Router builds around the string-based URL, it can't provide similar type safety, clarity, and documentability to pure Java code. In the original code, two components of the view, grid and form, communicate through a string in the browser's address bar.

diagram-2

An enhanced version of the same view uses Java code to pass objects from the grid to the form.

My strategy is to use the Router API only to decode the deep linking details from the URL when a user arrives at the view and to do the top-level view transitions. Inside the views, I use pure Java to define the UI logic and maintain the "deeper" deep linking details as a side effect where necessary. This approach passes the selected object from the grid to the form using pure Java.

Let's go through the changes I did at the code level. Here is the initial state of a code from the original MasterDetailView that runs when users pick a row from the grid to a selection the form:

// when a row is selected or deselected, populate form
grid.asSingleSelect().addValueChangeListener(event -> {
   if (event.getValue() != null) {
       UI.getCurrent().navigate(String.format(SAMPLEPERSON_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
   } else {
       clearForm();
       UI.getCurrent().navigate(MasterDetailView.class);
   }
});

Even with the helpful comment, it is hard to understand what happens. And even if you are already a master of Vaadin’s routing functionality, there is no way to jump to the code that does the “population of the form” using IDE. In the else clause, there has been an attempt to make the code more readable. Still, it is unclear why we need to navigate from the current view to the same view again, although already maintaining the UI with the clearForm method (editor guess: a clever way to clear entity id from the URL).

Now here is my enhanced version of this same code snippet:

grid.asSingleSelect().addValueChangeListener(event -> {
   var selectedPerson = event.getValue();
   if(selectedPerson == null) {
       prepareFormForNewPerson();
   } else {
       editPerson(selectedPerson);
   }
});

At first glance, you'll see self-documenting Java methods that describe what happens on a row selection instead of cryptic calls to navigate methods. You could accomplish this by wrapping those navigate calls into meaningful Java methods, but that's only some of what was done. Let's dig into this new editPerson method as well:

private void editPerson(SamplePerson person) {
   binder.setBean(person);
   formHasChanges = false;
   updateRouteParameters();
}

Instead of calling the navigate methods, I’m now really doing that “population of the form”: binding data from the selected row to the UI components using Binder. The same code is in the original example in some form, but you’ll find it only through some URL parameter handling, not through the UI event listener that effectively triggers it. Also, in the original example (not shown here), the person object must be re-fetched from the backend, as the parameter handling only receives the identifier.

This method can (and is) also used elsewhere within the view. The formHasChanges flag is irrelevant for this blog post, but updateRouteParameters is relevant if you wish to maintain the deep linking feature that the original example also has. Before looking at the implementation, I want to remind you that only some views need that functionality. So if you don’t need deep linking at this level, you can remove all routing-related code.

private void updateRouteParameters() {
   if(isAttached()) {
       String deepLinkingUrl = RouteConfiguration.forSessionScope().getUrl(getClass());
       if(binder.getBean().getId() != null) {
           deepLinkingUrl = deepLinkingUrl + String.format(SAMPLEPERSON_EDIT_ROUTE_TEMPLATE, binder.getBean().getId().toString());
       }
       getUI().get().getPage().getHistory()
               .replaceState(null, deepLinkingUrl);
   }
}

The original reason for using the UI.navigate method was to maintain the edited person id in the URL so that a user can share a direct link from the browser’s location bar. This method is doing exactly that and only that. We’ll first get the base URL of the view and then append the parameters in the same manner as in the original code. But instead of navigating via the UI object, we update the URL directly with the History object. It doesn’t cause navigation events or fire parameter handlers, but that is no longer needed because the UI was updated naturally using more readable Java code.

The method could have been implemented more concisely for this trivial example. Still, I suggest always writing it so you can call the method from any part of your UI logic when you know you want the deep linking details to be updated. This way, you’ll have deep-linking related code only in two places: in this updateRouteParameters method and in BeforeEnterListener (or setParameter method or wherever method you are using to decode details from the URL to the initial UI state).

Key takeaways

Although the original approach may have fewer lines of code (especially in the form in my example), I suggest this approach for every Vaadin developer. As a summary of benefits, you’ll get:

  • More readable and maintainable code.
  • The ability to navigate within your UI code using IDE as you have used to when working with Java in general.
  • Easier maintenance of the deep-linking logic since it is held in only two methods instead of scattered around the UI code.
  • Easier to add deep linking code to an existing view.
  • Easier to drop it altogether if you find out that you don’t need deep linking for that view.
  • Fewer backend queries as DTOs don’t need to be "re-fetched" from the URL parameter handling in UI code.

Check out my enhanced MasterDetailView on GitHub.

Matti Tahvonen
Matti Tahvonen
Matti Tahvonen has a long history in Vaadin R&D: developing the core framework from the dark ages of pure JS client side to the GWT era and creating number of official and unofficial Vaadin add-ons. His current responsibility is to keep you up to date with latest and greatest Vaadin related technologies. You can follow him on Twitter – @MattiTahvonen
Other posts by Matti Tahvonen