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:
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.
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.
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.
@Inject
private
AsyncServerOperationExecutorFactoryService asyncExecutorFactory;
Calling AsyncServerOperationExecutorFactory
The interface provides only one method with four parameters of the following types:
String
String
Callable< ResultType >
ServerOperationCallback< ResultType >
This results in a nice call like this.
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.
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:
void finished( ServerOperationResult< ResultType> serverOperationResult ): This method is called when the execution was successful.
void error( Throwable t ): This method is called when the execution failed.
Usually your implementation will look like the code below.
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.
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.
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.
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.