Can Any one help Me on How to Implement on Handling Pagination on Scrolling of listview in xamarin.android .any link or any sample wil be helpful
Well, Android pagination is quite easy in comparison to iOS and can be done as follows:
public class EndlessScrollListener : Java.Lang.Object, Android.Widget.AbsListView.IOnScrollListener
{
private int visibleThreshold = 5;
private int currentPage = 0;
private int previousTotal = 0;
private bool loading = true;
public EndlessScrollListener()
{
}
public EndlessScrollListener(int visibleThreshold)
{
this.visibleThreshold = visibleThreshold;
}
public void OnScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if (loading)
{
if (totalItemCount > previousTotal)
{
loading = false;
previousTotal = totalItemCount;
currentPage++;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold))
{
// I load the next page of gigs using a background task,
// but you can call any function here.
//new LoadGigsTask().execute(currentPage + 1);
loading = true;
}
}
public void OnScrollStateChanged(AbsListView view, [GeneratedEnum] ScrollState scrollState)
{
// throw new NotImplementedException();
}
}
Then set it to the listview as follows:
List.SetOnScrollListener(new EndlessScrollListener());
Working of code:
visibleThreshold – The minimum amount of items to have below your current scroll position, before loading more.
currentPage – The current page of data you have loaded.
previousTotal – The total number of items in the dataset after the last load.
loading – True if we are still waiting for the last set of data to load.
Next, we have a couple of constructors that allow us to set the visibleThreshold inline if we want.
The first overridden method we have is called every time the list is scrolled. This happens many times a second during a scroll, so be wary of the code you place here. We are given a few useful parameters to help us work out if we need to load some more data, but first, we check if we are waiting for the previous load to finish.
If it’s still loading, we check to see if the dataset count has changed, if so we conclude it has finished loading and update the current page number and total item count.
If it isn’t currently loading, we check to see if we have breached the visibleThreshold and need to reload more data. If we do need to reload some more data, we execute a background task and set the loading flag to true. Thus solving the problem forever!
The last method in the class we do not need, however, if you’re interested, it is primarily used for tracking changes in the scroll action itself via the scrollState parameter.
Finally, the code to call the class creates a new instance of EndlessScrollListener and bind’s it to a ListView of mine. Of course, put your own ListView in place of List.
Related
Use Case 1 is answered below, Use Case 2 has been moved to a separate question (Vaadin Flow: Returning to a view, the view should not reload data from the backend)
I'd like to use a Vaadin Flow (v14 LTS/v19) grid component backed by a lazy DataProvider which does not automatically fetch data from the backend when the grid is shown.
There are at least two use cases:
showing grid data does not make sense unless the user provided filter parameters
returning to a #PreserveOnRefresh tagged view should not replace the shown data with current data. (further elaborated in update)
Being pretty new to Vaadin 14+, I could not figure out how to achieve this. Every time my GridView is displayed, the count and fetch callbacks of DataProvider are queried. The call originates from the DataCommunicator of the grid.
So for Use Case 1: How to stop the DataProvider from fetching data as long as it does not make sense?
And for Use Case 2: How to prevent overwriting the grid state when adding a grid to the UI for the second time?
Thanks a lot!
StackTrace to my fetch callback (Vaadin Flow 14):
at org.vaadin.example.GridView.fetch(GridView.java:46)
at org.vaadin.example.GridView.lambda$new$c4b2c115$1(GridView.java:23)
at com.vaadin.flow.data.provider.CallbackDataProvider.fetchFromBackEnd(CallbackDataProvider.java:137)
at com.vaadin.flow.data.provider.AbstractBackEndDataProvider.fetch(AbstractBackEndDataProvider.java:61)
at com.vaadin.flow.data.provider.DataCommunicator.fetchFromProvider(DataCommunicator.java:362)
at com.vaadin.flow.data.provider.DataCommunicator.activate(DataCommunicator.java:647)
at com.vaadin.flow.data.provider.DataCommunicator.collectKeysToFlush(DataCommunicator.java:589)
at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:461)
at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425)
at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$2(StateTree.java:390)
at [java.util.stream] omitted
at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:387)
at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411)
at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187)
at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:122)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:91)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1547)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
update 20210430
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
// filter omitted
final Button refresh = new Button("refresh");
refresh.addClickListener(e -> {
System.out.println("refresh clicked");
g.getDataProvider().refreshAll();
});
add(refresh);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
new Exception().printStackTrace(); //figure out who called
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
My MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
MainView is an AppLayout used to switch the contents of the AppLayout from GridView to EmptyView and back.
Use Case 2 is: When returning to GridView, the GridView should be exactly same state as before (which works fine with the TextField).
open GridView -> grid should not be filled with data
enter filter params (not shown in code)
click "refresh" to populate the grid
enter "Spiderman" in TextField "stateCheck"
switch to EmptyView
in the real app: do something in EmptyView and potentially other views
return to GridView -> the grid should not reload the data, it should just stay as it was - just like the TextField still displays "Spiderman", the grid should display the same data as before without reloading it.
For Case 1: In the callback check if you have filter parameters, return an empty set if not. Using the new V17+ API it would look like this:
grid.setItems(query -> {
if(filterParameters.isEmpty()) {
// Return an empty stream
} else {
// Fetch from backend
}
});
You can read more in the docs here: https://vaadin.com/docs/latest/flow/binding-data/data-provider (V19) or https://vaadin.com/docs/v14/flow/binding-data/tutorial-flow-data-provider (V14)
I would need more info on what you're currently doing to help out with Case 2. How are you constructing the view, what does your code look like? A full stack trace with the "Caused by" would also help.
I would recommend only setting the DataProvider to the Grid once the first filter parameter is set. The client-side Grid expects to receive the number of items it requires from the fetch query; it might work in some corner case if you don't provide the requested numbers of items from fetch, but it's not designed to behave like that.
Note that this applies specifically to using DataProviders with filters in Vaadin 14 series - Vaadin 17 introduced a new optional simplified way of fetching items, which changes this equation a bit. It's not backported to Vaadin 14 yet (currently planned for 14.7).
Split out from Vaadin Dataprovider: how to avoid "auto-fetch"?.
Given a Vaadin Flow 19 app with a MainView extends AppLayout, a GridView and an EmptyView And #PreserveOnRefresh annotation is used on MainView.
When returning to GridView, the GridView should be exactly in the same state as before:
open GridView using button in MainView for the first time -> Grid uses DataProvider to fetch data from backend
enter "Spiderman" in TextField with caption "stateCheck"
switch to EmptyView using button in MainView
in the real app: do something in EmptyView and potentially other views
return to GridView using button in MainView for the 2nd time
Then (1) the TextField with caption "stateCheck" should display the value "Spiderman"
And (2) the grid should still show the same data as before; it should not reload the data from the backend
Observed behaviour:
(1) is ok, but (2) not: the grid always calls fetch method to get data from the backend.
How do I achieve the desired behavior?
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
This is actually intentional behavior. The server side dataprovider listener needs to be removed when component is detached and rewired on attaching. The reason is that otherwise there would be listeners accumulating and producing a memory leakage. If you think your users would be using refresh page often, you should consider adding a cache to your application to optimize performance.
Now one could entertain with the idea of having this kind of caching of previous loaded data behavior via API in Grid also in Vaadin framework, as it may or may not be desirable. It is application specific.
If the use case of refreshing is really to get the fresh data of live and active database, it is actually desired that data is loaded when page is refreshed.
If the desire is to avoid extra bombarding of DB as data is known to be static, you want to have caching.
I have an app where a particular parent that has n elements lets say 10 elements (tiles) and only 5 are present in the dom and visible in the current screen. The next 5 would appear in dom/screen on swiping up. How do I swipe to a particular element say 7th element?
I'm not able to get find the element by id/name as the element is not in dom so I'm not able to use swipe until visibility of an element. also, the number of such elements varies based on how the backend system is configured so I cannot make a fixed number of swipes. Also since I do not know the last element in the parent I'm at risk of running into an infinite loop if the particular element is not present in the app (due to a bug).
Please help on how to solve this.
You can use following methods to scroll down until expected element. To avoid the infinite loop you can keep count of while loop.
public void scrollDown()
{
TouchAction action = new TouchAction((AppiumDriver)driver);
action.press(new PointOption().point(300, 701)).waitAction(newWaitOptions().waitOptions(Duration.ofSeconds(2))).moveTo(new PointOption().point(300, 441)).release();
action.perform();
}
public void scrollTillElement(By element)
{
int countofLoop = 0;
List<WebElement> elements = driver.findElements(element);
while (elements.size()==0)
{
countofLoop++;
scrollDown();
elements = driver.findElements(element);
if(countofLoop==20)
{
break;
}
}
}
I'm looking for right way to create a table that could upload new rows dynamically. As a DataSource I'm using SQLContainer with TableQuery. There could be much of data which should be uploaded quickly.
Anyway, my current realization is following:
Table messagesList = new Table();
...............................
messagesList.setCacheRate(0.1d);
messagesList.setContainerDataSource(messagesContainer);
messagesList.setSelectable(true);
messagesList.setImmediate(true);
messagesList.setSizeFull();
new InitializerThread().start();
...............................
Data is uploading using refreshRowCache method and Vaadin Push tecknology in another thread:
class InitializerThread extends Thread {
#Override
public void run() {
while (!Thread.interrupted()) {
try {
Thread.sleep(refreshMessagesPeriod);
} catch (InterruptedException e) {
}
access(new Runnable() {
#Override
public void run() {
if (messagesList != null && !messagesList.getItemIds().isEmpty()) {
messagesList.refreshRowCache();
messagesList.focus();
}
}
});
}
}
}
This approach has many disadvantages:
1. If there are many rows in the table, it is very inefficient way to refresh all row's cache in the table everytime.
2. Scroll bar jumps to the top of page in the table when the row's cache is refreshing. I didn't find the way to save the scroll's position and set the previous scroll position after refresh.
3. If I select some text in a cell of the table, the selection dissapears after row's cache refresh.
I hope that there is a lightweight and more nice technique to fill new data into Table dynamically.
I use Vaadin 7.1.15 and it is allowed to change version of Vaadin type of table (instead com.vaadin.ui.Table) if necessary.
I found a better solution - control container content manually. Using IndexedContainer as a data source instead TableQuery and periodically checking a new data using sql queries. Vaadin Push helps me to visualise new rows. To prevent a scroll bar jump (which is the result of calling refreshRowCache) I call the private method Table.setCurrentPageFirstItemId(int, boolean) with following parameters: a new row ID, false (do not call refreshRowCache).
I have a scroll bar ( fl.controls.UIScrollBar ), which i create dynamically in a class, and add it to the stage.
public class Slider extends Sprite
{
private var scroll:UIScrollBar = new UIScrollBar();
// etc.
// constructor
addChild(scroll);
scroll.setSize(15.75, 205.3);
scroll.direction = ScrollBarDirection.HORIZONTAL;
scroll.setScrollProperties(150, minScrollPos, maxScrollPos,snapInterval);
scroll.addEventListener(ScrollEvent.SCROLL, scrollHandler);
}
Then, I try to call
scroll.scrollPosition = 30;
My method call will not update the scroll thumb.Any ideas why?
Salut Mihai,
I found that odd at first. I expected setting the value would suffice.
If I set scrollPosition in an enter frame loop, it works, but not if I use the setter straight away. This probably means that if you make the calls right after creating/setting up the component, internally, it's not ready yet. UIComponents(like UIScrollBar) have a whole lifecycle to deal with. Jeff Kamerer has a nice set of devnet articles on this, if you're interested.
Long story short, the component is not ready right away, so the best bet is to wait for it to be ready by listening to the RENDER event:
scroll.addEventListener(Event.RENDER,rendered);
function rendered(event:Event):void {
scroll.removeEventListener(Event.RENDER,rendered);
scroll.scrollPosition = 30;
}