Asynchronous loading of custom dashboard components

Introduction

A business dashboard is composed of a number of components, like a pie chart, a bar chart or a task list. If one of the used components doesn't work asynchronously it will not only block itself, but the entire user interface, until it has processed everything that is required to display the desired data. Because of that it isn't even possible to switch to another area of the web application while it is loading the information that has to be displayed.

This is why we introduced the possibility to load data for components asynchronously. In order to achieve an asynchronous behavior we introduced a service providing the possibility to execute an operation on the server in a separate thread. After the execution has terminated a callback is invoked which updates the user interface accordingly. To let a user know that data is still being processed and will be displayed afterwards we also provide a layout class which is capable of displaying a loading indicator.

Technical limitations

Asynchronous loading needs the following prerequisites to be used on a dashboard:

  • The web application needs to support push mode. If webfrontend.properties has the property web.vaadin.pushMode set to DISABLED, then synchronous loading will be used for all dashboards.

  • All components on a given dashboard must support asynchronous loading. If one of them does not, the whole dashboard will load synchronously. The following sections contain a guide on how to implement asynchronous loading in a dashboard component.

Asynchronous loading and Vaadin push mode detection

To determine if a component supports asynchronous loading use the interface AsyncLoadingFlexComponent. General support is indicated by following method:

Asynchronous loading support
AsyncLoadingFlexComponent.supportsAsynchronousLoading();

The method automatically returns true for all components of the type AbstractAsyncLoadingFlexComponent. The method may be overwritten to determine per component whether asynchronous loading is supported.
So a component is considered to support asynchronous loading only if it implements the AsyncLoadingFlexComponent interface and the method supportsAsynchronousLoading() returns true.

For determining whether asynchronous loading is finally to be used per component, use the flag useAsynchronousLoading of the interface AsyncLoadingFlexComponent.

Determine whether to use asynchronous loading
AsyncLoadingFlexComponent.isUseAsynchronousLoading();

The flag is set to false for all components during creation of a certain dashboard via AsyncLoadingFlexComponent.setUseAsynchronousLoading( boolean useAsynchronousLoading ) if one of these following to conditions is true:

  • One of the components on the dashboard does not support asynchronous loading.

  • Push mode is set to DISABLED.

Use the following code snippet to check if Vaadin's push mode is generally available.

Check Vaadin's push mode
UI.getCurrent()
.getPushConfiguration()
.getPushMode()
.isEnabled();

Useful code snippets and guidelines

The classes you will be working with are:

  • AsyncServerOperationExecutorFactory: Provides an interface to pass a Callable< ResultType > and an implementation of ServerOperationCallback< ResultType > for creating a AsyncServerOperationExecutor.

  • AsyncServerOperationExecutor: The actual executor.

  • ServerOperationCallback< ResultType >: Interface that has to be implemented for handling a callback from AsyncServerOperationService.

  • AbstractAsyncLoadingFlexComponent: An abstract component you should extend to build you own components. It implements the interface AsyncLoadingFlexComponent, which provides the necessary interface for controlling the overall dashboard behavior.

Getting AsyncServerOperationExecutorFactory

This is done by a simple injection.

Getting {{AsyncServerOperationExecutor}}
@Inject
private AsyncServerOperationExecutorFactoryService asyncExecutorFactory;

Calling AsyncServerOperationExecutorFactory

The interface provides only one method with four parameters of the following types:

  1. String

  2. String

  3. Callable< ResultType >

  4. ServerOperationCallback< ResultType >

This results in a nice call like this.

Calling {{AsyncServerOperationExecutor}}
public void doSomething()
{
AsyncServerOperationExecutor executor = this.asyncExecutorFactory.create( REQUEST_TYPE, this.componentId, asyncServerOperationCallable, new ServerOperationCallbackImplementation< ResultType > );
executor.execute();
}

You'll be given an example on how to build each of the three required parameters below.

Implementing Callable< ResultType >

You have to implement the method call() with a fitting return type. Usually this would call another method which does the actual work for a better readability.

An implementation may look like the following example.

Extending {{AbstractServerOperationRequest}}
Callable< ResultType > asyncServerOperationCallable = new Callable< ResultType >()
{
@Override
public ResultType call() throws Exception
{
return getSomeResultType();
}
};

Implementing ServerOperationCallback< ResultType >

For handling the callback after a Callable< ResultsType > has been executed you need to implement the interface ServerOperationCallback< ResultType >. To do so you have to override two methods:

  1. void finished( ServerOperationResult< ResultType> serverOperationResult ): This method is called when the execution was successful.

  2. void error( Throwable t ): This method is called when the execution failed.

Usually your implementation will look like the code below.

Implementing {{ServerOperationCallback< ResultType >}}
private class MyCallback implements ServerOperationCallback< ResultType >
{
@Override
public void finished( ServerOperationResult< ResultType > serverOperationResult )
{
handleServerOperationResult( serverOperationResult.getOperationResult() );
}
 
@Override
public void error( Throwable t )
{
handleServerOperationError( t );
}
}

We recommend implementing this as a private inner class of your dashboard component class, because it is unlikely that anything else needs access to it. Of course there might exceptions from this recommendation, depending on the dashboard components you're implementing.

Purpose of using a generic < ResultType >

The API is implemented using generics, so it is easy to guarantee that it always returns the expected type and thereby avoids casting classes of type Object.

Extending AbstractAsyncLoadingFlexComponent

AbstractAsyncLoadingFlexComponent is an abstract component that implements the interface AsyncLoadingFlexComponent.

A component supporting asynchronous loading by extending AbstractAsyncLoadingFlexComponent must still implement synchronous loading. This is because if there is any component on a dashboard that doesn't support asynchronous loading all other components on that dashboard have to behave like they're loading their data synchronously.

By implementing the interface AsyncLoadingFlexComponent there are three methods controlling the component's overall loading behavior.

Method

Return type

Default value

supportsAsynchronousLoading()

boolean

true

isUseAsynchronousLoading()

boolean

true

setUseAsynchronousLoading( boolean useAsynchronousLoading )

void

-

Method supportsAsynchronousLoading()

The method specifies whether the component supports loading data asynchronously or not. It is necessary so that the underlying dashboard can decide if it is possible to use asynchronous loading at all.

Method isUseAsynchronousLoading()

The method specifies if the component actually uses asynchronous loading. After all, there might be circumstances in which a developer decides to actively disable that capability. The method is necessary so that the underlying dashboard can decide if it is possible to use asynchronous loading at all.

Method setUseAsynchronousLoading( boolean useAsynchronousLoading )

This method allows to enable or disable asynchronous loading. It can be used by a developer to actively control the component's behavior. It is necessary for the underlying dashboard to have that method implemented so that asynchronous loading can be deactivated in case another component on that dashboard doesn't allow for asynchronous loading to be used at all.

User interface

For implementation convenience a dashboard component should use LoadingIndicatorVerticalLayout as main layout for displaying the loading indicator.

An example for switching it on and off is in the code snippet below.

Switching the loading indicator on and off
private void setLoadingIndicatorVisible( boolean visible )
{
Component loadingIndicator = ( ( LoadingIndicatorVerticalLayout ) this.mainLayout ).getLoadingIndicator();
if ( loadingIndicator != null )
{
loadingIndicator.setVisible( visible );
}
}

Piecing it all together

With all the examples above your resulting class will probably look similar to the one below. We highly recommend sticking to the practice of using private inner classes and dealing with results and errors in the actual class. This way the requests and callbacks in each of your dashboard components should look very similar.

Example implementation
public class AsyncLoadingExample extends AbstractAsyncLoadingFlexComponent
{
private static final String ASYNC_LOADING_EXAMPLE_REQUEST = "AsyncLoadingExample.Request"; //$NON-NLS-1$
@Inject
private AsyncServerOperationExecutorFactory asyncExecutorFactory;
private final String componentId = UUID.randomUUID().toString();
private Integer currentValue;
 
@Override
public void refresh( boolean reload ) // refresh method from interface FlexComponent
{
if( reload ) {
loadData();
} else {
showData( this.currentValue );
}
}
 
public void loadData()
{
if( isUseAsynchronousLoading() ) {
setLoadingIndicatorVisible( true );
 
Callable< Integer > asyncServerOperationCallable = new Callable< Integer >()
{
@Override
public Integer call() throws Exception
{
return loadingOperation();
}
}
 
AsyncServerOperationExecutor executor = this.asyncExeutorFactory.create( ASYNC_LOADING_EXAMPLE_REQUEST, this.componenteId, asyncServerOperationCallable, new MyServerOperationCallback() );
executor.execute();
} else {
// load data synchronously, must always be supported as well
this.currentValue = loadingOperation();
showData( this.currentValue );
}
}
 
public Integer loadingOperation()
{
// use only server operations, no UI relevant code
return new Integer(1);
}
 
public handleAsyncServerOperationResult( Integer value )
{
this.currentValue = value;
// visualize the result - in case the result is empty display a text like 'No data available'
setLoadingIndicatorVisible( false );
showData( value );
}
 
public void handleAsyncServerOperationError( Throwable t )
{
LOG.error( "Error loading data asynchronously for AsyncLoadingExample", t );
setLoadingIndicatorVisible( false );
// display error as text inside dashboard component
showError( t );
}
 
private void showData( Integer value ) {
// visualize data in component
}
 
private void showError( Throwable t ) {
// visualize error as text, do not show stack traces, use short description like 'Error on loading'
}
 
private class MyServerOperationCallback implements ServerOperationCallback< Integer >
{
@Override
public void finished( ServerOperationResult< Integer > serverOperationResult )
{
handleAsyncServerOperationResult( serverOperationResult.getOperationResult() );
}
 
@Override
public void error( Throwable t )
{
handleAsyncServerOperationError( t );
}
}
}

Thread pool for background execution

The techniques described above utilize a thread pool for background execution. Details on the thread pool's configuration are described in the chapter 'Asynchronous loading thread pool configuration' of the Configuration Manual.

Memory consumption

Please keep in mind that dashboard components usually aggregate data for a lot of items, products or variants and thus consume a considerable amount of memory.