Progress bar not moving - android

I have progress bar in every item inside my listview. When I tap an item on the list, another thread makes the progress bar move. For some reason the output says the progress bar is increasing but it's not. Below is some of the code.
mHandler.post(new Runnable() {
public void run() {
ViewHolder vh = (ViewHolder) adapter.getView(pro, null, null).getTag();
Log.d("Progress",String.valueOf(vh.progress.getProgress()));
vh.progress.setProgress((int) prog[pro]);
}
});

getView() on your Adapter is probably creating a brand new row, since that's what it is supposed to do when you call getView() with a null second parameter.
You need to have a better scheme for retrieving the ProgressBar that you wish to update, taking into account that the ProgressBar may have been recycled to be displaying some other row's progress.

Related

RecyclerView notifyItemRangeInserted not maintaining scroll position

I have a simple recyclerview with items (tips) and a loading spinner at the bottom.
here's how the item count and item view type methods look:
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) { // last position
return LOADING_FOOTER_VIEW_TYPE;
}
else {
return TIP_VIEW_TYPE;
}
}
#Override
public int getItemCount() {
return tips.size() + 1; // + 1 for the loading footer
}
basically, i just have a loading spinner under all my items.
I create the adapter once like so:
public TipsListAdapter(TipsActivity tipsActivity, ArrayList<Tip> tips) {
this.tipsActivity = tipsActivity;
this.tips = tips;
}
and then once i have fetched additional items, i call add like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeInserted(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
What's odd here is that when i do that, the scroll position goes to the very bottom. It almost seems like it followed the loading spinner. This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
This doesn't happen if i change notifyItemRangeInserted() to notifyItemRangeChanged() like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeChanged(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
Nor does it happen if i simply call notifyDataSetChanged() like so:
public void addTips(List<Tip> tips) {
this.tips.addAll(tips);
notifyDataSetChanged();
}
Here's the code for setting the adapter in my Activity:
public void setAdapter(#NonNull ArrayList<Tip> tips) {
if (!tips.isEmpty()) { // won't be empty if restoring state
hideProgressBar();
}
tipsList.setAdapter(new TipsListAdapter(this, tips));
}
public void addTips(List<Tip> tips) {
hideProgressBar();
getAdapter().addTips(tips);
restorePageIfNecessary();
}
private TipsListAdapter getAdapter() {
return (TipsListAdapter) tipsList.getAdapter();
}
Note:
I don't manually set scroll position anywhere.
I call setAdapter() in onResume()
addTips() is called after I fetch items from the server
Let me know if you need any additional parts of my code.
This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
RecyclerView has built-in behavior when calling the more-specific dataset change methods (like notifyItemRangeInserted() as opposed to notifyDataSetChanged()) that tries to keep the user looking at "the same thing" as before the operation.
When the data set changes, the first item the user can see is prioritized as the "anchor" to keep the user looking at approximately the same thing. If possible, the RecyclerView will try to keep this "anchor" view visible after the adapter update.
On the very first load, the first item (the only item) is the loading indicator. Therefore, when you load the new tips and update the adapter, this behavior will prioritize keeping the loading indicator on-screen. Since the loading indicator is kept at the end of the list, this will scroll the list to the bottom.
On subsequent loads, the first item is not the loading indicator, and it doesn't move. So the RecyclerView will not appear to scroll, since it doesn't have to do so to keep the "anchor" on-screen.
My recommendation is to check insertPos and see if it is zero. If it is, that means this is the first load, so you should update the adapter by calling notifyDataSetChanged() in order to avoid this anchoring behavior. Otherwise, call notifyItemRangeInserted() as you're currently doing.
Remove the setAdapter code from onResume ASAP as you are setting new TipsListAdapter(this, tips);
Every time a new reference of the adapter is created...make field mAdapter and then set it in onCreate . RecyclerView doesnt remember the scrolled position because everytime a new reference of adapter is being created.. onResume gets called infinitely when activity is in running state..
So either you setAdapter in onCreate using new operator to create reference for adapter or,
in onResume use mAdapter field variable reference..

Null pointer exception when trying to programmatically perform click on non visible Recycler view item

I have a recycler view and I want to perform click on one of its items.
Here is my code:
mRecyclerView.findViewHolderForAdapterPosition(2).itemView.performClick();
It works fine when the item is visible, but when it is not visible i'm getting a null pointer exception
also i tried scroll to position before perform click but i got same result
Any idea on how to solve this issue?
I have solved my problem with this code
mRecyclerView.getLayoutManager().scrollToPosition(17);
search_list.postDelayed(new Runnable() {
#Override
public void run() {
mRecyclerView.findViewHolderForAdapterPosition(17).itemView.performClick();
}
}, 50);
There is a slight delay for the viewholder to be created. Thus if the item is clicked before viewholder is created an NPE would occur
Unfortunately for you, this is working as intended. When a child View is scrolled out of the boundaries of a RecyclerView, the child View is often reused to display another item for another position in the list, hence you will get a null View for the position that is no longer displayed.
What you can do is implement a getItem() on the RecyclerView.Adapter to retrieve the item for that position. Not sure if that satisfies your requirements though.
It is recommended to use a listener to wait for the drawing to complete,
Then perform the operation you want.
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// At this point the layout is complete
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});

Progress visibility not working in list view footer

I am using pagination in my list view and i am displaying a progress bar(small) in list view footer when user scroll down to end of the list view.
LayoutInflater inflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.list_footer, null);
myListView.addFooter(view).
Then i am setting my adapter as follows
ProgressBar progressBar = (ProgressBar) getFooterView().findViewById(R.id.progressBar);
mAdapter = new MyAdapter(BaseActivity.getActivity(), 0, progressBar, myArrayList);
mList.setAdapter(mAdapter);
In my adapter class i am settings progress bar visibility in getView() method as follows.
if(position == MAX_RECORDS)
{
progressBar.setVisibility(View.VISIBLE);
// Some code goes here.
} else
progressBar.setVisibility(View.GONE);
But ProgressBar not disappears when list has no data to fetch. Please help me.
Set progressBar to visible state initially and use the same code itself.
try this one.Changes in this line will help you
ProgressBar progressBar = (ProgressBar)view.findViewById(R.id.progressBar);
instead of
ProgressBar progressBar = (ProgressBar) getFooterView().findViewById(R.id.progressBar);
try :
if(position == MAX_RECORDS-1)
position starts with 0
UPDATE
Just trying to understand your code here:
if(position == MAX_RECORDS-1)
{
progressBar.setVisibility(View.VISIBLE);
// Some code goes here.
}
above block will get executed when the last row is displayed. since getview is called for all rows before, and hopefully you are using convertview, the else block will not be executed again.
Net result is : dialog will be visible indefinitely.
If I am right you want to show the dialog when loading data and hide it when load completes. There are two scenarios:
you fetch data in one go
you fetch data when last row is displayed.
Can you shed some light which one of above is true?

update single item view in listview scroll issue

A little difficult problem to describe so please be patient
i have a progress bar in every listitem of listview used to show downloading
now to update a single view i get view object of single item
now this view is passed to async task to update only that view for which downloading is in progress
it works fine untill i scroll listview
when i scroll list view progress bar is shown for some random listitem of listview
code to invoke asynctask
ListView lv = getListView();
int visiblePosition = lv.getFirstVisiblePosition();
View v = lv.getChildAt(position - visiblePosition);
v.setClickable(true);
task = new TestLoadingTask();
task.v = lv.getChildAt(position - visiblePosition);
executeAsyncTask(task, null, null, null);
on progressupdate of async task
#Override
protected void onProgressUpdate(Object... values) {
super.onProgressUpdate(values);
progress[currentposition] = (Integer)values[0];
max[currentposition] = 100;
updateprogress(currentposition,v);
}
public void updateprogress(int position,View v)
{
ProgressBar update = (ProgressBar)v.findViewById(R.id.downloadbar);
if(max!=null && max[position]!=0)
update.setMax(max[position]);
if(progress!=null)
update.setProgress(progress[position]);
TextView tv_per = (TextView) v.findViewById(R.id.percentage);
if(progress[position]==max[position])
{
tv_per.setVisibility(View.GONE);
update.setVisibility(View.GONE);
}
else
{
tv_per.setVisibility(View.VISIBLE);
tv_per.setText(progress[position]+"/"+max[position]);
update.setVisibility(View.VISIBLE);
}
}
any reference/advice/example would be greatly appreciated.
Okay, if I understood you right, your problem is that ListView's adapter doesn't create new list-items every time you scroll it, it just converts previous. So if it doesn't have special directions about some views of new visible list-item, it will draw them like in previous list-item which was at this position.
The solution is to add some flag to your list-items (like boolean isUpdating) and show progress bar at your adapter's getView method if it's true, and hide progress bar otherwise

Android ListView notifyDataSetChanged

I've got a custom listview, each entry contains spinning ProgressBar and single line textview. I'm always updating only the first item in the list. The problem is that every time I call notifyDataSetChanged all visible views are either recreated or recycled as convertView.
There are 2 problems with this approach:
all entries are recreated and that's slow - and that's not required as I'm updating only the textview in first entry
the ProgressBar animation restarts every time
So I though that I'll just keep the reference to first View in adapter and update it manually. It worked, but it randomly throws IllegalStateException ("The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.")
How do I solve it? Is there a way to notify ListView that only first entry changed? Even if there is, ProgressBar animation will still fail. Is there any other way to update it manually?
Thanks
Whenever i need to update one single item on a listview i use this:
public void updateItemAt(int index) {
View v = listView.getChildAt(index - listView.getFirstVisiblePosition());
if (v != null) {
//updates the view
}
}
The downside is that you need to hold a reference to the listView in your adapter.
As this code is usually called from a background task, you have to use a handler to call it:
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
int index = msg.what;
myAdapter.updateItemAt(index);
};
};
//when task is complete:
myHandler.sendEmptyMessage(index);

Categories

Resources