-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Description
Description:
If an insets listener is set on NavigationView's header, it is possible to get into a situation when it will start to be called many times per second in an infinite loop. This happens on Android < 11.
Expected behavior: Insets listener is not called hundreds of times per second.
Minimal sample app repro:
Launch the app. Open the left drawer. Click on "request insets" button. Observe counter going up (the counter shows the number of times insets listener was called).
Android API version: 29
Material Library version: 1.13.0
Device: Any Android 10 device
I believe that the cause of the issue is the combination of two factors:
- ScrimInsetsFrameLayout (which NavigationView extends) calls its onInsetsChanged method with insets before consumeSystemWindowInsets():
Line 88 in 889a391
onInsetsChanged(insets);
These insets are applied directly to the header container in NavigationMenuPresenter:
Line 469 in 889a391
| ViewCompat.dispatchApplyWindowInsets(headerLayout, insets); |
However when ScrimInsetsFrameLayout's insets listener return insets after consumeSystemWindowInsets(), these insets (which are now different) are then also applied to the header through ViewGroup's insets dispatching.
- Now the piece that actually causes the loop is in the ViewCompat.setOnApplyWindowInsetsListener(): https://github.com/androidx/androidx/blob/d0264c461cc7e0eee8003ef0d1de0d5ee496a0d0/core/core/src/main/java/androidx/core/view/ViewCompat.java#L5051
What happens is that when insets are applied again (because of requestApplyInsets() or some other reason), insets from ScrimInsetsFrameLayout.onInsetsChanged and from ViewGroup dispatching arrive in pairs and because they are different (the equals() check fails) it causes wrappedUserListener to repeatedly call requestApplyInsets() which created a self-sustaining loop.
I'm not sure why it doesn't happen immediately on application start though.