Merge "Fix focus transaction issue" into androidx-main
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
index 5c098de..bd71cac 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
@@ -22,7 +22,6 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
-import androidx.compose.ui.focus.FocusStateImpl.Active
import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
import androidx.compose.ui.focus.FocusStateImpl.Inactive
import androidx.compose.ui.input.InputMode.Companion.Keyboard
@@ -92,6 +91,40 @@
}
}
+ @OptIn(ExperimentalComposeUiApi::class)
+ @Test
+ fun reentrantRequestFocus_byCallingRequestFocusWithinOnFocusChanged2() {
+ // Arrange.
+ val (item1, item2) = FocusRequester.createRefs()
+ var (item1Focused, item2Focused) = List(2) { false }
+ rule.setFocusableContent {
+ Box(
+ Modifier
+ .focusRequester(item1)
+ .onFocusChanged {
+ item1Focused = it.isFocused
+ if (item1Focused) item2.requestFocus()
+ }
+ .focusTarget()
+ )
+ Box(
+ Modifier
+ .focusRequester(item2)
+ .onFocusChanged { item2Focused = it.isFocused }
+ .focusTarget()
+ )
+ }
+
+ // Act.
+ rule.runOnIdle { item1.requestFocus() }
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(item1Focused).isFalse()
+ assertThat(item2Focused).isTrue()
+ }
+ }
+
@Test
fun cancelTakeFocus_fromOnFocusChanged() {
// Arrange.
@@ -129,10 +162,8 @@
// Assert.
rule.runOnIdle {
assertThat(focusState1).isEqualTo(Inactive)
- // TODO(b/312524818): When a focus transaction is cancelled, we should re-notify
- // all the focus event modifiers that were called in the previous transaction.
- assertThat(focusState2).isEqualTo(Active) // Should be Inactive.
- assertThat(focusState3).isEqualTo(Active) // Should be Inactive.
+ assertThat(focusState2).isEqualTo(Inactive)
+ assertThat(focusState3).isEqualTo(Inactive)
val root = view as AndroidComposeView
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
index 09e2c47..3666a3e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
@@ -132,11 +132,9 @@
* [FocusTargetNode] was found or if the focus search was cancelled.
*/
override fun takeFocus(focusDirection: FocusDirection, previouslyFocusedRect: Rect?): Boolean {
- return focusTransactionManager.withExistingTransaction {
- focusSearch(focusDirection, previouslyFocusedRect) {
- it.requestFocus(focusDirection) ?: false
- } ?: false
- }
+ return focusSearch(focusDirection, previouslyFocusedRect) {
+ it.requestFocus(focusDirection) ?: false
+ } ?: false
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactionManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactionManager.kt
index 59327a0..ab8d02b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactionManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactionManager.kt
@@ -89,12 +89,13 @@
}
states.clear()
ongoingTransaction = false
+ cancellationListener.clear()
}
private fun cancelTransaction() {
- cancellationListener.forEach { it() }
- cancellationListener.clear()
states.clear()
ongoingTransaction = false
+ cancellationListener.forEach { it() }
+ cancellationListener.clear()
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index 6078b91..24eaca0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -41,7 +41,9 @@
internal fun FocusTargetNode.requestFocus(): Boolean = requestFocus(Enter) ?: false
internal fun FocusTargetNode.requestFocus(focusDirection: FocusDirection): Boolean? {
- return requireTransactionManager().withNewTransaction {
+ return requireTransactionManager().withNewTransaction(
+ onCancelled = { if (node.isAttached) refreshFocusEventNodes() }
+ ) {
when (performCustomRequestFocus(focusDirection)) {
None -> performRequestFocus()
Redirected -> true
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index dc83020..8cbcca3 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -206,10 +206,8 @@
init {
snapshotObserver.startObserving()
root.attach(this)
- focusOwner.focusTransactionManager.withNewTransaction {
- // TODO instead of taking focus here, call this when the owner gets focused.
- focusOwner.takeFocus(Enter, previouslyFocusedRect = null)
- }
+ // TODO instead of taking focus here, call this when the owner gets focused.
+ focusOwner.takeFocus(Enter, previouslyFocusedRect = null)
}
fun dispose() {