Android Paging 3 is not loading next page - android-paging

After migrating from custom paging implementation to Jetpack Paging 3 library,
data is not loading as expected.
The first page is handled correctly according to the PagingConfig of Pager:
internal fun createProductListPager(pagingSource: ProductListPagingSource): Pager<Int, Product> = Pager(
config = PagingConfig(
pageSize = 10,
prefetchDistance = 2,
),
initialKey = 0,
) { pagingSource }
Here is an extract of the Adapter:
public class PagingProductCardAdapter(private val viewBinder: CoreViewBinder) :
PagingDataAdapter<Listable, RecyclerView.ViewHolder>(viewBinder.getDiffUtils()) {
public val list: List<Listable>
get() = snapshot().items
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// ...
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
viewBinder.bind(list[position], holder)
}
// ...
}
When scrolling to the bottom of the RecyclerView, the next page is not loaded at all (No call to PagingSource.load())
What can go wrong?

How does the PagingSource know when to load more data? What magic is behind it?
Well, it turns out that the Adapter is responsible for this.
How can the Adapter be aware of loaded data?
You have to call getItem() as documented:
/**
* Returns the presented item at the specified position, notifying Paging of the item access to
* trigger any loads necessary to fulfill [prefetchDistance][PagingConfig.prefetchDistance].
*
* #param position Index of the presented item to return, including placeholders.
* #return The presented item at [position], `null` if it is a placeholder
*/
protected fun getItem(#IntRange(from = 0) position: Int) = differ.getItem(position)
As we were accessing the entire list via snapshot:
public val list: List<Listable>
get() = snapshot().items
the Adapter couldn't know what item was being loaded, and couldn't trigger the next page loading.
So the fix is:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
getItem(position)?.let {
viewBinder.bind(list[position], holder)
}
}
With this, everything is working properly!

Related

How to pass widget and data from one file to another file in different class?

Beginner-level questions. I’m creating a counter application (first application from The 7 Tasks). I created this application in one file and it is working fine. Following is the code.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.uname.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Gtk.Entry();
entry.text = val.to_string();
entry.editable = false;
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Gtk.Button.with_label("Counter");
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Now, I'm trying to divide the above code into separate files such as Application.vala, Entry.vala, and Button.vala. Here is the code for these files.
Code for Application.vala.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.chauhankiran.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Code for Entry.vala.
public class Entry : Gtk.Entry {
public Entry(int val) {
text = val.to_string();
}
construct {
editable = false;
}
}
Code for Button.vala.
public class Button : Gtk.Button {
// Is it correct?
public int val;
public Button(int val) {
this.val = val;
}
construct {
label = "Counter";
}
// How to write this within Button.vala from Application.vala?
// How to get entry widget in this class?
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
}
Now, I have the following questions.
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
In Button.vala I need val as well access to entry so that I can get access to entry in Button.vala? Or this is incorrect way to do the code? If that is that is the case, please suggest correct way. Currently separate files code throws error as I don’t know how to connect and pass the information correctly.
The 7 Tasks are a good exercise to learn, and you seem to be off to a great start!
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
The preferred way to handle construction in Vala is using GObject-style construction: https://wiki.gnome.org/Projects/Vala/Tutorial#GObject-Style_Construction
What you're doing would technically work, but using the GObject-style you'd end up with something like the following:
public class Entry : Gtk.Entry {
public Entry(int val) {
Object (
text: val.to_string(),
editable: false
);
}
}
The important things to note here are:
This only works for properties declared as construct or set
The syntax is slightly different than what you were doing (property: value vs. member = value)
And one other little optimization:
editable is also a property that can be set in the constructor, so no need for a construct block here!
Notice that you can make some similar changes to your Button class as well!
In Button.vala I need val as well access to entry so that I can get
access to entry in Button.vala? Or this is incorrect way to do the
code? If that is that is the case, please suggest correct way.
Currently separate files code throws error as I don’t know how to
connect and pass the information correctly.
Currently your Button code has references to the Entry in it. I would advise against this from an object-oriented programming (OOP) perspective (you may hear people toss around terms like Single-Responsibility or Separation of Concerns, etc.), but the gist is that the button should just focus on being what it is: a button. A button doesn't need to be aware of the presence of the entry, and the entry doesn't need to exist in order to have a button. Your logic of handling what happens between widgets when the button is clicked should happen at a level above. In this case, that would be in your Application class where you've created both of those widgets:
...
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
// Update the entry
});
...
Let's take a quick look at your button:
public class Button : Gtk.Button {
// Is it correct?
public int val;
...
It's not wrong, and there are many ways to do what you're doing. So let's roll with it as is.
At this point you've got your button which updates an internal int value every time it's clicked and you now need to update the entry to display the new value. Currently you have:
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
Since this is now being handled in the Application class, you'll need to change those references to this, since you want to reference and update val from the button, not the variable in Application that you're using for the initial value:
button1.clicked.connect (() => {
button1.val = button1.val + 1;
entry.text = button1.val.to_string();
});
Hopefully that helped a bit, you were 99% of the way there with splitting it into multiple classes! Keep it up, and good luck on the next tasks!

How to dynamically change the children in a view in a vsc extension

The code examples for TreeDataProviders on github sometimes show a refresh method but I'm not sure how to use it. Do I just call refresh() and pass in the data to be used by getChildren() and just update the class property that getChildren uses?
The refresh() function usually triggers the onDidChangeTreeData even, to which the base class (TreeDataProvider) is listening. It will then call getChildren again to re-fill the tree. See also the description of that event:
/**
* An optional event to signal that an element or root has changed.
* This will trigger the view to update the changed element/root and its children recursively (if shown).
* To signal that root has changed, do not pass any argument or pass `undefined` or `null`.
*/
onDidChangeTreeData?: Event<T | undefined | null>;
You can design the refresh function as you like, e.g. passing in new data, or you keep a reference to an applicationm data provider in the tree provider (e.g. passed to it in the c-tor). Up to you.
There are a few methods on the TreeDataProvider that are important to know about...
getChildren - method to obtain the data for items that will be displayed in the tree. This should return an array -- don't worry about turning the data into a TreeItem yet, this is just raw data.
getTreeItem - called on each item in the array returned by getChildren. Should return a single TreeItem using the data provided..
onDidChangeTreeData - a vscode.Event that, when changed, will trigger getChildren to be re-evaluated. This can be done by creating a vscode.EventEmitter(let's call it eventEmitter) and calling the fire method on the eventEmitter. This will cause the eventEmitter.event to be updated/triggered.
Here is an example of how to set up a TreeDataProvider, that I hope will help illustrate how to create the EventEmitter, set the TreeDataProvider's onDidChangeTreeData property to the event of the EventEmitter, and create/export a refresh method that can be called to trigger an update of the data.
TreeDataProvider
import * as vscode from 'vscode';
export class AccountsProvider implements vscode.TreeDataProvider<Account> {
private accounts: Array<Account>;
constructor() {
this.accounts = getAccounts();
}
_onDidChangeTreeData: vscode.EventEmitter<undefined> =
new vscode.EventEmitter<undefined>();
onDidChangeTreeData: vscode.Event<undefined> =
this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(a: Account): vscode.TreeItem {
return new AccountTreeItem(
a.name,
a.id,
a,
vscode.TreeItemCollapsibleState.None
);
}
getChildren(): Thenable<Account[] | undefined> {
this.accounts = getAccounts();
if (this.accounts) {
return Promise.resolve(this.accounts);
}
return Promise.resolve([]);
}
}
TreeItem
export class AccountTreeItem extends vscode.TreeItem {
constructor(
public readonly name: string,
public readonly id: string,
public readonly accountData: Account,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly iconPath: string = new vscode.ThemeIcon('account'),
public readonly contextValue: string = 'accountTreeItem'
) {
super(name, collapsibleState);
this.tooltip = `Active Account: ${accountData.name}`;
}
}
Importing and triggering refresh method
import * as vscode from 'vscode';
import { AccountsProvider } from 'accountsProvider'; // The TreeDataProvider
const accountProvider = new AccountsProvider();
context.subscriptions.push(
vscode.commands.registerCommand('ext.accounts.refresh', () => {
accountProvider.refresh();
})
);
vscode.commands.executeCommand('ext.accounts.refresh');

How to signal/notify super-controller of change in sub-controller?

In JavaFX, how do you model the following:
I show a List of Customers in a Scene. On the left side there is a table on the right side (contentPane) the currently select customer's details are shown.
(Relevant part of) Main-Controller:
#jfxf.FXML
protected def newButtonPressed(): Unit =
{
contentPane.getChildren.clear
contentPane.getChildren.add(FXMLLoader.load(GUILoader.load("customers/form.fxml")))
}
There is a Button to add a new Customer. Upon clicking this button instead of opening a Popup, I replace the "details"-part of the scene and add a form there.
Now for this form (designed - like the rest of the GUI - in the SceneBuilder and saved as .fxml) I use another controller.
class Form extends Main with jfxf.Initializable
{
#jfxf.FXML
private var foreNameTextField: jfxsc.TextField = _
#jfxf.FXML
private var lastNameTextField: jfxsc.TextField = _
#jfxf.FXML
private var ageTextField: jfxsc.TextField = _
override def initialize(url: URL, resourceBundle: ResourceBundle): Unit =
{
}
#jfxf.FXML
protected def ok(): Unit =
{
// TODO validation
val newPerson = new Person(-1, foreNameTextField.getText, lastNameTextField.getText, ageTextField.getText.toInt)
// Save to DB
// Close whole form
}
}
When I'm done with filling in a new customer's detail I click on another button (that calls ok()) and save it to a database.
What I want to do now is close the form and replace it with the detail-form.
Something like calling a protected method in the main-controller like:
protected def clearDetails(): Unit =
{
contentPane.getChildren.clear
contentPane.getChildren.add(savedOldDetails)
}
won't work of course. (Will throw a runtime-exception because there is no contentpane in the sub/form-controller (even if I make it protected)
In Qt (C++) I'd use signals/slots and connect them accordingly.
Seems like in JavaFX there is nothing the like. How am I supposed to share such information?
Do I need to create a "super-controller" for the contentPane?
(I don't know Scala, so I'll write this in Java, hope that is ok).
You can define an observable property in the Form controller, and observe it when you load the form from the main controller:
public class Form implements Initializable {
private final ObjectProperty<Person> person = new SimpleObjectProperty<>(null);
public ObjectProperty<Person> personProperty() {
return person ;
}
public final Person getPerson() {
return personProperty().get();
}
public final void setPerson(Person person) {
personProperty().set(person);
}
// ... code you had before...
#FXML
protected void ok() {
Person person = new Person(-1, foreNameTextField.getText(), lastNameTextField.getText(), ageTextField.getText());
// save to DB...
personProperty().set(person);
}
}
Now in your main controller, load the form as follows:
#FXML
protected void newButtonPressed() {
contentPane.getChildren().clear();
FXMLLoader loader = new FXMLLoader(getClass().getResource("customers/form.fxml"));
Parent form = loader.load();
Form formController = loader.getController();
formController.personProperty().addListener((obs, oldPerson, newPerson) {
if (newPerson != null) {
// clear form, etc, e.g.
clearDetails();
}
});
contentPane.getChildren().add(form);
}

create new instance of class that is managed by gin

Im using GWTP, working on their tab panel example. My issue is i need to demonstrate taking a search term and adding a new tab to the tab panel with the search results. So if i search 5 times, i have 5 tabs. easy enough, so i thought.
Gin is used extensively in GWTP. So my method to add a new tab, which should be something as simple as
tabPanel.addTab(new SearchDataGridView(), NameTokens.searchPage + 1);
gets confusing because of the constructor for the SearchDataGridView class
#Inject
SearchDataGridView(Binder uiBinder) {
employeeDataProvider = new ListDataProvider<Employee>();
initSearchGrid();
initWidget(uiBinder.createAndBindUi(this));
}
yea, i know im not passing the search term yet, im still trying to get the tab to open.
My gin config is this
bindPresenter(
SearchDataGridPresenter.class,
SearchDataGridPresenter.MyView.class,
SearchDataGridView.class,
SearchDataGridPresenter.MyProxy.class);
The gwtp gin config
#Override
protected void configure() {
// RestDispatchAsyncModule.Builder dispatchBuilder = new RestDispatchAsyncModule.Builder();
// install(dispatchBuilder.build());
// install(new RestDispatchAsyncModule.Builder().build());
install(new DefaultModule(DefaultPlaceManager.class));
install(new ApplicationModule());
bind(CurrentUser.class).in(Singleton.class);
bind(IsAdminGatekeeper.class).in(Singleton.class);
// DefaultPlaceManager Constants
bindConstant().annotatedWith(DefaultPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(ErrorPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(UnauthorizedPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(Names.named("rest")).to("http://localhost/services");
// Google Analytics
// bindConstant().annotatedWith(GaAccount.class).to("UA-8319339-6");
// Load and inject CSS resources
bind(ResourceLoader.class).asEagerSingleton();
}
How do i pull this off?
thanks
Using comments below, sorta got it working. The problem is i can't get the contents of the tab to display. I added debugging code to the setInSlot method of my SearchContainer class and realized that whenever i click the search tab, it fires this setInSlot method, but its fired with my default page presenter listed as content.
#Override
public void setInSlot(Object slot, IsWidget content) {
Window.alert("fired setInSlot: " + slot.toString());
Window.alert("fired setInSlot: " + content.toString());
if (slot == ApplicationPresenter.TYPE_SetTabContent) {
tabPanel.setPanelContent(content);
} else {
super.setInSlot(slot, content);
}
}
Thats the method im using to get my info. Its weird that the tab appears properly, the jsonRPC calls that are built into the view are executed properly, it just doesn't display.
My main container presenter has its view and proxy identified by this
public class ApplicationPresenter
extends
TabContainerPresenter<ApplicationPresenter.MyView, ApplicationPresenter.MyProxy> implements
CurrentUserChangedHandler, AsyncCallStartHandler, AsyncCallFailHandler, AsyncCallSucceedHandler,
ApplicationUiHandler {
/**
* {#link ApplicationPresenter}'s proxy.
*/
#ProxyStandard
public interface MyProxy extends Proxy<ApplicationPresenter> {
}
/**
* {#link ApplicationPresenter}'s view.
*/
public interface MyView extends TabView, HasUiHandlers<ApplicationUiHandler> {
void refreshTabs();
void setTopMessage(String string);
}
Could my issue be with my content type? Here is what i have defined for all my types
/**
* This will be the event sent to our "unknown" child presenters, in order for them to register their tabs.
*/
#RequestTabs
public static final Type<RequestTabsHandler> TYPE_RequestTabs = new Type<RequestTabsHandler>();
/**
* Fired by child proxie's when their tab content is changed.
*/
#ChangeTab
public static final Type<ChangeTabHandler> TYPE_ChangeTab = new Type<ChangeTabHandler>();
/**
* Use this in leaf presenters, inside their {#link #revealInParent} method.
*/
#ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetTabContent = new Type<RevealContentHandler<?>>();
The proper way to do this is to use a PresenterWidget and a Provider.
In your ClientModule you define this:
bindPresenterWidget(SearchDataGridPresenter.class, SearchDataGridPresenter.MyView.class, SearchDataGridView.class);
In your Presenter that adds the SearchDataGridView you inject a Provider:
#Inject
public SeachContainerPresenter(final Provider<SearchDataGridPresenter> seachDataGridProvider) {
}
For each search you call addToSlot(TYPE_SetSearchDataGridContent,seachDataGridProvider.get()) in your SearchContainerPresenter.
In the SearchContainerView you override the addToSlot() method:
#Override
public void addToSlot(Object slot,Widget content) {
if (slot == SeachContainerPresenter.TYPE_SetSearchDataGridContent) {
tabPanel.addTab(content, NameTokens.searchPage + 1);
}
else {
super.addToSlot(slot,content);
}
}
Look in binding everything together. I think that you have to add the AsyncProvider of your presenter to yuor Ginjector.
AsyncProvider<SearchDataGridPresenter> getSearchDataGridPresenter();
You can put a SearchDataGridView getSearchDataGridView() method in the GIN Injector and call it in order to obtain the SearchDataGridView instance.

GWT 2.1 Data Presentation Widgets without paging

I am trying to build a table with large dataset and would like to avoid paging. (I would like to do something similar to Yahoo Mail grid which retrieves data after the grid is drawn. I think initially the first 100 mails are retrieved and then mail is only retrieved after the user scrolls down)
The example of the data presentation widget I have seen include paging. Is it possible to do what I want?
edit: You could also call this an infinite scroll table
There are an exemple of this in the GWT Showcase
/**
* A scrolling pager that automatically increases the range every time the
* scroll bar reaches the bottom.
*/
public class ShowMorePagerPanel extends AbstractPager {
/**
* The default increment size.
*/
private static final int DEFAULT_INCREMENT = 20;
/**
* The increment size.
*/
private int incrementSize = DEFAULT_INCREMENT;
/**
* The last scroll position.
*/
private int lastScrollPos = 0;
/**
* The scrollable panel.
*/
private final ScrollPanel scrollable = new ScrollPanel();
/**
* Construct a new {#link ShowMorePagerPanel}.
*/
public ShowMorePagerPanel() {
initWidget(scrollable);
// Handle scroll events.
scrollable.addScrollHandler(new ScrollHandler() {
public void onScroll(ScrollEvent event) {
// If scrolling up, ignore the event.
int oldScrollPos = lastScrollPos;
lastScrollPos = scrollable.getScrollPosition();
if (oldScrollPos >= lastScrollPos) {
return;
}
HasRows display = getDisplay();
if (display == null) {
return;
}
int maxScrollTop = scrollable.getWidget().getOffsetHeight()
- scrollable.getOffsetHeight();
if (lastScrollPos >= maxScrollTop) {
// We are near the end, so increase the page size.
int newPageSize = Math.min(
display.getVisibleRange().getLength() + incrementSize,
display.getRowCount());
display.setVisibleRange(0, newPageSize);
}
}
});
}
/**
* Get the number of rows by which the range is increased when the scrollbar
* reaches the bottom.
*
* #return the increment size
*/
public int getIncrementSize() {
return incrementSize;
}
#Override
public void setDisplay(HasRows display) {
assert display instanceof Widget : "display must extend Widget";
scrollable.setWidget((Widget) display);
super.setDisplay(display);
}
/**
* Set the number of rows by which the range is increased when the scrollbar
* reaches the bottom.
*
* #param incrementSize the incremental number of rows
*/
public void setIncrementSize(int incrementSize) {
this.incrementSize = incrementSize;
}
#Override
protected void onRangeOrRowCountChanged() {
}
}
Dean already mentioned Ext GWT but I'd like to suggest SmartGWT's implementation as well.
Yes, it is possible. There used to be an example of that called DynaGrid here but that link is dead now. I haven't been able to find it anywhere else (note: that's not the same as the DynaGrid on SourceForge). You might be able to contact the author, Reinier Zwitserloot, to inquire about getting a copy of his DynaGrid. You could also search the GWT Group and if you don't find anything, post there asking if anyone else knows where to find it.
Ext Gwt (AKA GXT) has an implementation of a "Live Grid" that supports this functionality (see http://www.sencha.com/examples/explorer.html#livegrid). The code is GPL although you can buy a license to use this in commercial applications.