question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Design for varying size of data provider

See original GitHub issue

Part of #8052 and follow up for #7968. Based on initial discussion the cases we would want to support are:

  1. Current case where the size of the data set is known beforehand and the existing size query produces the actual size of the data. Thus all existing apps should keep working with this without any code changes.
  2. Size is unknown and not provided - it is not necessary to the user to do anything with the size query, e.g. not even implement it or just return -1 or such. The component will keep fetching things until there is no more data provided. Not sure what should the component use as a guestimate for the initial data size to adjust scroll bar if applicable.
  3. Giving an estimated size that might change. Thus the application developer can return a initial size of 1000 and at some point update that the size is actually something else - without causing a full reset for the data (full data reset naturally occurs when query state like sorting or filtering changes)

Couple related things but don’t need to be fixed at the same time, but what can be can be taken into account now and ticketized&investigated separately: a) the application developer can define the optimal page size for the query, instead of some default 50 items b) removing or adding items to the data set should work without refreshAll, meaning the data provider and the component will just add/drop related items and adjust any size indicators accordingly c) It has been requested in FW8 and partly here #4510 that it would be helpful if we expose an API that providers external access to what happens to the size inside the data provider / component

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
plekucommented, Apr 23, 2020

So, my current thoughts based on earlier investigation and discussion today with the team, followed by more investigation:

Goal: Undefined size for data provider. It should be possible for the application developer to change the “estimated” size.

    Approach A
     Introduce DynamicDataProvider & HasDynamicDataProvider (name pending).
     (Another name could be SizelessDataProvider)

     Why introduce a subclass instead of a just adding things to DataProvider ?
     1) Keep existing components, data providers and their API & behavior untouched and 100 % backwards compatible
     2) Separate data providers with undefined/changing size from the current defined size.
        Components that don't support dynamic size, can keep requiring an explicit size
        and respect the existing contract with DataProvider and the component,
        where size is fetch first and then data is fetch until the end is reached.
        For any component that supports dynamic size, should implement HasDynamicDataProvider instead.
        Similar approach to separating ConfigurableFilterDataProvider.
        Also it would imply that any component that supports dynamic size is
        fine with the fact that the fetch query might not return the asked amount of items.
        Currently this will explode (our) components and might do so for custom components too.

     Using a normal data provider in any component is still possible by having
     HasDynamicDataProvider to just support setting DataProvider too, but using a DynamicDataProvider
     should not be possible with a component that doesn't support dynamic data but would always
     require the size up front and cannot adjust to it without a reset.
     This would imply DynamicDataProvider doesn't inherit DataProvider
     and would allow to not support dynamic data for hierarchical and filtering cases
     immediately but add support later incrementally if neeeded.
     However, to be able to use dynamic data providers (without implementing the size query)
     in components that require the size to be known up front and not modified, it could be possible
     to fetch more/less items than the initial size is, and then just call refreshAll().
     To be tested though.

     Approach B
     Make DataProvider support undefined size.
     Add methods to DataProvider with possible unsupported operation exception
     for anything that is not applicable for non-dynamic case. Or add an interface
     that components implement that allows specifying the callback for providing a
     size estimate. Supporting undefined size would be indicated by a isUndefinedSize() method in
     DataProvider, which by default returns false and components would then require
     the explicit size, like thus far.
     Need to look at this more closely on what would it mean for existing components
     and data provider implementations. Using a dynamic data provider implementation in a
     component that doesn't support needs to be handled either by:
     a) throwing an exception from the size query and making users aware of the issue
     b) keeping on track on estimated size and when it is reached, calling
     refreshAll() and causing the component to reset. This currently resets the scrolling position ???
     Need to look at more what it would entail. One question is how to provide
     the "size provider" in this case so that dynamic data providers could customize
     the size when needed. Maybe those could implement the size query and 
     simply return an "estimated size" whenever needed, like when previous limit is reached. 

Some examples below for approach A, not attaching the flow-data classes, but I think it could make sense to try to make approach B work instead.

public class DataProviderDemo extends Div {

    public DataProviderDemo() {
        GridDemo.PersonService personService = new GridDemo.PersonService();
        Grid<GridDemo.Person> grid = new Grid<>();
        // Examples - Approach A:

        // use case 0: explicit size, like it is set currently.
        CallbackDataProvider<GridDemo.Person, Void> provider = DataProvider
                .fromCallbacks(query -> personService
                                .fetch(query.getOffset(), query.getLimit()).stream(),
                        query -> personService.count());
        grid.setDataProvider(provider);

        // use case 1: Undefined size and nothing set. Query is repeat until nothing
        // returned (or less than asked)
        DynamicCallbackDataProvider<GridDemo.Person, Void> undefinedSizeDataProvider = DataProvider
                .fromCallback(query -> personService
                        .fetch(query.getOffset(), query.getLimit()).stream());
        // fromCallback is quite close to fromCallbacks but should be at top on
        // IDE autocomplete

        // grid will be switched to implement HasDynamicDataProvider that still
        // accepts DataProvider
        grid.setDataProvider(undefinedSizeDataProvider);

        // use case 2: How to change the data size for dynamic data provider ?

        // 2a) Maybe set from query (?) in case no access to component.
        // NOTE: this could just be replaced by the sizeProvider callback below
        DynamicDataProvider<GridDemo.Person, Void> dynamicCallbackDataProvider = DataProvider
                .fromCallback(query -> {
                    // Introduce a subtype for Query with added API to avoid methods in normal query without any effect ?
                    // -1 would be the value for "unset" and let component to decide
                    if (query.getEstimatedDataSize() == -1) {
                        query.setEstimatedDataSize(500);
                    }
                    // it would be possible to later on change the
                    return personService
                            .fetch(query.getOffset(), query.getLimit())
                            .stream();
                });

        // Add the estimated size as state to DynamicDataProvider implementations.
        // The component should not stop querying after this is reached but until data ends.
        dynamicCallbackDataProvider.getEstimatedDataSize(); // would initially return -1 for undefined

        // the initial query will execute from ^ and set the estimated size there
        grid.setDataProvider(dynamicCallbackDataProvider);

        // 2b) Change size at any point.
        // Fires an event from the DataProvider like with refreshAll()
        // that will notify the component that estimate might have changed, BUT
        // the component should not do anything if there is already 999 items shown and
        // estimated size is set to less items. So the change is only visual or maybe NOOP
        dynamicCallbackDataProvider.setEstimatedDataSize(666); // resetting guestimated size

        // if/when there would be adding & removing items possible from the data provider,
        // the estimated size would not change - as with normal DataProvider this should affect
        // the size (either queried again or just --/++). So for the use case where the
        // data provider size changes and component should update itself, there should still
        // be a reset call

        // 2c) Defining a callback that is used to
        // - get the initial size when set
        // - query for estimated size whenever bypassing the previous limit
        dynamicCallbackDataProvider.setSizeProvider(query -> {
            // check something based on query params like previous size, offset, previous size or filter
            // provide component that is used ?
            if (query.getPreviousSize() == -1) {
                return 1000;
            }
            return query.getPreviousSize() + query.getPageSize() * 4;
        });
        query -> {
            if (query.getOffset()+query.getLimit() < getEstimatedSize())
                return getEstimatedSize();

        }
        // The option 2c makes the most sense probably and support most cases,
        // adding other API is pointless ?
}
1reaction
mshabarovcommented, Apr 15, 2020

Acceptance criteria:

  • New API design for DataProvider/DataCommunicator/Components interfaces/classes has been suggested as well as the list of a new interfaces (if any) to provide use cases 1)-3) and a)-c) from above description.
  • Use cases examples provided for those new API with stubbed implementation.
Read more comments on GitHub >

github_iconTop Results From Across the Web

How To Use DataProviders In TestNG [With Examples]
DataProvider in TestNG is used to inject multiple values into the same test case, this guide explains how to use them in your...
Read more >
I am trying to use @DataProvider to run @test with Different ...
so i am using @DataProvider which is retuning two dimensional Object array. i am having one dimensional string so at first i am...
Read more >
How to get size of filtered dataprovider? - Vaadin
The answer is to use grid. getDataCommunicator(). fetchItemsWithRange(..) and get size of the result.
Read more >
Data Provider - Refine Dev
A data provider is the place where a refine app communicates with an API. ... possible to consume different API's and data services...
Read more >
How to Use DataProvider in TestNG + Basic Annotations
Learn the fundamentals of DataProvider and how it can be used in TestNG for automated testing. As the volume of applications increases, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found