How to fetch data from api and show when user scroll - android

I have an API which gives 10 per page result at once
{ item1,
item2,
item3,
.
.
item10
}
I have RecycleView in which I want to display the result.
public class RecycleAdapter extends RecyclerView.Adapter<RecycleAdapter.ViewHolder> {
private Entry[] entry;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
TextView labelText;
TextView descText;
ImageView itemImage;
public ViewHolder(View v) {
super(v);
labelText = (TextView) v.findViewById(R.id.item_label);
descText = (TextView) v.findViewById(R.id.item_text);
itemImage = (ImageView) v.findViewById(R.id.item_image);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public RecycleAdapter(Entry[] en) {
entry = en;
}
// Create new views (invoked by the layout manager)
#Override
public RecycleAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_items, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(view);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.labelText.setText(entry[position].title);
holder.itemImage.setImageResource(R.drawable.img);
holder.descText.setText("Random Text to Recycle");
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return entry.length;
}
}
Entry object contains the Array if Object containing data (basically String). instead of this I would like to pass and integer array
denoting the page of API and than I want to list all 10 item of that page
It seems to me that we can get only one item at a time in bind function or other way is to insert all item in Entry at beginning and than fetch them (which I want to avoid).

You can use addOnScrollListener behavior of recyclerView to monitor the scroll, and use layoutManager.findLastCompletelyVisibleItemPosition() to know who's the last item.When you know it you can populate the next 10 items you've fetched before and for a better user experience you can use adapter.notifyItemRangeInserted(int startPosition, int itemCount) to notify the list from below that postition so that if for example you had 10 items in the recyclerView before the fetch, so after the fetch you'll notify from last item (via layoutManager method above) to itemCount added (in your case 10) then the first 10 displayed will still be shown and the next 10 will be added to the bottom of the recycler, and the user will be able to 'continue' scrolling.
Hope it helps, good luck

Lets say your API gives you X items at a time. When getView is called with position lastFetchedItem-Y (where Y is a small number used to give some buffer for network delay) you request the next X items. When you receive them, you update the model backing your adapter and call notifyDataSetChanged. This will give you a smooth scroll. Adjust X and Y for network efficiency (slower networks or slower APIs, bigger Y).

Use Endless Scrolling with AdapterViews and RecyclerView
This tutorial will help you do paging functionality.

Related

Can recyclerView have the last row with custom item that does not same with the other items?

I have a layout with order list and last row is the total price of those order. And I don't have an idea that how can I do those list items and last row with my desire custom item, together in a recycler view. Do I make some logic in onBindViewHolder? Or, does it have another way, one of the RecyclerView methods?
You can do this by using below code on your RecyclerAdapter class
#Override
public int getItemViewType(int position) {
if(position==(getItemCount()-1))return 1;
else return 2;
}
In onCreateViewHolder inflate your last layout according to your viewType.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your first item layout & return that viewHolder
} else {
// inflate your second item layout & return that viewHolder
}
}
Yes, you can do that using ItemTypes,RecyclerView can render different type of child views, Please refer to this example : RecyclerView With Multiple Item Types
You can use simple relative or linear layout to display Total price of those orders.
And this layout will place below the recyclerview / Listview in XMl.
if you use above solution then you don't need to use last item of list for displaying total price of those orders.

How to insert list item into listview without modifying the underlying data set?

I have a ListView that is populated by an ArrayAdapter, and I need to insert an advertisement after every Nth legitimate list item. The easiest way I can think of to do this would be to modify the array that defines the adapter's data set directly (or indirectly, via ArrayAdapter::add/insert) as follows:
/**
* Injects ad entries into the station list after every N legit entries
*
* #param adapter the arrayadapter that will generate the views representing our legit and ad list items
*/
private void injectAdEntries(MyArrayAdapter adapter){
int legitItemCount = adapter.getCount();
int adStride = 4;
if(legitItemCount >= adStride) {
//adstride-1 to account for the 0 index
for (int legitItemIndex = adStride-1; legitItemIndex < legitItemCount; legitItemIndex += adStride) {
//create clone of last legit entry, to use as context
// data for the ad, and mark it 'illegitimate' (ad)
LegitObject totallyLegit = new LegitObject(adapter.getItem(legitItemIndex));
totallyLegit.setLegit(false);
adapter.insert(totallyLegit,legitItemIndex+1);
}
}
}
After the injection, MyArrayAdapter's getView override can detect the false legit value and handle advertisement entries differently from legitimate entries.
The trouble is that I don't want to pollute the legitimate data array. Ideally, I would insert these advertisement list items without modifying the underlying data set in any way. Is there a way to make an adapter produce views that aren't reflected in its data set?
Edit
I also need all the items in the legitimate data set to be displayed in the ListView; the ad list items need to appear 'in addition to' rather than 'instead of' legitimate list items.
I will assume that you are using a recyclerView as a list. So, to have heterogenous layouts (i.e.: YOUR ACTUAL ITEM LAYOUT + AD LAYOUT) you should follow these steps:
Override getItemView like
#Override
public int getItemViewType(int position) {
// insert an Ad every multiple of 5
if (position % 5 == 0) {
return AD_POSITION;
}
return NORMAL_POSITION;
}
Next, you need to override the onCreateViewHolder method to tell the RecyclerView.Adapter about which RecyclerView.ViewHolder object to create based on the viewType returned.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
RecyclerView.ViewHolder viewHolder;
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
switch (viewType) {
case AD_POSITION:
View v1 = inflater.inflate(R.layout.list_item_ad, viewGroup, false);
viewHolder = new AdViewHolder(v1);
break;
default:
// NORMAL_POSITION
View v = inflater.inflate(R.layout.list_item, viewGroup, false);
viewHolder = new NormalViewHolder(v);
break;
}
return viewHolder;
}
For more details you can check this link out.

How to make RecyclerView stops recycling defined positions?

My problem is: I have a video streaming happening on one of the views inside the RecyclerView.
When the user scrolls, the view gets recycled and other cameras starts their own streaming on that recycled viewholder. This is bad for user interface since the streaming process takes some seconds to start.
How can I say to the RecyclerView: "Hey Recycler, please, do not recycle that exact position x and give that position ALWAYS the same viewholder you gave it the first time, instead of random one"?
Please someone help me =(
In your getItemViewType(int position) method of adapter, assign unique values for each video, so it will always return same ViewHolder for same video as you wish.
return unique positive number as type for each video type (here i used the adapter position as unique key)
return negative numbers for any non-video items. (nothing special here, just to avoid conflicts with video items, we use negative numbers for non-video items)
I hope you get the idea. cheers :)
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
if(dataList.get(position).isVideo()){
return position;
}else{
return -1;//indicates general type, if you have more types other than video, you can use -1,-2,-3 and so on.
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case -1: View view1 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.general_item, parent, false);
return new GeneralViewHolder(view1);
default:View view2 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.video_item, parent, false);
return new VideoViewHolder(view2);
}
}
Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.
From docs of ViewHolder#setIsRecyclable(boolean):
Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until setIsRecyclable() is later set to true.
This will cause only one ViewHolder to be created.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
...
#Override
public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder) {
holder.setIsRecyclable(false);
}
super.onViewAttachedToWindow(holder);
}
#Override
public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder){
holder.setIsRecyclable(true);
}
super.onViewDetachedFromWindow(holder);
}
...
}
RecyclerView uses one view multiple times, when it contains the list which is not displaying on the screen at a time(means a list contain large amount of items which is not displaying on screen at same time you need to scroll up and down). When user scroll the list the offscreen items are reused to display the remaining list items which is called recycling.
To Stop recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(false);
This statement stop the recycling the views.
To Start recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(true);
I hope this will solve your problem.
Thanks
Your problem comes from the viewholder itself. Viewholders keep reference to views, while the adapter don't. The adapter keeps the data collection only. So, add a field to the viewholder to keep a reference of the data element you used to populate the view in the viewholder. In other words:
public class SomeViewHolder extends RecyclerView.ViewHolder{
private View view;
private Data data;
public SomeViewHolder(View itemView) {
super(itemView);
view = itemView;
}
public void bindData(Data data){
view.setData(data);
this.data = data;
}
public void setData(Data data){
this.data = data;
}
public Data getData(){
return data;
}
public View getView(){
return view;
}
}
Now, the viewholder know which element of the adapter is using. Therefore, when overriding the binding method in the adapter, you can check if the holder has already bonded with some data, and, if the data contains video, you can avoid the binding and forcefully set an already loaded view.
#Override
public void onBindViewHolder(SomeViewHolder holder, int position) {
//videoViewData is a data field you have to put into the adapter.
//videoView is a view field you have to put into the adapter.
if(adapterData.get(position).equals(videoViewData)){
holder.setView(videoView);
holder.setData(adapterData.get(position));
}else{
holder.bindData(adapterData.get(position));
if(adapterData.get(position).isVideo()){
videoViewData = adapterData.get(position);
videoView = holder.getView();
}
}
}
Finally, you'll have to override the onViewRecycled method in the adapter, so, when a view containing a video gets recycled, you can get the view and put it somewhere else.
public void onViewRecycled(SomeViewHolder holder){
if(holder.getData().isVideo()){
videoViewData = holder.getData().
videoView = holder.getView();
videoView.pauseVideo();
}
}
keep in mind, this can cause some serious leaks if you don't manage the stored view. Also, you have to define methods for telling when your data is video, and a properly defined equals method.
Best way to handle item not to recycle in recyclerview this answer will resolve your problem.
Not to recycle item
Try using this for that particular position:
holder.setIsRecyclable(false);
Hope this may help.
If You are using query, you can use
query.limit(//no of items you want to show in your RecyclerView)
give it a try.
or Plese post your QueryCode

How to design Gridview like guardian app

I am trying to make layout like guardian app. I know what is gridview and how to design it and inflate it with data etc.
what i want to design?
This layout have items with images and not with and there is also lazy loading going on in it.
What are the problem i am facing?
1-Confused which viewi should i go with. GridView,ListView or
RecyclerView.
2-if i go with GridView then how to have different item layouts for
some items.
What i have tried?
I have tried using linear layout as seperate xml and then i add that xml to root layout on run time. it works somewhat but problem rise when i need to add clicklistener to show relevent post since there would be more than 100+ post data.
It would be a lot of help if somebody guide me in right direction. Thanks!
EDIT. After going through the answer here. I used this approach. I used to xml. Then i change the layout with getViewType in adapter but that doesn't give such results. I am still looking for more convincing solution.
Here is the code that i have tired.
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
MainDTO mainDTO;
public RecyclerAdapter(MainDTO mainDTO){
this.mainDTO=mainDTO;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
ViewHolder viewHolder;
switch (viewType){
case 0:
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.header,parent,false);
viewHolder=new ViewHolder(view,viewType);
return viewHolder;
default:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.box,parent,false);
viewHolder=new ViewHolder(view,viewType);
return viewHolder;
}
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
ImageLoader imageLoader = ImageLoader.getInstance();
if(position == 0){
imageLoader.displayImage(mainDTO.getPosts().get(position).getThumbnail_images().getFull().getUrl(),holder.thumbnail);
holder.title.setText(mainDTO.getPosts().get(position).getTitle());
}
else if (position > 0 ){
if(mainDTO.getPosts().get(position).getThumbnail_images()!=null)
imageLoader.displayImage(mainDTO.getPosts().get(position).getThumbnail_images().getFull().getUrl(),holder.thumbnail);
holder.title.setText(mainDTO.getPosts().get(position).getTitle());
}
}
#Override
public int getItemCount() {
return mainDTO.getPosts().size();
}
#Override
public int getItemViewType(int position) {
int viewType = 1; //Default is 1
if (position == 0) viewType = 0; //if zero, it will be a header view
return viewType;
}
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView title;
public ImageView thumbnail;
public ViewHolder(View itemView,int viewType) {
super(itemView);
if(viewType == 0){
title = (TextView)itemView.findViewById(R.id.tv_title);
thumbnail = (ImageView)itemView.findViewById(R.id.iv_thumbnail);
}else if(viewType == 1){
title = (TextView)itemView.findViewById(R.id.tv_title_2);
thumbnail = (ImageView)itemView.findViewById(R.id.iv_thumbnail_2);
}
}
}
}
You will need to use StaggeredGridLayoutManager with RecyclerView to achieve what is being done in the guardian app. See this link StaggeredGridLayoutManager Tutorial
Edit 1
I have written a small sample application which can demonstrate what guardian application is achieving. Here is the Github link. I will explain it along the way with each step:
I used a StaggerdGridLayoutManager since in guardian app you are referring to have occupied different cell heights. This layout enables us to have items with different height.
For every different view type we have to create different view holders. For instance I have created 3 different view holders for every different item type in the sample application.
Override getItemViewType to let the recyclerview adapter know which view to inflate.
For instance of sample, I stored my data objects in an List of type Object to store heterogeneous objects and checked every item if its an instance of a particular class. I created 3 different types:
#Override
public int getItemViewType(int position) {
// we check here which item type to return based on object type
if (items.get(position) instanceof ImageModel)
return ITEM_TYPE_IMAGE;
if (items.get(position) instanceof TextViewModel)
return ITEM_TYPE_TEXT;
if (items.get(position) instanceof ButtonModel)
return ITEM_TYPE_BUTTON;
return -1;
}
Get itemViewType for the current view holder in OnCreateViewHolder in order to determine which layout to inflate.
For the instance of sample:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case ITEM_TYPE_IMAGE:
View image = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_image, parent, false);
return new ImageViewHolder(image);
case ITEM_TYPE_BUTTON:
View button = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_button, parent, false);
return new ButtonViewHolder(button);
case ITEM_TYPE_TEXT:
View text = ((LayoutInflater)BaseApplication.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.item_text, parent, false);
return new TextViewHolder(text);
}
return null;
}
Make specific type of items cover full row span.
Since some posts types are occupying full span in guardian application, we can use below code in OnBindViewHolder method to make any item expand to full span of layout.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_IMAGE){
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setFullSpan(true);
}
}
This makes the item cover all the span of layout like the biggest post in guardian application.
By following above steps, you can create a similar layout like this (image from sample github application):
Here there are 3 different item types: above two items are Buttons, middle one is ImageView and bottom are TextView.
You can use recycler view with GridLayoutManager. And In your adapter make different layout type as per your requirements.
GridLayoutManager manager = new GridLayoutManager(getActivity(), 6);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
// return your span size as per your layout type.
return 6;
}
}
});
Go to this for more info.
Edit:
Follow my github demo
There is three different kinds of views, one large grid, two small grids and 3-4 list view items. It is hard to use only one kind of view to complete such a task.
I suggest you to create some custom views to handle the grids (large and small), and a list view to handle the list item. After that, you can reuse the custom views for the grids and the list view's custom adapter in other sessions.
If you really want to use one Grid view to handle different views, then create a generic view that has all the functions and disable/enable the functions when you needed. However, this is much more complicated.
You can use RecyclerView because it gives you method to define different item types. but still to create such view you have to do so much code on the basis of its layout.
You have to override getItemViewType method and try to find which view type will be next to display. example code
#Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
Hope this will help
You require: Asymmetric Gridview
https://github.com/felipecsl/AsymmetricGridView
Above link will help.
You can define a common onClickListener
I will explain You How to do instead of writing the whole code we will use Recylerview for it
Recylervew which have four Items (Item1,Item2,Item3,Item4)
Item1 : it will contains the View1
Item2 : It will contains the VIew2
Item3 : It will contains the VIew3
Item4 : It will contains the VIew4
View1 : It will contains one Layout for text and other layout for comment and day section
View2 : It will contain the Image View
View3 : It will contains the tablelayout with one row and two columns
View4 : It will contains the linearlayout
TO achieve above layout design, you need to use recyclerView with StaggaredGridLayoutManager.
You have to use RecyclerView with StaggeredGridLayoutManager.

Android - Custom dynamically-generated compound View recreated in RecyclerView, causing poor performance

I'm using a custom CompoundView which extends LinearLayout to display items of a RecyclerView. Each item displays an article which contains multiple paragraphs and images. The CompoundView adds TextView or ImageView dynamically based on the data attached by CompoundView.setData(List<DataPiece> pieces), the number of which is unknown before data is attached. Each DataPiece object tells CompoundView whether it's a piece of text or an image. And here is the code for CompoundView.setData(List<DataPiece> pieces):
public void setData(List<DataPiece> pieces) {
removeAllViews();
for (DataPiece dataPiece : pieces) {
switch (dataPiece.getType()) {
case IMAGE:
ImageView imageView = new ImageView(getContext());
...
addView(imageView);
break;
case TEXT:
TextView textView = new TextView(getContext());
...
addView(textView);
break;
}
}
}
In the RecyclerView.Adapter.onBindViewHolder(), the data is attached to CompoundView by calling MyViewHolder.compoundView.setData(...). And it works fine when the RecyclerView is created.
However, for a CompoundView item with multiple ImageViews and TextViews, when I scroll away from it and then scroll back, the scroll becomes heavily unsmooth.
I guess it's because removeAllViews() in setData() is called, and the CompoundView creation for-loop is executed again by the recycler. But I don't know how to avoid this.
And I also wonder why the scroll is always smooth when using TextView(with Images) in a RecyclerView even it's recycled too.
Thanks in advance!
There are multiple considerations that could go into deciding what the best approach might be.
First, do you have an idea about the maximum number of items in the recycler's list? If it is just a handful, maybe you could ditch the RecyclerView approach and just add your CompoundView into a container hosted by a ScrollView.
Secondly - is the layout of each item fairly complicated (a.k.a. are there many TextViews, ImageViews etc. in it)? If yes, maybe you could take an approach that would resemble an ExpandableListView - show a summary as each list item and expand to the full layout of the item on click.
Thirdly - if none of the above is acceptable and you still want to go the current approach - don't construct/add your view in the binding method. Do it in the onCreateViewHolder, when the system expects you to construct your view (I don't know for sure but by the time you're called on onBindViewHolder your view might have been already added to the hierarchy and any hierarchical change to it has a ripple effect on its containers - but don't take my word for it, I don't actually know the view is already added, it is just an assumption). You will have to assign each item a different type, so that in onCreateViewHolder you could match the view type with the supporting data (for the addition of the corresponding number of child views); create the view from scratch each time - this way you don't need to call on removeAllViews. Something like(I left out parts of the adapter that are not relevant to the case):
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ArrayList<DataPiecesList> mItems;
public RecyclerViewAdapter(ArrayList<DataPiecesList> items) {
mItems = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CompoundView compoundView = new CompoundView();
List<DataPiece> dataPieces = mItems.get(viewType);
for (int i = 0; i < dataPieces.size(); i++)
{
// construct TextView or ImageView or whatever
compoundView.add(child);
}
MyViewHolder view = new MyViewHolder(compoundView);
return view;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
CompoundView compoundView = viewHolder.itemView;
DataPiece dataPiece = mItems.get(i);
for (int j = 0; j < compoundView.getChildCount(); j++)
{
compoundView.getChildAt(j) <- dataPiece.get(j);
}
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
return mItems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
...
public MyViewHolder(View itemView) {
super(itemView);
}
}
}
RecyclerView is supposed to reuse the views. It will be slow if you throw away the already created TextView / ImageView objects and create new ones every time.
It sounds like you need a RecyclerView with multiple view types. The idea is to create multiple view holders - some of them with ImageView, the others with TextView. You'll have to override the getItemViewType(int position) method of your adapter - it should return different values for the IMAGE items and the TEXT items. The onCreateViewHolder(ViewGroup parent, int viewType) receives a viewType parameter so you know which type of ViewHolder to create there. In the onBindViewHolder(VH holder, int position) you could assume that the holder passed to you is the correct type (i.e. the type with TextView for TEXT items and the type with ImageView for IMAGE items), so there is no need to remove its child views and create them again.
There is nice article about RecyclerView's Adapters with multiple view types here.

Categories

Resources