Fixed
Status Update
Comments
ae...@google.com <ae...@google.com>
ap...@google.com <ap...@google.com> #2
Project: platform/frameworks/support
Branch: androidx-main
commit 687ecbe5adef51e74eb19181e81c266c9c932697
Author: Ralston Da Silva <ralu@google.com>
Date: Wed Mar 29 17:03:03 2023
Fix 2D focus search perf issue
2D focus search triggers a beyond bounds layout
for items within a lazylist. It does this via a
composition local. Focus search can't distinguish
between the focus parent that is part of a lazylist
and other intermediate focus parents. This results
in a situation where nested focusables in a lazylist
trigger unnecessary beyond bounds layouts.
Fix: 269627309
Test: ./gradlew compose:foundation:foundation:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveCompositionCountTest
Change-Id: I56091234a6bb6cf141e5faa1755556fc0336fa1c
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
https://android-review.googlesource.com/2514698
Branch: androidx-main
commit 687ecbe5adef51e74eb19181e81c266c9c932697
Author: Ralston Da Silva <ralu@google.com>
Date: Wed Mar 29 17:03:03 2023
Fix 2D focus search perf issue
2D focus search triggers a beyond bounds layout
for items within a lazylist. It does this via a
composition local. Focus search can't distinguish
between the focus parent that is part of a lazylist
and other intermediate focus parents. This results
in a situation where nested focusables in a lazylist
trigger unnecessary beyond bounds layouts.
Fix: 269627309
Test: ./gradlew compose:foundation:foundation:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveCompositionCountTest
Change-Id: I56091234a6bb6cf141e5faa1755556fc0336fa1c
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveCompositionCountTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
Description
Jetpack Compose version: 1.3.0-alpha03, 1.4.0-alpha01, 1.4.0-alpha02
Jetpack Compose component used: LazyList
Android Studio Build: #AI-221.6008.13.2211.9514443
Kotlin version: 1.7.20
When navigating between focus targets inside a LazyList, the focus search algorithm incorrectly loads all content before moving focus if there are nested focus target modifiers in the tree. This can lead to delays in the magnitude of tens of seconds, depending on the complexity and amount of content to load.
Original discussion of this bug is in b/184670295 .
The performance degradation is caused by a bug in the behaviour of
twoDimensionalFocusSearch/generateAndSearchChildren
. Here's an outline of how I believe it happens:Assume a layout/node hierarchy
ActiveParent
, root focus modifierActiveParent
, .scrollable -> .focusGroup -> .focusTarget of the lazy listActiveParent
, any intermediate focus target nodeActive
, the element that actually has focus when beginning the searchRoot.twoDimensionalFocusSearch
callsF0.twoDimensionalFocusSearch
.F0.twoDimensionalFocusSearch
callsF1.twoDimensionalFocusSearch
.F1.twoDimensionalFocusSearch
callsF1.generateAndSearchChildren
which callsF1.searchBeyoundBounds
.F1.searchBeyoundBounds
will compose more children of L, say F3, which is now a sibling of F1 and a child of F0.F1.searchChildren
will not find the newly added F3 (because it is not its child), causing L to load the next interval.This will repeat until all intervals are loaded. <-- This causes the performance hit
F1.twoDimensionalFocusSearch
will callF0.generateAndSearchChildren
, which will find F3.Relevant implementation in TwoDimensionalFocusSearch.kt
I am not entirely familiar with the focus node system, and it has been rewritten in 1.4.0 (I am still using 1.3.0), but I would assume this same bug is still present there as well, as on a brief check of the source the algorithm implementation looks similar.
1.3.0 used a
ResetFocusModifierLocals
to reset composition locals, but this doesn't seem to exist any more in 1.4.0. I believe one trivial solution to this would be to resetModifierLocalBeyondBoundsLayout
as well. This would not allow F1 or any other children to load more children of L. However, with the new node system in 1.4.0 the implementation seems to have moved away from using composition locals, so theModifierLocalBeyondBoundsLayout
will probably be refactored soon as well?As a temporary workaround, this bug can be circumvented by do not nesting focus target modifiers inside a lazy layout.