Open donpwilson opened 7 years ago
Thanks for reporting!
Is it possible to share your piece of code that may be relevant to reproduce it?
Here's what I hope is the most relevant code. My adapter is full of what I think is irrelevant code so I have cut it down below to what I think is relevant. I've also cut down the photo_item layout substantially. You will see that I set the size of each photo in the imageView so that its height matches the grid side and its width respects its aspect ratio. I should perhaps add that I don't have any issues with the other layout managers. I hope this helps:
Code to initialize:
private void setGridLayoutManager() {
int type = My_Preferences.getInstance().getGridLayout();
switch (type) {
case 0:
pGridLayoutManager = new StaggeredGridLayoutManager(2, GridLayoutManager.VERTICAL);
pCurrentLayoutManagerType = GRID_TYPE.STAGGERED_GRID;
break;
case 1:
pGridLayoutManager = new GridLayoutManager(getActivity(), 2, GridLayoutManager.VERTICAL, false);
pCurrentLayoutManagerType = VERTICAL_LINEAR_GRID;
break;
case 2:
pGridLayoutManager = new GridLayoutManager(getActivity(), 2, GridLayoutManager.VERTICAL, false);
pCurrentLayoutManagerType = VERTICAL_GRID_OF_SQUARES;
break;
case 3:
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
layoutManager.setFlexWrap(FlexWrap.WRAP);
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setAlignItems(AlignItems.STRETCH);
layoutManager.setJustifyContent(JustifyContent.CENTER);
pGridLayoutManager = layoutManager;
pCurrentLayoutManagerType = VERTICAL_FILLED_ROWS;
break;
default:
pGridLayoutManager = new GridLayoutManager(getActivity(), 2, GridLayoutManager.VERTICAL, false);
pCurrentLayoutManagerType = VERTICAL_LINEAR_GRID;
break;
}
pGridGallery.setLayoutManager(pGridLayoutManager);
}
Code to reset grid side:
pAdapter.setGridSide(grid_side);
Cut down adapter:
class Adapter_Photo_Album_Copy extends RecyclerView.Adapter<Adapter_Photo_Album_Copy.ViewHolder> {
private Fragment_Photo_Album.GRID_TYPE pLayoutType;
private int pGridSide;
@Override
public int getItemCount() {
return ....
}
Photo_Record getItem(final int position) {
Photo_Record record = null;
return....
}
void setGridSide(final int side) {
if (side != pGridSide) {
pGridSide = side;
if (getItemCount() > 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
notifyItemRangeChanged(0, getItemCount());
}
});
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.photo_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
if (position != RecyclerView.NO_POSITION) {
final Photo_Record photo_record = getItem(position);
if (photo_record != null) {
final Source_Record source_record = photo_record.getSourceRecord();
if (source_record != null) {
final String photo = photo_record.getPhoto();
Point dimensions = photo_record.getDimensionsAdjustedForOrientation();
int grid_item_height;
int grid_item_width;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewHolder.getImageView().getLayoutParams();
if (pLayoutType == Fragment_Photo_Album.GRID_TYPE.HORIZONTAL_SINGLE_ROW) {
//....
} else if (pLayoutType == Fragment_Photo_Album.GRID_TYPE.VERTICAL_FILLED_ROWS) {
grid_item_height = pGridSide;
// adjusts for borders
grid_item_width = (int) ((double) (grid_item_height - My_Utils.dpToPx(2)) * (double) dimensions.x /
(double) dimensions.y) + My_Utils.dpToPx(2);
} else {
//....
}
params.width = grid_item_width;
params.height = grid_item_height;
viewHolder.getImageView().setLayoutParams(params);
if (pLayoutType == Fragment_Photo_Album.GRID_TYPE.VERTICAL_GRID_OF_SQUARES) {
viewHolder.getImageView().setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
viewHolder.getImageView().setScaleType(ImageView.ScaleType.FIT_CENTER);
}
if ((grid_item_width > My_Utils.dpToPx(2)) && (grid_item_height > My_Utils.dpToPx(2))) {
//......
}
}
}
}
}
class ViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout layout;
private final ImageView imageView;
ViewHolder(View v) {
super(v);
layout = (LinearLayout) v.findViewById(R.id.photo_item);
imageView = (ImageView) v.findViewById(R.id.photo_image);
}
LinearLayout getLayoutView() {
return layout;
}
ImageView getImageView() {
return imageView;
}
}
}
Layout:
<LinearLayout
android:id="@+id/photo_item"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/photo_border"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/photo_background">
<ImageView
android:id="@+id/photo_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/app_name"
android:gravity="center"
android:scaleType="fitCenter"/>
</FrameLayout>
</LinearLayout>
Thanks for pasting the code.
How do you zoom in the RecyclerView when the button is clicked?
I just calculate a new value and feed it to the adapter through setGridSide(). As you can see above this runs notifyItemRangeChanged() on all the items in the adapter. onBindViewHolder() then picks up the revised height and recalculates the size of each photo.
Sorry for the delayed reply.
I couldn't still figure out the root cause of this issue. By any chance is it possible to create a sample project that I can build and debug?
Also there is a new version of 0.3.0-alpha3 with bunch of bug fixes. So it doesn't hurt if you can try it and see if it's fixed.
Thanks.
I've already tried 0.3.0-alpha3 and the problem still exists.
It might take me a little while but I'll try to set something up, maybe using your cats sample app as a base.
Thanks. Really appreciate it.
I've set up zoom in / out on the cats sample ad it worked fine. So I looked harder at the crash. I noticed that it appears to be to do with the animation of views, specifically as they are removed from view. I switched off this animation in my app and it no longer crashes. However it then does not properly refresh until I scroll up or down.
I tried to clear animations on view detach from window but with no effect. See http://stackoverflow.com/questions/26724964/how-to-animate-recyclerview-items-when-they-appear
Does this help at all?
Thanks! It's very helpful. Is it possible to see your forked version of the cat demo app? I'd like to try and see the code 👍
How best can I send you the code. Email the zipped file? Or, I've never used gist but you could start me off?
Sure, either of creating a GitHub repository, or attaching a zipped file to this issue are convenient to see the file. Thanks in advance :)
Here's a zipped file. You will find that I haven't changed very much. Feel free to incorporate in an enhanced demo if you wish. demo-cat-gallery.zip
Thank you! I was able to launch the app, but what operations are needed to reproduce? I tried several operations including "zoom in" and "zoom out" including pushing the bottom image out of the boundary by tapping "zoom in", but couldn't reproduce it so far.
Sorry about the confusion. The cat app doesn't crash. When I said that I looked further at the crash I meant the crash in my app - I included the logcat at the top of this correspondence where you can see how it appears to be related to the animation of views.
Okay, so is it possible to see your app code in a way I can debug by any chance? It's really hard to debug from only the Logcat for this issue.
I appreciate the difficulty. However, I have some good news. I have been continuing to investigate and experiment and have found a way to avoid crashes. To make sure that I get the animations I was using:
notifyItemRangeChanged(0, getItemCount())
but have changed to use:
notifyDataSetChanged()
My app no longer crashes on zoom in, presumably because the way that changes are processed is different. I still get animated changes because I have set:
setHasStableIds(true)
I still think there is an issue somewhere because I still get the:
requestLayout() improperly called
message.
In my researches I came across the following where another recyclerView layout manager developer was experiencing a similar issue. It's possible, if you haven't seen it before, that the discussion may give you some pointers. https://issuetracker.google.com/issues/37098293
I would be happy to let you have access to my app code but in view of the above I suggest the following.
How does that sound?
Thanks for the pointer, it's really useful.
Just to make sure, if you use the built-in layout managers (such as LinearLayoutManager, StaggeredGridLayoutManager), the app doesn't crash, right?
If so, it's likely the issue is on FlexboxLayoutManager, so I want to keep this issue open. But given that there are other outstanding bugs and enhancements that are likely to be experienced/used more often, I'd choose the option 3.
Glad to be of help!
Yes, the built in layout managers do not crash and do not have the request layout warning.
Don't hesitate to come back to me if or when you want.
same issue is here. https://github.com/onlymash/Flexbooru
2019-01-31 02:47:39.883 10254-10254/onlymash.flexbooru E/AndroidRuntime: FATAL EXCEPTION: main
Process: onlymash.flexbooru, PID: 10254
java.lang.IllegalArgumentException: Tmp detached view should be removed from RecyclerView before it can be recycled: ViewHolder{91a68b4 position=7 id=-1, oldPos=-1, pLpos:-1 tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{c0a257e VFED.V... ........ 0,0-1080,1584 #7f08008c app:id/list}, adapter:onlymash.flexbooru.ui.adapter.PostDanAdapter@5328adf, layout:com.google.android.flexbox.FlexboxLayoutManager@44f432c, context:onlymash.flexbooru.ui.MainActivity@6dce1ec
at androidx.recyclerview.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:6165)
at androidx.recyclerview.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1451)
at androidx.recyclerview.widget.RecyclerView$ItemAnimatorRestoreListener.onAnimationFinished(RecyclerView.java:12454)
at androidx.recyclerview.widget.RecyclerView$ItemAnimator.dispatchAnimationFinished(RecyclerView.java:12954)
at androidx.recyclerview.widget.SimpleItemAnimator.dispatchRemoveFinished(SimpleItemAnimator.java:277)
at androidx.recyclerview.widget.DefaultItemAnimator$4.onAnimationEnd(DefaultItemAnimator.java:213)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1122)
at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:552)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1232)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1474)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.access$100(AnimationHandler.java:37)
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:947)
at android.view.Choreographer.doCallbacks(Choreographer.java:761)
at android.view.Choreographer.doFrame(Choreographer.java:693)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
private fun init() {
postViewModel = getPostViewModel(app.serviceLocator.getRepository())
glide = GlideApp.with(this)
val flexboxLayoutManager = FlexboxLayoutManager(requireContext()).apply {
flexWrap = FlexWrap.WRAP
flexDirection = FlexDirection.ROW
alignItems = AlignItems.STRETCH
}
list.layoutManager = flexboxLayoutManager
list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> glide.resumeRequests()
else -> glide.pauseRequests()
}
}
})
initPostDanAdapter()
}
private fun initPostDanAdapter() {
val postDanAdapter = PostDanAdapter(glide, requireActivity())
list.adapter = postDanAdapter
postViewModel.postsDan.observe(this, Observer<PagedList<PostDan>> { posts ->
postDanAdapter.submitList(posts)
})
initSwipeToRefreshDan()
}
class PostDanAdapter(private val glide: GlideRequests,
private val activity: Activity): PagedListAdapter<PostDan, RecyclerView.ViewHolder>(POST_COMPARATOR) {
companion object {
val POST_COMPARATOR = object : DiffUtil.ItemCallback<PostDan>() {
override fun areContentsTheSame(oldItem: PostDan, newItem: PostDan): Boolean =
oldItem == newItem
override fun areItemsTheSame(oldItem: PostDan, newItem: PostDan): Boolean {
return oldItem.id == newItem.id
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
PostDanViewHolder.create(parent, glide, activity)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as PostDanViewHolder).bind(getItem(position))
}
}
class PostDanViewHolder(itemView: View,
private val glide: GlideRequests,
private val activity: Activity): RecyclerView.ViewHolder(itemView){
companion object {
fun create(parent: ViewGroup, glide: GlideRequests, activity: Activity): PostDanViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.post_item, parent, false)
return PostDanViewHolder(view, glide, activity)
}
}
private val preview: ImageView = itemView.findViewById(R.id.preview)
private var postDan: PostDan? = null
fun bind(post: PostDan?) {
postDan = post
if (post is PostDan && !post.preview_file_url.isNullOrEmpty()) {
val lp = preview.layoutParams
if (lp is FlexboxLayoutManager.LayoutParams) {
lp.flexGrow = 1f
}
val placeholder = when (post.rating) {
"s" -> R.drawable.background_rating_s
"q" -> R.drawable.background_rating_q
else -> R.drawable.background_rating_e
}
lp.width = lp.height * post.image_width/post.image_height
glide.load(FlexGlideUrl(post.preview_file_url))
.placeholder(activity.resources.getDrawable(placeholder, activity.theme))
.centerCrop()
.into(preview)
}
}
}
For me it does crash with androidx.recyclerview.widget.LinearLayoutManager.
In my case it has something to do with the combination of TransitionManager.beginDelayedTransition, adapter.setHasStableIds(true) and adapter.notifyDataSetChanged().
I’m seeing this crash as well.
The crash does not occur for me if I use LinearLayoutManager
, or if I disable item animations on the RecyclerView
altogether using RecyclerView#setItemAnimator(null)
. Setting stable IDs did not help.
I'll add that I'm using a RecyclerView
to display search results as the user types; the FlexboxLayoutManager
here is constantly updating item views as new search results are returned.
java.lang.IllegalArgumentException: Tmp detached view should be removed from RecyclerView before it can be recycled: HeaderViewHolder{7c84648 position=22 id=-1, oldPos=-1, pLpos:-1 tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{c92a72b VFED..... ......ID 0,0-1080,1337 #7f080163 app:id/sound_recycler}, adapter:com.testapp.android.ui.recycler.SearchResultsListAdapter@fa27788, layout:com.google.android.flexbox.FlexboxLayoutManager@3c88521, context:com.testapp.android.MainActivity@6a2f083
at androidx.recyclerview.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:6439)
at androidx.recyclerview.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1456)
at androidx.recyclerview.widget.RecyclerView$ItemAnimatorRestoreListener.onAnimationFinished(RecyclerView.java:12699)
at androidx.recyclerview.widget.RecyclerView$ItemAnimator.dispatchAnimationFinished(RecyclerView.java:13199)
at androidx.recyclerview.widget.SimpleItemAnimator.dispatchRemoveFinished(SimpleItemAnimator.java:277)
at androidx.recyclerview.widget.DefaultItemAnimator$4.onAnimationEnd(DefaultItemAnimator.java:213)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1111)
at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1242)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1484)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.access$100(AnimationHandler.java:37)
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:964)
at android.view.Choreographer.doCallbacks(Choreographer.java:790)
at android.view.Choreographer.doFrame(Choreographer.java:721)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Do we have any solutions for this now?
Check if my fix helps: https://github.com/androidx/androidx/pull/252
+1
I'm using RecyclerView's ListAdapter (with DiffUtil).
Setting recyclerView.itemAnimator = null
solved the issue.
I have received feedback from a user of my app that allows me to reproduce the problem. My app has a screen where user manages a list of tags. I use RecyclerView with FlexboxLayoutManager, layout horizontal and DiffUtil.ItemCallback. Tags are sorted alphabetically. When user edits a tag and it's new position was a place that's just around the end of the screen, app crashes.
I have been experimenting with "com.google.android:flexbox:0.3.0-alpha2" with a view to adding it to my photo app as an alternative way of displaying photos. I appreciate that this version is not yet fully stable.
I experience a crash in certain circumstances and report it here in the hope that your testing / debugging can resolve it.
I display a grid of photos and have buttons that enable the user to zoom in or out of the grid. These vary to height of each row. I get a crash on zoom in when the bottom photos are pushed out of view. I'm thinking that there is a confusion between being detached and being recycled. This crash also follows a warning message from View's requestLayout(). Here's the log: