material-components / material-components/material-components-android
[NavigationView] Insets listener set on NavigationView header can be called many times per second in an infinite loop
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 17.4k
- Forks
- 3.2k
- PR merge metrics
- No merged PRs in 30d
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(): https://github.com/material-components/material-components-android/blob/889a3910bd9266407bfc2f0fb3abbce3685cad96/lib/java/com/google/android/material/internal/ScrimInsetsFrameLayout.java#L88
These insets are applied directly to the header container in NavigationMenuPresenter: https://github.com/material-components/material-components-android/blob/889a3910bd9266407bfc2f0fb3abbce3685cad96/lib/java/com/google/android/material/internal/NavigationMenuPresenter.java#L469
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue with the linked minimal sample on an Android 10/API 29 device: launch it, open the drawer, and click “request insets” while watching the counter. Start by reading ScrimInsetsFrameLayout.java and NavigationMenuPresenter.java, then compare their inset handling with ViewCompat.setOnApplyWindowInsetsListener(). Done means the header listener no longer loops or runs hundreds of times per second.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, java
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100