How does Android Paging Library know to load more data? - java

Iʼm fairly new to developing Android apps and Iʼm trying to do everything “the right way.” So right now, Iʼm implementing the new Android Paging Library into my project, where I need to load a list of articles from a network server.
I have an ArticlesRepository class that returns an ArticleList class containing instances of ArticleListItem that I would like to display in a RecyclerView. The list of articles is paginated already on the server, so the repository sends a request for the first page and returns an ArticleList with the page property set to 1 and the articles property containing a List<ArticleListItem> of articles on the requested page. I donʼt know how many articles can be on one page.
Now, I was able to implement a PageKeyedDataSource<Integer, ArticleListItem>, but it only fetches the first page:
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(1);
if (list != null) {
callback.onResult(list.articles, null, next(list));
}
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(previous(params.key));
if (list != null) {
callback.onResult(list.articles, previous(list));
}
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(next(params.key));
if (list != null) {
callback.onResult(list.articles, next(list));
}
}
The previous/next functions return an Integer with the previous/next page number or null if there isnʼt one.
In my ViewModel, I configure the PagedList like this:
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(1)
.setPageSize(1)
.setPrefetchDistance(1)
.build();
This way Iʼm able to load the first page, but when I scroll to the bottom of the RecyclerView (that is inside a NestedScrollView), nothing happens. Debugging shows that the PageKeyedDataSource.loadAfter method is not invoked.
Do I have to somehow tell the PagedList that the next page has to be loaded, or is it the RecyclerView/DataSource/GodKnowsWhatElseʼs job and Iʼm just doing something wrong? Thanks for any advice.

The paging library should know automatically when to load new items. The problem in your implementation is that the paged RecyclerView is inside a NestedScrollView and according to this issue the libary doesn't have built in support for that.
when you put recyclerview inside an infinite scrolling parent, it will
layout all of its children because the parent provides infinite
dimensions.
You'll need to create your own implementation of Nested Scroll View, there is actually one here in this gist that might be able to help you.
It is also suggested to add fillViewPort to this custom nested scroll view:
android:fillViewport="true" to scrollable container

Related

Google ad click is not opening the external web browser

I am trying to implement the ad in my app with Custom Native Ad Format - https://developers.google.com/ad-manager/mobile-ads-sdk/android/native/custom-formats#java_1
So, according to the documentation I am going with the approach described there and creating the ad
...
private void setListeners() {
...
imageView.setOnClickListener(v -> {
nativeCustomFormatAd.performClick("IMAGE");
});
...
}
private NativeCustomFormatAd nativeCustomFormatAd;
AdLoader adLoader = new AdLoader.Builder(context, "/6499/example/native")
.forCustomFormatAd("10063170",
new NativeCustomFormatAd.OnCustomFormatAdLoadedListener() {
#Override
public void onCustomFormatAdLoaded(NativeCustomFormatAd ad) {
// Show the custom format and record an impression.
nativeCustomFormatAd = ad;
Drawable drawable = vm.nativeCustomFormatAd.getImage("IMAGE").getDrawable();
imageView.setDrawable(drawable);
}
},
new NativeCustomFormatAd.OnCustomClickListener() {
#Override
public void onCustomClick(NativeCustomFormatAd ad, String s) {
// Handle the click action
}
})
.withAdListener( ... )
.withNativeAdOptions( ... )
.build();
#SuppressLint("VisibleForTests")
AdManagerAdRequest adManagerAdRequest = new AdManagerAdRequest.Builder().build();
adLoader.loadAd(adManagerAdRequest);
...
So, it looks pretty simple I try to make a request for the ad then I got (in a callback) NativeCustomFormatAd, save it as a class member, and along with it get drawable and set it to the imageView (to present it in the UI). Once a user clicks on the imageView I get an event in the click listener and invoke nativeCustomFormatAd.performClick("IMAGE");.
The problem is that I expect that once I transfer the ad click to the SDK (by nativeCustomFormatAd.performClick("IMAGE");) SDK is supposed to open the external browser, but instead nothing happens.
P.S. I am sure that nativeCustomFormatAd.performClick("IMAGE"); getting invoked and also I see that SDK gets the click as I got a callback event here:
...
new NativeCustomFormatAd.OnCustomClickListener() {
#Override
public void onCustomClick(NativeCustomFormatAd ad, String s) {
// Handle the click action
}
})
...
What am I missing here?
According to the docs you linked:
When a click is performed on a custom format ad, there are three possible responses from the SDK, attempted in this order:
Invoke the OnCustomClickListener from AdLoader, if one was provided.
For each of the ad's deep link URLs, attempt to locate a content resolver and start the first one that resolves.
Open a browser and navigate to the ad's traditional Destination URL.
Also:
If you pass a listener object in, the SDK instead invokes its onCustomClick method and takes no further action.
Therefore, it seems you have to pass a null OnCustomClickListener.

Merging Local Data With Firebase Database and showing it in recyclerview

I have a chatting application which is made using firebase. It works fine. But some users send slangs or stuffs like that. I use Firebase Recyclerview. My connection is direct to database. So I can't verify if the message is good or bad before data gets added.
What I want to do is, I want to send that data to some other location. Then after verification using cloud functions, I want to add it to original chat room database.
Till that step I have no problems. But when user sends a message. I dont want the user to wait until data gets updates. Cloud Functions takes 2-3 seconds for me. I want to add that data locally with a sending status and then update it when it gets updated on the chat room database.
So What's the best way to do that..
There is nothing wrong with my code. It's just basic firebase adapter..
Query query = FirebaseDatabase.getInstance()
.getReference()
.child("rooms").child("Off-Topic").child("chat")
.limitToLast(500);
firebaseOptions = new FirebaseRecyclerOptions.Builder<ChatData>()
.setQuery(query, ChatData.class)
.build();
chatAdapter = new FirebaseRecyclerAdapter<ChatData, ChatHolder>(firebaseOptions) {
#Override
protected void onBindViewHolder(ChatHolder holder, int position, ChatData model) {
}
#Override
public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.sender_text_message, parent, false);
return new ChatHolder(v);
}
};
The simplest way for you to solve this would be to add a boolean variable to your ChatData class. This variable (let's call it isGood) would be false when the message is sent and would become true when it has been updated by your Cloud Function.
So your Adapter's onBindViewHolder would be:
#Override
protected void onBindViewHolder(ChatHolder holder, int position, ChatData model) {
if(model.isGood())
holder.textView.setText(model.getMessage()); //edit this line as needed
else
holder.textView.setText("sending..."); //replace textView with the name of your actual TextView
}

Handling positions in FirebaseRecyclerAdapter

I have fiddled with the FirebaseRecyclerAdapter for quite some time now. It's really a great tool to populate a custom list/recycler view very fast with all of its features. However, one thing that I would like to ask is how to handle positions of items inside the adapter itself.
So for example, I want to mimic this small feature that WhatsApp has in their chats.
So, in a group chat setting, if a person sends more than one consecutive message in a row, the display name of that particular person will be invisible.
The logic behind it according to my understanding: if the person who sends the message is the same for (position - 1), then I will just make the EditText invisible for (position). This is, of course, to prevent a very long stream of text with minimum amounts of repetitive information.
Let's say the JSON tree from Firebase database is as follows.
{
"messages" : {
"pushed_id_1" : {
"message_sender" : "AppleJuice",
"message_text" : "Are you free?"
},
"pushed_id_2" : {
"message_sender" : "AppleJuice",
"message_text" : "On Saturday I mean..."
}
}
}
The FirebaseRecyclerAdapter would look like this.
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter = new FirebaseRecyclerAdapter<Message, MessageViewHolder>(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
The furthest I have gone is to use getItemCount() method in the FirebaseRecyclerAdapter, but I am still unable to achieve the feature that mimics that of Whatsapp's that I was talking about previously.
Is there a method that can achieve this? Or am I missing something very important in this example?
String lastSender=null; //or some random string
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter =
new FirebaseRecyclerAdapter<Message, MessageViewHolder>
(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
if (model.getMessage_sender().equals(lastSender){ //check if the current sender is same as the last sender
viewHolder.messageText.setText(model.getMessage_text()); //setting only message text
viewHolder.messageSender.setVisibility(View.GONE); //if required
}else{
lastSender=model.getMessage_sender();//updating the lastSender value
viewHolder.messageSender.setVisibility(View.VISIBLE); //if required
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
}
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
As discussed in comments:
Let's suppose I received message and stored sender's name in constant String that should be static constant in some class i.e. AppConstants so that It can be accessed everywhere therefore after that:
in populateViewHolder or in your message receiver do something like this:
if (TextUtils.isEqual(storedSender,model.getMessage_sender())){
viewHolder.messageSender.setVisiblity(View.GONE)
}
else{
// do your normal flow
viewHolder.messageSender.setVisiblity(View.VISIBLE);
storedSender = model.getMessage_sender();
}
In this way automatically the last message's sender's name will be updated , this is exactly what you were trying to achieve by adapter position!

GXT pagination without RPC proxy

Any idea how I can implement proper pagination without a RPCProxy in GXT? I am currently setting the loader like this:
final PagingLoader<PagingLoadResult<ModelData>> loader = new BasePagingLoader<PagingLoadResult<ModelData>>(null);
store = new ListStore<T>(loader);
And then pass the store to the grid constructor.
Now, if I set null instead of a proxy in the constructor, my pagingToolbar just freezes and goes disabled and displays what appears to be a loading circle.
I read the ideas in here http://www.sencha.com/forum/showthread.php?61780-Pagination-without-RPC, but can anyone be a bit more explicit on how to achieve this?
I am creating the grid and then adding the data and I'm working with RequestFactory so no RCPProxy needed.
You can just implement the DataProxy interface and use your custom data-obtaining method:
BasePagingLoader<PagingLoadResult<ModelData>> loader = new BasePagingLoader<PagingLoadResult<ModelData>>(new DataProxy<PagingLoadResult<ModelData>>() {
#Override
public void load(DataReader<PagingLoadResult<ModelData>> reader,
Object loadConfigAsObject, AsyncCallback<PagingLoadResult<ModelData>> callback) {
BasePagingLoadConfig loadConfig = (BasePagingLoadConfig) loadConfigAsObject;
// Get the results for the requested page...
BasePagingLoadResult<ModelData> pagingLoadResult = new BasePagingLoadResult<ModelData>(...);
callback.onSuccess(pagingLoadResult);
}
});

Passing parameters to the view

In my RCP application, I have a View with a TreeViewer for Navigation on the left side and a Folder for my views on the right side. The Perspective looks like this:
public void createInitialLayout(IPageLayout layout) {
layout.setEditorAreaVisible(false);
layout.setFixed(false);
layout.addStandaloneView(NavigationView.ID, false, IPageLayout.LEFT, 0.7f, layout.getEditorArea());
right = layout.createFolder("right", IPageLayout.RIGHT, 0.3f, "com.my.app.views.browser.navigation");
layout.getViewLayout(WallpaperView.Id).setCloseable(false);//dummy view to keep the folder from closing
layout.getViewLayout(WallpaperView.Id).setMoveable(false);
right.addView(WallpaperView.Id);
//add some placeholders for the potential views
right.addPlaceholder(DefaultAdminView.ID+":*");
}
I would like to open different views, based on what the user selects in the navigation tree. Figured that wouldn't be to hard. My Navigation Tree view:
tree = new TreeViewer(composite);
tree.setContentProvider(new BrowserNavigationTreeContentProvider());
tree.setLabelProvider(new BrowserNavigationTreeLabelProvider());
tree.setInput(UserProfileAdvisor.getProject());
//register Mouselistener for doubleclick events
tree.addDoubleClickListener(new IDoubleClickListener(){
#Override
public void doubleClick(DoubleClickEvent event) {
TreeSelection ts = (TreeSelection) event.getSelection();
Object selectedItem = ts.getFirstElement();
String viewId = DefaultAdminView.ID;
//set viewId depending on the selectedItem.class
try {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(viewId, String.valueOf(++viewCounter), IWorkbenchPage.VIEW_ACTIVATE);
} catch (PartInitException e) {
ILogHelper.error("The view for the selected object could not be opened", e);
}
}
});
This seems to work fine. There's just one tiny problem:
I need to pass the object (let's say the selectedItem) to my view somehow, in order to let the user interact with its content. How do I do that?
I've seen some examples where some of my colleagues wrote an own View which they placed on the right side. Then they added a CTabFolder, instantiated the views and added them manually. Is there a smarter solution?
Create a new interface, giving it a method like accept( Object parameter ) and make your views implement it.
Then, when you do PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(viewId, String.valueOf(++viewCounter), IWorkbenchPage.VIEW_ACTIVATE) the method showView returns an IViewPart. Cast this return to your interface and call the accept method.
Use the SelectionService for that, please refer to Eclipse RCP let two views communicate
Implement the SelectionProvider in your "Navigation" and in the opened view you can ask for the selected object from the selection-service (see article)
HTH Tom

Categories

Resources