Preface
I want to implement a download animation in RecyclerView. In onBindViewHolder and onViewRecycled I subscribe to and unsubscribe from presenter which retrieves the percentage of already downloaded file.
I start animation:
AnimatedVectorDrawableCompat drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.download_animated);
if (drawable != null) {
holder.downloadingIcon.setImageDrawable(drawable);
drawable.start();
}
Problem
I have got onViewRecycled invoked right after the animation is finished with follow stacktrace:
java.lang.RuntimeException
at EventRecyclerViewAdapter.onViewRecycled(EventRecyclerViewAdapter.java:271)
at android.support.v7.widget.RecyclerView$Recycler.dispatchViewRecycled(RecyclerView.java:6064)
at android.support.v7.widget.RecyclerView$Recycler.addViewHolderToRecycledViewPool(RecyclerView.java:5835)
at android.support.v7.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:5800)
at android.support.v7.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1305)
at android.support.v7.widget.RecyclerView$ItemAnimatorRestoreListener.onAnimationFinished(RecyclerView.java:11775)
at android.support.v7.widget.RecyclerView$ItemAnimator.dispatchAnimationFinished(RecyclerView.java:12275)
at android.support.v7.widget.SimpleItemAnimator.dispatchChangeFinished(SimpleItemAnimator.java:304)
at android.support.v7.widget.DefaultItemAnimator$7.onAnimationEnd(DefaultItemAnimator.java:363)
at android.support.v4.view.ViewPropertyAnimatorCompatJB$1.onAnimationEnd(ViewPropertyAnimatorCompatJB.java:51)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1121)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1149)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1309)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.-wrap2(AnimationHandler.java)
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:957)
at android.view.Choreographer.doCallbacks(Choreographer.java:734)
at android.view.Choreographer.doFrame(Choreographer.java:667)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:945)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
Question
Why it behaves like so and how can I fix it?
It turns out, notifyItemChanged caused to recycler previous ViewHolder so onBindViewHolder and onViewRecycled are invoked... but on different instances. Firstly onBindViewHolder is invoked with new ViewHolder instance. Next onViewRecycled is invoked with the old ViewHolder. In my case problem was relying only on one id of the model. It doesn't unambiguously describe Presenter's View I used to notify about updates.
I'm using a cursor loader with the RecyclerView, and everything else works just fine except every time I remove an item from the RecyclerView, the last item blinks, like this
last item blinks
my code for deletion is
public void deleteData(long id){
Uri uri = ContentUris.withAppendedId(URI, id);
getContentResolver().delete(uri, null, null);
}
code on swipe:
deleteData(viewHolder.getItemId());
note that I have another activity that's using the same RecyclerAdapter, ContentProvider and layout code, even most of the implementations and method approaches are the same, but that one works perfectly without any blinks, so it is a pretty weird situation for me.
Is there a specific reason that can cause this problem? I already tried disabling the animation like
recyclerView.getItemAnimator().setChangeDuration(0);
or
((DefaultItemAnimator) recyclerViewObject.getItemAnimator()).setSupportsChangeAnimations(false);
But none of that worked, not to mention that I do want the animations to work.
Edit: Break points upload:
onCreate:
"main#4668" prio=5 tid=0x2 nid=NA runnable
java.lang.Thread.State: RUNNABLE at com.jackz314.todo.HistoryActivity.onCreateLoader(HistoryActivity.java:803)
at
android.support.v4.app.LoaderManagerImpl.createLoader(LoaderManager.java:539)
at
android.support.v4.app.LoaderManagerImpl.createAndInstallLoader(LoaderManager.java:548) at
android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:603)
at
com.jackz314.todo.HistoryActivity.displayAllNotes(HistoryActivity.java:394)
at
com.jackz314.todo.HistoryActivity.deleteExpiredNotes(HistoryActivity.java:767)
at
com.jackz314.todo.HistoryActivity.onCreate(HistoryActivity.java:129)
at android.app.Activity.performCreate(Activity.java:6975) at
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
at
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
at
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(ActivityThread.java:-1) at
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105) at
android.os.Looper.loop(Looper.java:164) at
android.app.ActivityThread.main(ActivityThread.java:6541) at
java.lang.reflect.Method.invoke(Method.java:-1) at
com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
onDelete(remove item):
"main#4668" prio=5 tid=0x2 nid=NA runnable java.lang.Thread.State:
RUNNABLE at
com.jackz314.todo.HistoryActivity.deleteData(HistoryActivity.java:791)
at
com.jackz314.todo.HistoryActivity$4.onSwiped(HistoryActivity.java:434)
at
android.support.v7.widget.helper.ItemTouchHelper$4.run(ItemTouchHelper.java:686)
at android.os.Handler.handleCallback(Handler.java:789) at
android.os.Handler.dispatchMessage(Handler.java:98) at
android.os.Looper.loop(Looper.java:164) at
android.app.ActivityThread.main(ActivityThread.java:6541) at
java.lang.reflect.Method.invoke(Method.java:-1) at
com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Hope those helps
Edit: After a little investigation, I found out that the last item didn't actually blink, it seems that the RecyclerView first changed it's length/size, cuts off a section that has the equal size as the deleted item(deleting), so that the last item(depending on the size of the deleted item) is not visible for a while, and then the RecyclerView deletes the item that's supposed to be deleted at the beginning. I'm still working on it, trying to figure out whether it's because I messed up the order somewhere or it's some other reason.
Finally got it!
Yet another stupid mistake, it's not complicated at all, I kept thinking, why I'm having this problem only on this activity, but not my previous one, and I suddenly realized that the layout in the two activities are different, turns out I set the RecyclerView's height to wrap_content, so I suppose what happened is that, the layout told the system to wrap the content in the RecyclerView, and when you delete an item, it's technically not there anymore, so, even though I have animations for the deleted item, the item is removed immediately from the layout's level, that's why it seems like Android adjusted the RecyclerView's height first, and then delete the item, what actually happened is, since RecyclerView is set to wrap_content, it removes the position of the view(item) immediately after delete, not after the animation, and that's why it looks like a blink.
Anyways, the solution is just simply to change the RecyclerView's height from wrap_content to any other value, if you are using ConstraintLayout like I did, just change the height to 0dp, then constraint the RecyclerView to something else, or the edge.
Thanks to everyone who tried to help me solve my problem though, I admit this is one of the stupidest yet frustrating problems I ever had.
I believe there're others who also encountered the same problem, so just go and check if the height is set to wrap_content.
I'm stuck with the same thing as you. I believe the problem is that CursorLoader restarts every time we delete something from the database, so onLoadFinished is triggering every time and swapping cursors.
The problem can be "solved" if we load our data from the cursor into the List and pass that to the adapter. Still, i find this as a workaround, not a real solution.
Let place the break point at the data object, which bind with the RecyclerViewAdapter and check if the data is blinked or not. In the most of case like this situation, I realize the data maybe is effected and make view change uncontrollable. Because I don't know how you handle the data, so I can't give more detail answers for you.
Have you tried that, to disable animation?
adapter.setHasStableIds(true);
recyclerView.setItemAnimator(null);
Jackz this code below is in the DetailsActivity that has a btnDelete it fires and goes over to DBHelper and deletes the record and returns to DetailsActivity then makes a call to ListActivity to remove the item from the list based on position NOT record ID
private void addListenerOnButtonDelete() {
btnDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Calls the Method deleteDBRow in DatabaseHelper
// which acts on the TABLE_INFO to remove a record by getting the record ID
helper.deleteDBRow(String.valueOf(dbList.get(position).getID()));
ListActivity.removeListRow(position);
// Code line above calls Method in ListActivity to notify recycler view of changes
// NOTICE the List keeps items by position not ID <== READ
etWebSite.setText("");
etUN.setText("");
etPW.setText("");
etSecQuestion.setText("");
etSecAnswer.setText("");
etNotes.setText("");
Intent intent = new Intent(DetailsActivity.this, ListActivity.class);
startActivity(intent);
}
});
}
Here is the code that lives in the ListActivity that talks to the Adapter
// This method is called from DetailsActivity and notifies Recycler View that the DB was changed
// and the method makes the same changes to the Recycler View kind of a sync of DB and Recycler View
public static void removeListRow(int position) {
dbList.remove(position);
mAdapter.notifyItemRemoved(position);
mAdapter.notifyItemRangeChanged(position, dbList.size());
}
I exactly have the same issue and it makes me crazy.
Your solution does not work in my case, probably because my RecyclerViewer is inside a LinearLayout?
The only workaround I found so far is to disable animation like that
cardRecyclerView.itemAnimator = null
But I really want to re-enable animation since without it is a little bit choppy.
Here is my layout, has someone any idea to avoid the last item blinking when a card is removed? thank you in advance
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="wrap_content">
<fragment
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.bendk97.platenumber.activities.main.Toolbar"/>
<fragment
android:id="#+id/search_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.bendk97.platenumber.activities.main.SearchBar"/>
<ProgressBar
style="#style/Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminateTint="#android:color/white"
android:id="#+id/progressBar" android:visibility="invisible"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
I have created a Linear Layout onclick of a button which has an edit text. While typing there is a popup window for suggestions that comes up and the app crashes.
Stack trace:
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at android.widget.Editor$SuggestionsPopupWindow.initContentView(Editor.java:3714)
at android.widget.Editor$PinnedPopupWindow.(Editor.java:3375)
at android.widget.Editor$SuggestionsPopupWindow.(Editor.java:3660)
at android.widget.Editor.replace(Editor.java:423)
at android.widget.Editor$3.run(Editor.java:2340)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
I found some related answers which stated that this happens on using Android PopupWindow class since the suggestions are using a PopupWindow as well. My code does not use PopupWindow as well.
This crash is happening mostly on Samsung Note 5, and Galaxy S6.
Any help would be appreciated!
It's saying that your button (which you used like buttonName.setOnClickListener()) is not defined. Check your find view by id function, id inside of it etc.
I think it is because you set in XML a click, but don't have in your class. A check has this tag and remove:
android:onClick="save"
I have implemented the Endless scroll with RecyclerView. When user hit the bottom I'm adding null object to display progress in AsyncTask onPreExecute and notify adapter item Inserted. This code I have been using for quiet some time already. Recently I have updated android Support Library From 23.0.3 to 24.2.1. Since then I am getting exception when hit bottom of the list.
Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure & layout pass where you cannot change the RecyclerView data. Any method call that might change the structure of the RecyclerView or the adapter contents should be postponed to the next frame.
java.lang.IllegalStateException:
at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2403)
at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeInserted(RecyclerView.java:4634)
at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeInserted(RecyclerView.java:10472)
at android.support.v7.widget.RecyclerView$Adapter.notifyItemInserted(RecyclerView.java:6214)
at com.test.film.buzz.BuzzFeedsFragment$5.onPreExecute(BuzzFeedsFragment.java:229)
at com.test.utils.ParallelAsyncTask.executeOnExecutor(ParallelAsyncTask.java:595)
at com.test.utils.ParallelAsyncTask.execute(ParallelAsyncTask.java:543)
at com.test.film.buzz.BuzzFeedsFragment.onLoadMore(BuzzFeedsFragment.java:258)
at com.test.film.buzz.BuzzFeedsFragment$3.onScrolled(BuzzFeedsFragment.java:177)
at android.support.v7.widget.RecyclerView.dispatchOnScrolled(RecyclerView.java:4305)
at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:4463)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
at android.view.Choreographer.doCallbacks(Choreographer.java:683)
at android.view.Choreographer.doFrame(Choreographer.java:616)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
I'm stuck with the weirdest error I've ever got since I started programming. I'm setting a text to a button. Nothing complicated there!
I do it this way:
btnButton.setText(cell.getText());
btnButton exists since I've passed some others things to it before setting its text and cell.getText() is not null either since I can log the text in my console.
Still, I get this weird error:
03-31 14:58:35.407 24398-24398/ca.gggolf.agggolf.full E/AndroidRuntime: FATAL EXCEPTION: main
Process: ca.gggolf.agggolf.full, PID: 24398
java.lang.NullPointerException: Attempt to read from field 'int android.view.ViewGroup$LayoutParams.width' on a null object reference
at android.widget.TextView.checkForRelayout(TextView.java:6836)
at android.widget.TextView.setText(TextView.java:4063)
at android.widget.TextView.setText(TextView.java:3921)
at android.widget.TextView.setText(TextView.java:3896)
at ca.gggolf.agggolf.activities.fragments.AutoGridFragment.displayAutoGridBody(AutoGridFragment.java:467)
at ca.gggolf.agggolf.activities.fragments.AutoGridFragment.createAutogridBody(AutoGridFragment.java:206)
at ca.gggolf.agggolf.activities.fragments.AutoGridFragment.onCreate(AutoGridFragment.java:94)
at android.support.v4.app.Fragment.performCreate(Fragment.java:1939)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1029)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1248)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:738)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1613)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:517)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:700)
Can someone help me fix this? It sometimes work but it depends from where in the application that I come from.
Thanks!
EDIT
Here's my whole code when creating the button. I assign it's width and initial text and then if the text should be truncated, I measure to make sure that my button will still fit in the size I gave it otherwise, I change it to textView.
It's when I set it back to normal after that it gives me the error.
Button btnButton = new Button(getActivity());
btnButton.setWidth(width);
btnButton.setText(cell.getText());
if(adjustedAutogrid.getColumn(indexCell).isTruncated()){
btnButton.setText("…");
btnButton.measure(0, 0);
if(btnButton.getMeasuredWidth() > width){
TextView btnText = new TextView(getActivity());
btnText.setWidth(width);
btnText.setText(cell.getText());
cell.getCSS().applyToTextView(btnText);
btnText.setEllipsize(TextUtils.TruncateAt.END);
btnText.setLines(1);
layout.addView(btnText);
break;
}
btnButton.setText(cell.getText());
btnButton.setEllipsize(TextUtils.TruncateAt.END);
btnButton.setLines(1);
}
And for those wondering from where it causes problem, well it's a bit complicated. I receive information through internet (an XML) that I convert to a table that I show. I must set the different cells the appropriate size and content. When I load what we call an AutoGrid coming from the XML, all my AutoGrid works except for one...
Edit 2
Everything works fine if I leave the text to the ellipsize symbol (…). It really is when i try to set the text again that it doesn't work..
I don't know why but it seems that if I set the text later with my button that everything is fine...
btnButton.setWidth(width);
btnButton.setEllipsize(TextUtils.TruncateAt.END);
btnButton.setLines(1);
btnButton.setText(cell.getText()); // this line used to be the first one
If someone has an explanation, please fill free to enlighten me!