Search code examples
callbackvaadin-flowdataprovidervaadin-gridvaadin14

How to Vaadin CallbackDataProvider make an asyncronous fetch data?


Vaadin 14. CallbackDataProvider. When service is slow and answer time is long then Grid connected to CallbackDataProvider is freezes with all UI. Some example:

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.provider.CallbackDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

public class GridWithSortableColumnsView extends VerticalLayout {

    public GridWithSortableColumnsView() {
        addClassName("dialogwithgrid-view");
        setSizeFull();

        // prepare Data
        List<GridRow> gridRows = new LinkedList<>();
        for (int i = 0; i < 10000; i++) {
            gridRows.add(new GridRow(i, RandomStringUtils.randomAlphanumeric(10)));
        }

        CallbackDataProvider<GridRow, Void> dataProvider = new CallbackDataProvider<>(
                query -> {

                    // Slow service answer is here
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    Notification.show("Offset=" + query.getOffset() + " Limit=" + query.getLimit());
                    return gridRows.stream()
                            .sorted((o1, o2) -> StringUtils.compare(o1.getCol05(), o2.getCol05()))
                            .skip(query.getOffset())
                            .limit(query.getLimit());
                },
                query -> gridRows.size());

        Grid<GridRow> grid = new Grid<>();
        grid.setPageSize(10);
        grid.setSizeFull();
        grid.addColumn(GridRow::getId).setHeader("Id");
        grid.addColumn(GridRow::getCol05).setHeader("col05");
        grid.setDataProvider(dataProvider);

        add(grid);
    }


    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    @AllArgsConstructor
    @Getter
    @Setter
    public static class GridRow {
        @EqualsAndHashCode.Include
        private int id;
        private String col05;
    }

}

Session locked and all UI components freezes while scrolling this Grid. How it can be more faster and async? I think about caching inside own DataProvider extends CallbackDataProvider, but it will filled after the Grid receive empty data on first request and may be used only after some time with refreshing DataProvider again. It is not useful for me. In my real project fetched data changes frequently and has amount of items, up to 100000 items for one Grid, so i need to use CallbackDataProvider with limited request size.


Solution

  • Vaadin 23:

    In Vaadin 23 there is now a new feature, which allows you to define executor for DataCommunicator to use for asynchronous operation with Grid. If the value is null, DataCommunicator will operate synchronously and this is the default. If executor is defined, the fetch callback is run by executor and Grid is updated using UI#access(..). This requires Push to be enabled.

    grid.getDataCommunicator().enablePushUpdates(Executors.newCachedThreadPool());
    

    Vaadin 14:

    One can attempt to reduce query time with caches. You can either use some generic cache like ehcache, or integrate cache in your data provider. It is application specific which is better for you, global or local cache.

    If the query is still taking that long, then I would propose alternative approach for your UI. Instead of using callback data provider, use Grid with in memory data provider, but do not load whole data to the data provider at once. Instead create a paged view. Query new data when user clicks e.g. "next"/"previous", etc. buttons. And update the UI using UI#access method async when query completes.