Material-components-android: [MaterialContainerTransform] IllegalStateException: "End view bounds must not be null, make sure the end view is laid out and measured"

Created on 4 Jun 2020  路  17Comments  路  Source: material-components/material-components-android

I am not sure if this should be a question or a bug report, but after updating to material 1.2.0-beta01 I am starting to see random crashes in production when using MaterialContainerTransform as a sharedelementreturntransition. Same issue is there in the 1.3.0 alpha1.

I cannot reproduce this on any of my devices, but in production there is a 5% crash rate.

StackTrace:

Fatal Exception: java.lang.IllegalStateException: End view bounds must not be null, make sure the end view is laid out and measured
       at com.google.android.material.transition.MaterialContainerTransform.createAnimator(MaterialContainerTransform.java:889)
       at androidx.transition.Transition.createAnimators(Transition.java:747)
       at androidx.transition.TransitionSet.createAnimators(TransitionSet.java:480)
       at androidx.transition.TransitionSet.createAnimators(TransitionSet.java:480)
       at androidx.transition.Transition.playTransition(Transition.java:1821)
       at androidx.transition.TransitionManager$MultiListener.onPreDraw(TransitionManager.java:301)
       at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1102)
       at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3310)
       at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2200)
       at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9065)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:999)
       at android.view.Choreographer.doCallbacks(Choreographer.java:797)
       at android.view.Choreographer.doFrame(Choreographer.java:732)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:984)
       at android.os.Handler.handleCallback(Handler.java:883)
       at android.os.Handler.dispatchMessage(Handler.java:100)
       at android.os.Looper.loop(Looper.java:237)
       at android.app.ActivityThread.main(ActivityThread.java:8016)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)

How can I make sure that the end view is laid out and measured?

bug

All 17 comments

Turns out the error message was absolutely correct. I was actually not showing the return view in a particular state. Closing this issue.

I am reopening this issue again, since I have seen it in production even after fixing the obvious cases.

Isn't an IllegalStateException a bit harsh in this case? Returning null instead would cause the animation not to run instead of crashing, right?

Is there any way to detect that the end view is not laid out and measured before the transition is being executed and causing the crash?

The error message is: End view bounds must not be null, make sure the end view is laid out and measured.

The message itself is clear, but how can I make sure the end view is laid out and measured?

I think we wanted this to be a crash because it implies that something is misconfigured and should be addressed by the developer.

Do you have any more information about the setup of the view(s) related to the crash? It would be good to understand why this might be happening.

The thing is that the state of the second view may lead to that the view used in the original MaterialContainerTransform isn't valid anymore. I perfectly understand why it happens, but I find that it sometimes is a bit hard to know the state of the view that I will return to.

Is there a callback that could be used to change the target view of the material containertransform after both fragments have been laid out? I want to be able to either change the target or to abort the transition so I can avoid the crashes.

Can you easily reproduce the scenario locally? Maybe you can try adding an OnLayoutChangeListener or OnGlobalLayoutListener to the root view of your second fragment, and then use MaterialContainerTransform#setEndView to change the "target" view?

However, it would have to be done before the Transitions framework calls captureEndValues on our MaterialContainerTransform.

If changing the end view doesn't work then maybe you can try calling MaterialContainerTransform#cancel if you detect the scenario in the layout listener.

Thank you! I鈥檒l try the suggestions and Post the results here...

@sipersso Any update on this? I've a recyclerview with Paging 3.0 library. If I scroll very fast and click one of the items, i get the same exact exception.

@teyyihan I think I managed to resolve it for my app by detecting the state before backing out and changing the transition to match. That being said, still I think the crash is way too harsh. I'd much prefer if this was a warning instead of an IllegalStateException. Sometimes it just isn't possible to know the state of the screen you are returning to.

I didn't get any help from the OnLayoutChangeListener and OnGlobalLayoutListener callbacks. These are being called multiple times so it is hard to know if they are done as a result of a back button press. Maybe @dsn5ft has some more guidance on how I was supposed to use those methods for detecting an invalid transition?

A common scenario:
1: Open a detail view for a list item with a MaterialContainerTransform.
2: Delete the detail item and navigate back
If you do this, the detail item won't exist in the list anymore and the app would crash. How would I use OnLayoutChangeListener or OnGlobalLayoutListener here to detect that the list item will no longer exist when I return?

I am getting this exception while navigating TO another fragment from recyclerview list item with navigation comp. I was able to reproduce this issue and it's a really weird bug. I uploaded a video while getting this issue.

I think it's not about end view state in my case. The recyclerview item that produces bug is not fully rendered on the screen.

Youtube link: URL

I was wrong. I was using motionlayout to hide/show my appbarlayout. And recyclerview's top is constrainted to bottom of appbarlayout. If you look at the end of the video very carefully, you can see that my appbarlayout haven't finished its transition when i clicked on one of my list item views. And i think that was the reason of my issue. I replaced motionlayout with constraintlayout and i don't get this exception anymore.

If you don't want to replace motionlayout with constarintlayout you can manually finish the transition or wait until the transition completed. Then you can navigate to target view/fragment/activity.

One month later and I am still not able to fully resolve this issue. There are some states where I just can't guarantee that the return view will be there when I back out from the fragment. Is there any way to just prevent it from crashing?

The class is final, which means I can't override it. I can't copy it either since it is using restricted classes that I can't access. I don't understand how OnGlobalLayoutListener or OnLayoutChangeListener would help.

Any advice?

I'm considering switching the exception to a log warning, but I'm still not sure it's the right decision. @teyyihan's situation, and your initial situation of "not showing the return view in a particular state", show how this exception could help developers find out about cases where some views are misconfigured or not yet ready for the transition. If we switch to a log warning, it's less likely developers will notice it when working locally, and much less likely in QA or production builds.

Regarding the "can't guarantee that the return view will be there when I back out from the fragment", it seems this crash would only happen when the return view is actually there, but just doesn't have a size yet. Now that I think about this more, I wonder if you can do the following in your first fragment to delay the return transition until the target view is measured and laid out:

postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }

https://github.com/android/architecture-components-samples/issues/495#issuecomment-488364003

I'm considering switching the exception to a log warning, but I'm still not sure it's the right decision. @teyyihan's situation, and your initial situation of "not showing the return view in a particular state", show how this exception could help developers find out about cases where some views are misconfigured or not yet ready for the transition. If we switch to a log warning, it's less likely developers will notice it when working locally, and much less likely in QA or production builds.

Regarding the "can't guarantee that the return view will be there when I back out from the fragment", it seems this crash would only happen when the return view is actually there, but just doesn't have a size yet. Now that I think about this more, I wonder if you can do the following in your first fragment to delay the return transition until the target view is measured and laid out:

postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }

android/architecture-components-samples#495 (comment)

Thanks for the feedback. However, I am already using the postponeEnterTransition() and view.doOnPreDraw { startPostoneEnterTransition() } in my returning fragment. This doesn't have any effect on the crash.

Can you create a minimal sample app that reproduces the issue? That way I could help debug and look for solutions or workarounds.

I have been trying to reproduce the issue for a month now without success, so I think producing a sample would be difficult.

There were some obvious errors in the code where the return view was not visible, but this has been fixed and because of this, crash rate is down, but it is still around 1%. I don't see any obvious reasons now why it would crash, but on the other hand I think it is a bit dangerous to have a return transition that crashes depending on the view state of the view that it is returning to. What if the state changed and the target view isn't visible? Every time this happens for any reason, would result in a crash.

Ok I've looked into this a bit more considering the androidx/framework ChangeBounds transition, which is comparable to MaterialContainerTransform. Whether intentionally or not, it seems like ChangeBounds will short-circuit before accessing null bounds objects, because of this view parent check and the fact that the parent property is only set when non-null bounds are set.

So I think it makes sense to be consistent with that behavior, even if it means some developers won't find out about this potential misconfiguration. Assuming we don't find any other reasoning, I will change our IllegalStateException to be an early return with a log warning.

Thanks for looking into this!

Was this page helpful?
0 / 5 - 0 ratings