VirtualList issue

Working on a Vaadin Flow v24.9.5 and I use a VirtualList to display some data with a lazy loading data provider. Suffering with displaying some empty rows when refreshing the data provider (filtering), when removing equals/hashCode methodes from MyEntity I’m getting duplicates instead of the empty rows.

Data provider:

        VirtualList<MyEntity> list = new VirtualList<>();

        CallbackDataProvider<MyEntity, MyEntityFilter> provider =
                DataProvider.fromFilteringCallbacks(
                        // fetch callback
                        (Query<MyEntity, MyEntityFilter> query) -> {
                            MyEntityFilter filter = query.getFilter().orElse(null);
                            Pageable pageable = PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query).and(defaultSort));
                            Page<MyEntity> pageResult = myEntityService.findByFilter(filter, pageable);
                            return pageResult.stream();
                        },
                        // count callback
                        (Query<MyEntity, MyEntityFilter> query) -> {
                            MyEntityFilter filter = query.getFilter().orElse(null);
                            long count = myEntityService.countByFilter(filter);
                            return (int) count;
                        }
                );
ConfigurableFilterDataProvider<MyEntity, Void, MyEntityFilter> filteredProvider = provider.withConfigurableFilter();
        list.setDataProvider(filteredProvider);
        filteredProvider.setFilter(currentFilter);

With the following renderer:

        list.setRenderer(new ComponentRenderer<>(myEntity-> {
            Card card = new Card ();
            card.setClassName("card ");
            card.addThemeVariants(CardVariant.LUMO_ELEVATED);
            card.setTitle(new Div(myEntity.getTitle()));
            card.add(myEntity.getBody());
            return card;
        }));

In MyEntity class I override equals/hashCode methods as the following:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof MyEntity)) return false;
        MyEntity that = (MyEntity) o;

        Long id = this.getId();
        Long thatId = that.getId();

        return id != null && id.equals(thatId);
    }

    @Override
    public int hashCode() {
        Long id = this.getId();
        return (id != null) ? id.hashCode() : System.identityHashCode(this);
    }

Filtering action:

        search.addClickListener(buttonClickEvent -> {
                ConfigurableFilterDataProvider<MyEntity, Void, MyEntityFilter> filter =
                        (ConfigurableFilterDataProvider<MyEntity, Void, MyEntityFilter>) list.getDataProvider().withConfigurableFilter();
                filter.setFilter(currentFilter);
                list.getDataProvider().refreshAll();
        });

I think this is the culprit and refreshing should not be necessary when applying a filter

1 Like

you can use either *.setFilter or *.refreshAll() if the dataProvider already contains the filter object and you only mutate the content of that filter object (e.g. via setter methods).
But I don’t think this is the reason for your mentioned behavior … is your equalsmethod totally correct regarding NULL that entities ?

1 Like

That doesn’t look correct. .withConfigurableFilter() creates a new DataProvider that wraps your “old” one. If you don’t connect this new instance with your VirtualList the filter won’t be correctly applied.

Try something like

public class MyList extends VirtualList<MyEntity> {

    private final ConfigurableFilterDataProvider<MyEntity, Void, MyEntityFilter> filteredDataProvider;

    public MyList(final DataProvider<MyEntity, MyEntityFilter> realDataProvider) {
        super();

        this.filteredDataProvider = realDataProvider.withConfigurableFilter();
        setDataProvider(filteredDataProvider);
    }

    public void updateFilter(final MyEntityFilter newFilter) {
        this.filteredDataProvider.setFilter(newFilter);
    }
    
}

and then call it like

        search.addClickListener(buttonClickEvent -> list.updateFilter(currentFilter));

PS: setFilter(...) invokes refreshAll() thus there is no need to call it yourself.

1 Like