I have the problem that I want to update a nested RecyclerView with dynamically loading data. The outer recyclerView is vertical and the inner recyclerView is horizontal. So, I have created 2 adapters.
The main activity:
public class GroupScreenActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private OuterRecyclerViewAdapter adapter;
// the service connection
private ServiceConnection connection = new ServiceConnection() {
... // code that handles the service connection (not relevant for my question)
}
};
#Override
protected void onStart(){
// code that bind the service to the activity (not really relevant for my question)
}
#Override
protected void onStop(){
// code that unbinds the service from the activity (not really relevant for my question)
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group_screen);
recyclerView = (RecyclerView) findViewById(R.id.recycler_View);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
updateUI();
}
// a Handler that calls a method of a bound service to retrieve the data of interest
private void updateUI(final String token){
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
if(bound && (mDownloadCliquesService != null)){
// holds the list of the statement lists
ArrayList<ArrayList<Statement>> myList = mDownloadCliquesService.getDataOfUser();
if(adapter == null){
// data is passed to the outer recyclerView adapter
adapter = new OuterRecyclerViewAdapter(this, myList);
recyclerView.setAdapter(adapter);
}
else{
// notify that the data is changed
adapter.notifyDataSetChanged();
}
}
// repeat the whole after 5 seconds
handler.postDelayed(this, 5000);
}
});
}
}
As you can see: The main activity just retrieves some data from a bound service and passes it to the outer recycler view. The data is a list of lists of type Statement. The number of lists in myList gives the rows of the outer recyclerview and the items in each list will define the number of columns of each inner recyclerview.
The outer recycler view looks like the following:
public class OuterRecyclerViewAdapter extends RecyclerView.Adapter<OuterRecyclerViewAdapter.InnerRecyclerViewHolder> {
// some instance variables
public OuterRecyclerViewAdapter( ... ) {
...
}
#Override
public InnerRecyclerViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.inner_recyclerview_layout, parent, false);
return new InnerRecyclerViewHolder(view);
}
#Override
public void onBindViewHolder(final InnerRecyclerViewHolder holder, int position) {
// here, I create the inner recycler view adapter. Do I need to update it too???
adapter = new InnerRecyclerViewAdapter(context, items, position);
holder.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
holder.recyclerView.setAdapter(adapter);
}
#Override
public int getItemCount() {
return items.size();
}
public class InnerRecyclerViewHolder extends RecyclerView.ViewHolder {
private RecyclerView recyclerView;
private Button mAddButton;
private Button mSendButton;
private TextView tvCliqueName;
private ArrayList<Object> mList;
public InnerRecyclerViewHolder(View itemView) {
super(itemView);
// using 'itemView', get a reference to the inner recycler view.
recyclerView = (RecyclerView) itemView.findViewById(R.id.inner_recyclerView);
// get a reference to the clique name
tvCliqueName = (TextView) itemView.findViewById(R.id.cliqueName);
// get a reference to the send button
mSendButton = (Button) itemView.findViewById(R.id.send);
// get a reference to the add button
mAddButton = (Button) itemView.findViewById(R.id.add);
}
}
}
For the sake of brevity, I do not post the code for the inner recyclerview adapter because there is no adapter reference to update.
So, after every 5 second the main activity gets fresh data from my bound service and passes it to the outer recyclerview adapter which looks how many lists exist in the nested array list. Each list is then passed to the inner recycler view adapter which then shows the elements of each list.
So, my problem is the following: After each update the list is scrolling to the beginning. Let's say I have 5 elements in the first list, and I scroll to the 3rd one, after the update inner recycler view goes to the 1st automatically. Here is a short GIF how the output looks like:
I have checked out the following StackOverflow posts:
How to save RecyclerView's scroll position using RecyclerView.State?
Nested Recyclerview scrolls by itself
How to save scroll position of recyclerview in fragment
How to save scroll position of RecyclerView in Android?
But without success. How do I need to update so that the scroll position is not affected ?
Thanks and best regards
Save your horizontal scroll value:
outerRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
//save dx
}
});
and restore after update:
outerRecyclerView.setScrollX(dx)
Related
I have a recycler view for image display with a simple adapter.
public class ImageListAdapter extends RecyclerView.Adapter<ImageListAdapter.SingleItemRowHolder> {
private ArrayList<Bitmap> itemsList;
public ImageListAdapter(ArrayList<Bitmap> itemsList) {
this.itemsList = itemsList;
}
#Override
public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, null);
return new SingleItemRowHolder(v);
}
#Override
public void onBindViewHolder(SingleItemRowHolder holder, int i) {
Bitmap img = itemsList.get(i);
holder.itemImage.setImageBitmap(img);
}
#Override
public int getItemCount() {
return (null != itemsList ? itemsList.size() : 0);
}
public void addItem(Bitmap image) {
itemsList.add(image);
notifyItemInserted(itemsList.size()-1);
}
public void setItemsList(ArrayList<Bitmap> bmps) {
itemsList = bmps;
}
public ArrayList<Bitmap> getItemsList() {
return itemsList;
}
public void cleanData() {
itemsList = new ArrayList<>();
notifyDataSetChanged();
}
public class SingleItemRowHolder extends RecyclerView.ViewHolder {
protected ImageView itemImage;
public SingleItemRowHolder(View view) {
super(view);
this.itemImage = (ImageView) view.findViewById(R.id.itemImage);
}
}
}
Nothing complex here. Just a simple adapter that getting bitmap list as input and show them in the recycle view.
The point is, the bitmaps are asynchronously added via a retrofit call, as shown in the folloing:
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey).enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
}
});
As you can see here, the bitmap is added to the adapter and the adapter should be able to notify the changes as addItem() contains a notifyItemInserted() call. And this retrofit call is called several times depending on how many times the view model has changes its value (there is a observer observing the change of view model's value. Once the value changes, the above retrofit will get called). So the code that changes view model value look like this:
if (placeDetail.getPhotoRef() != null) {
for (PlaceDetail.PhotoRef ref : placeDetail.getPhotoRef()) {
viewModel.setPhoto(ref.getPhotoRef()); // ref.getPhotoRef returns the photo ref string
}
}
In my expectation, the item list of the adapter should now contains several images. I tried to debug it, and I found when the addItem() method get called, the amount of item list did change as expected. But when it came into the onBindViewHolder() callback, there is only one item left in the item list, which is the first bitmap.
Can anyone tell me why this issue happened? Any suggestion would be appreciated.
---------------------------------- Update --------------------------------
Interesting. I finally found why it only shows one photo. As you can see here, the adapter (id:24856) item list size is 4, which is in my expectation.
However, there is another adapter(id:24962) exists. For that adapter, the add item method only called once thus only one item in that adapter. When the recycler view changes the content, the onBindViewHolder is called on the second adapter. Now my question is, where does the second adapter come from ???
Here is my code to initialize recycler view and adapter
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
RecyclerView recyclerView = binding.imageGallery;
recyclerView.setHasFixedSize(true);
ImageListAdapter adapter = new ImageListAdapter(new ArrayList<>());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerView.setAdapter(adapter);
return binding.getRoot();
}
Hej AhSoHard,
did you check if it is a timing issue, because you are loading your data asynchronously and it could lead to missing updates.
also could it be that setItemsList(ArrayList<Bitmap> bmps) is called and overwrites the current list?
is there a reason why you do not use the viewGroup as a base for inflating the layout resource in onCreateViewHolder()?
LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, viewGroup, false)
in my opinion the Adapter should not be the holder of the current state, maybe have a list outside and use a ListAdapter to display it. When you have a new list of bitmaps, you just call adapter.submit(bitmaps) and the new list will be displayed. Together with a DiffCallback, the old items will not update and the new ones added at the end of the list.
Let's implement the DiffUtils adapter and try to change your calls in this way. As kotlin works as interoperable with java, therefore I am mentioning the gist for an adapter that is written in kotlin.
Check adapter and view holder implementation
Code
ImageListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
binding.imageGallery.setHasFixedSize(true);
adapter = new ImageListAdapter(new ArrayList<>());
binding.imageGallery.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
binding.imageGallery.setAdapter(adapter);
return binding.getRoot();
}
Interface googleMapService
Single<ResponseBody> getImageByPhotoReference(_,_,_,_)
Network Call
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ResponseBody>() {
#Override
public void onSuccess(ResponseBody response) {
if (response != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
#Override
public void onError(Throwable e) {
// Do with your error
}
});
It's interesting that you initialize the adapter in onCreateView method and it has local visibility(only visible inside of the onCreateView method). But in the googleMapService.getImageByPhotoReference() method's callback you are referencing it adapter.addItem(bmp);. So my guess is that you are referencing the wrong adapter object in the googleMapService.getImageByPhotoReference() method's callback. The callback may reference an old instance of ImageListAdapter.
Try to initialize ImageListAdapter adapter as a property of your Fragment class and see if it works.
I am displaying a recycler view on the whole screen the recycler view has only item an image view i want to know how i can get the positon of the imageview shown on my screen outside the recycler adapter.
I have tried to implement an interface but the results are not accurate.I want to save the current position instantly.
My recycler adapter:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHOlder> {
private List<ImageList> mlist;
private showPageNumber page;
public RecyclerAdapter(List<ImageList> dataList, showPageNumber page) {
mlist = dataList;
this.page = page;
}
#NonNull
#Override
public MyViewHOlder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item,parent,false);
return new MyViewHOlder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHOlder holder,int position) {
Picasso.get().load(mlist.get(position).getUrl()).into(holder.photoView);
page.showPage(position);
holder.pageNumber.setText(position+1+"");
}
#Override
public int getItemCount() {
return mlist.size();
}
class MyViewHOlder extends RecyclerView.ViewHolder{
ImageView photoView;
TextView pageNumber;
public MyViewHOlder(#NonNull View itemView) {
super(itemView);
photoView = itemView.findViewById(R.id.pdfImage);
pageNumber = itemView.findViewById(R.id.pageNumber);
}
}
public interface showPageNumber{
void showPage(int position);
}
}
Whenever I implements the showPageNumber interface on my MainActivity the showPage method does't give accurate results on scrolling the items in recycler view.
I am not completely sure what you like to accomplish but if you like to find out which item is currently shown you might want to try this:
Assuming you are using a LinearLayoutManager on your RecyclerView you could listen to Scroll events an evaluate which page is shown when the ReclycerView settles after scrolling
RecyclerView recyclerView = findViewById(R.id.rcv);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// if you just want to know if the new "page" is completely visible
if(newState == RecyclerView.SCROLL_STATE_SETTLING){
int pagePosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
// if you just want to know if the new "page" comes in view
int pagePosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
}
}
});
Alright, I stumbled into this problem and I ran here, got disappointed with the output I got from other answers and here.
But here is what I found.
The concept behind it is to monitor where the recyclerview is going to and update a global variable. In my case I had cancelled scroll gestures on the recyclerView and I wanted it to only scroll smoothly when I press a button.
So, here I monitored the recyclerview position and updated my values accordingly.
//Global variable:
var rvPosition = 0
onClick(v: View?){
when (v?.id) {
R.id.schedule_previous -> {
if (rvPosition > 0) {
smoothScroller.targetPosition = rvPosition - 1
binding.scheduleRv.layoutManager?.startSmoothScroll(smoothScroller)
rvPosition -=1
}
}
R.id.schedule_next -> {
if (rvPosition < dataset.size) {
smoothScroller.targetPosition = rvPosition + 1
binding.scheduleRv.layoutManager?.startSmoothScroll(smoothScroller)
rvPosition +=1
}
}
R.id.schedule_users_card -> {}
}
}
What I would suggest is creating a lambda that takes a value (here it would be the item position), and assigns it to the variable you want.Then, pass that lambda to the recycler and use it there to assign value to your variable.
That way, now you have your variable value wherever you want.
Simple enough if I do say so myself ;)
I am creating an AlertDialog custom class, called ActionDialog, which will contains a RecyclerView containing Buttons. I have a List of Button that I populate in the custom class ActionDialog (for now i just populate with useless Button just to try to use it, except one which I create in another class).
The problem is that when i create the AlertDialog, all buttons are showing empty, they are showed but with no text/no clicklistener (as you can see in the image below).
(I have added a custom ActionListener to a Button in another class and then give it as parameter in ActionDialog class. Will it lose the ActionListener?)
Here is the result.
I will leave here my ActionDialog class code, and the adapter class.
This is ActionDialog class:
public class ActionDialog extends AlertDialog{
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private Button actionButtons;
private List<Button> buttons;
private Activity context;
public ActionDialog(#NonNull Activity context, Button actionButtons) {
super(context);
this.context = context;
this.actionButtons = actionButtons;
buttons = new ArrayList<>();
initButton();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
}
private void initButton(){
initZoneButton();
//TODO init all buttons
Button b1 = new Button(context);
b1.setText("ExampleButton1");
Button b2 = new Button(context);
b2.setText("ExampleButton2");
b1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String a;
}
});
buttons.add(b1);
buttons.add(b2);
}
private void initZoneButton(){
buttons.add(actionButtons); //this button is created in another class and give as parameter in this class
}
public void createDialog(){
Builder mBuilder = new Builder(context);
View view = context.getLayoutInflater().inflate(R.layout.dialog_actionbuttons_layout, null);
mRecyclerView = view.findViewById(R.id.dialog_actionbuttons_rv);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(context);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new ActionButtonsAdapter(buttons);
mRecyclerView.setAdapter(mAdapter);
mBuilder.setView(view);
mBuilder.create().show();
}
}
Here is the RecyclerView adapter class:
public class ActionButtonsAdapter extends RecyclerView.Adapter<ActionButtonsAdapter.ViewHolder>{
private List<Button> dataButtons;
static class ViewHolder extends RecyclerView.ViewHolder {
Button actionButton;
ViewHolder(View v) {
super(v);
actionButton = v.findViewById(R.id.action_button_rv);
}
}
public ActionButtonsAdapter(List<Button> dataButtons){
this.dataButtons = dataButtons;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.actionButton = dataButtons.get(position);
//i think the problem is here, maybe
}
#Override
public ActionButtonsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_actionbutton_layout, parent, false);
return new ViewHolder(v);
}
#Override
public int getItemCount() {
return dataButtons.size();
}
}
I think in the onBindViewHolder method you should do what ever you want to do with your button.
Also there is no need for the list of buttons here. Make a list the data you need to be held in the Buttons RecyclerView.
I have a RecyclerView that will display Genres for restaurants lets say, So I will create a List of strings to hold these genres names (chickens, meats, etc,..)
Setting its text
holder.actionButton.setText(// Make use of position here);
Or Click Listeners.
Update
You can check google samples for recyclerview here
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
// Get element from your dataset at this position and replace the contents of the view
// with that element
viewHolder.getTextView().setText(mDataSet[position]);
}
wheres mDataset is Array of Strings.
I have implemented my RecyclerView with it's Custom Adapter as follows
Global Declarations as follows
private LinearLayoutManager linearLayoutManager;
private int pastVisibleItems, visibleItemCount, totalItemCount;
private CustomRecyclerViewAdapter customRecyclerViewAdapter;
First I created Adapter Instance inside onCreate() method which has Empty Array inside it and set it to recyclerView
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
Utility.ItemDecorationConst);
recyclerView.addItemDecoration(dividerItemDecoration);
customRecyclerViewAdapter = new CustomRecyclerViewAdapter(getActivity());
recyclerView.setAdapter(customRecyclerViewAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = linearLayoutManager.getChildCount();
totalItemCount = linearLayoutManager.getItemCount();
pastVisibleItems = linearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
loading = false;
customRecyclerViewAdapter.addProgressBarEntry();
controller.getNextPage(PublisherAppContainerFragment.this);
}
}
}
});
After rendering complete View when I get data from AsyncTask for filling in recyclerView
I call following method of the Adapter to fill data
customRecyclerViewAdapter.addAll(myArray);
note : addAll() is not any overridden method
following is code of my CustomRecyclerViewAdapter
class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {
ArrayList<MyModel> arrayList = new ArrayList<>();
Context context;
public CustomRecyclerViewAdapter(Context context) {
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = null;
//inflated some view
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//binded data to holder
}
#Override
public int getItemCount() {
return arrayList.size();
}
public void addAll(ArrayList myArray) {
this.arrayList.addAll(myArray)
}
public void clear() {
arrayList.clear();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CardView cardView;
public ViewHolder(View view) {
super(view);
this.cardView = (CardView) view.findViewById(R.id.card_view);
this.cardView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
//handle operations
}
}
}
So whenever I get data from AsynTask I call method addAll() and recyclerView works like charm.
Now, My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter. Are there any previously registered Observers for the adapter? who observes if the dataset which has been returned in public int getItemCount() has been changed?
As I have read from documentation
void notifyDataSetChanged ()
Notify any registered observers that the data set has changed.
that means even though there are some observers registered you need to notify them using notifyDataSetChanged(). Right?
I also called
boolean flag = customRecyclerViewAdapter.hasObservers();
to know if there are any observers registered? Flag is True.
So anyone would please help me understand how exactly these things work?
If you look at the source of RecyclerView setAdapter call you will find a method setAdapterInternal(adapter, false, true);which is responsible for
Replaces the current adapter with the new one and triggers listeners.
This method is responsible for swapping the old adapter with the new one and internally it also registers for the custom Data Observer. This is the reason you are getting the flag as true
Based on what I can see of your code, I would say that there are not any observers attached to your RecyclerView that are picking up changes and keeping the list updated. What is more likely is that you are just getting "lucky" as when you scroll through the list the layout manager is continually calling getItemCount() on the adapter to determine if it should show more items. Whenever you call addAll(), you silently update the item count and it just happens to appear that observers were notified of the changes.
This is definitely a bug, and you would more likely see its effects in your implementation if you were dependent on a particular observer to monitor some aspect of the list, or doing more than just appending new items to the bottom (for example altering or inserting between existing items). The correct implementation as you pointed out is to call notifyDataSetChanged() whenever the list is updated, or even better be more specific with what changed if you can. For example, you can use:
public void addAll(ArrayList myArray) {
int positionStart = getItemCount() - 1;
this.arrayList.addAll(myArray);
notifyItemRangeInserted(positionStart, myArray.size());
}
public void clear() {
int oldSize = getItemCount();
arrayList.clear();
notifyItemRangeRemoved(0, oldSize);
}
My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter
It's because the addAll method by default calls the notifyDataSetChanged().
public void addAll(T ... items) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.addAll(mOriginalValues, items);
} else {
Collections.addAll(mObjects, items);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
And
public void addAll(#NonNull Collection<? extends T> collection) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.addAll(collection);
} else {
mObjects.addAll(collection);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
Here's the link - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java
EDIT - I see that you have your own addAll method which is calling addAll method of ArrayList.
This is how addAll method works -
private ArrayList<String> ex1 = new ArrayList();
private ArrayList<String> ex2 = new ArrayList();
private ArrayList<String> ex3 = new ArrayList();
ex1.add("one");
ex2.add("two");
ex3.addAll(ex1);
ex3.addAll(ex2);
System.out.println(ex3);
OUTPUT - [one, two]
This is what happening in your case.
I have shown progress bar and once I fetch data I hide the progress bar and make recyclerView visible - If in layout or code you set RecyclerView visibility GONE then layout will not happen and that is why Adapter.getItemsCount() not get called. So if you fetch data and populate adapter array with it and then change RecyclerView visibility from GONE to VISIBLE it will trigger update.
In case you don't call notifyDataSetChanged() RecyclerView will not know about update. I guess there is something else in your code that trigger RecyclerView update. To clarify this behavior let's use some dummy adapter:
private class DummyViewHolder extends RecyclerView.ViewHolder {
public DummyViewHolder (View itemView) {
super(itemView);
}
}
private class Adapter extends RecyclerView.Adapter<DummyViewHolder> {
private int mDummySize = 5;
#Override
public DummyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.dummy_view, parent, false);
return new DummyViewHolder(view);
}
#Override
public void onBindViewHolder(DummyViewHolder holder, int position) {
}
void setSize(int size) { this.mDummySize = size; }
#Override
public int getItemCount() {
return mDummySize;
}
}
And in onCraete() :
ViewHolder v = ...
final Adapter adapter = ..
...
//postpone adapter update
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
adapter.setSize(10);//and nothing happend only 5 items on screen
}
}, 5000);
I'm trying to display the listview from sqlite. At the end of Listview I want to load next 10 data from sqlite and this process will continue till data ends in SQLite. But the issue is data load at first time successfully and when i scroll 3rd time page/data does not load and after LocalListPageIndex= 2 then progress bar is continuously running.
Here is my Code of ListView Scrolling.
listView.removeFooterView(progressBar);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, final int totalItemCount) {
lastInScreen = firstVisibleItem + visibleItemCount;
if (X != firstVisibleItem) {
if (LocalListPageIndex <= 5) {
Toast.makeText(getApplicationContext(), "Your Last Item." + lastInScreen, Toast.LENGTH_SHORT).show();
if (lastInScreen == totalItemCount) {
listView.addFooterView(progressBar);
// Execute some code after 15 seconds have passed
Handler handler = new Handler();
handler.postDelayed(new Runnable()
{
public void run()
{
LocalListPageIndex += 1;
int OFFSET_SCROLL = 10
List<All_Post> allDesc = dbhelper.getAllDescriptions(OFFSET_SCROLL);
for (All_Post all_Post : allDesc) {
descArray.add(all_Post);
}
if (adapter != null) {
adapter.notifyDataSetChanged();
// adapter = new AllPostAdapter(getApplicationContext(), R.layout.allpostlist, descArray);
listView.setAdapter(adapter);
//listView.invalidateViews();
listView.setSelection(totalItemCount);
}
}
}, 15000);
}
} if (LocalListPageIndex == 5) {
Log.e("hide footer", "footer hide");
listView.removeFooterView(progressBar);
}
}
X = firstVisibleItem;
}
});
You could try using RecyclerView:
The RecyclerView widget is a more advanced and flexible version of ListView. This widget is a container for displaying large data sets that can be scrolled very efficiently by maintaining a limited number of views. Use the RecyclerViewwidget when you have data collections whose elements change at runtime based on user action or network events.
A layout manager positions item views inside a RecyclerView and determines when to reuse item views that are no longer visible to the user. To reuse (or recycle) a view, a layout manager may ask the adapter to replace the contents of the view with a different element from the dataset. Recycling views in this manner improves performance by avoiding the creation of unnecessary views or performing expensivefindViewById() lookups
Example:
Add this view to your layout:
android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
Once you have added a RecyclerView widget to your layout, obtain a handle to the object, connect it to a layout manager, and attach an adapter for the data to be displayed:
public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
// Set your myDataSet with the url of your images.
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
...
}
Create an adapter to manage the recycler view:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private MySQLClass mDataset;
// 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
public ViewHolder(TextView v) {
super(v);
textView = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(MySQLClass myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_image_view, parent, false);
// set the view's size, margins, paddings and layout parameters
...
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
textView.setText(mDataset.get(i));
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return mDataset.length;
}
}
info from:
http://developer.android.com/training/material/lists-cards.html
When you are working with retrieving the data from the database its better to use loaders.A loader will load all the details depending upon your query and give it to you in the form of a Cursor.Now you can simply give that cursor to CursorAdapter(if you are using Cursor adapter)or you can extract the data from cursor to a list and give that list to normal adapter.