Skip to content

Commit e7eb798

Browse files
authored
Add Scene method to force synchronize compose state (JetBrains#2952)
This method is designed to address the issue of synchronising the state of text fields between Compose and the iOS text input service. The iOS keyboard API expectes all changes to be applied immediately, but this is not the case for Compose, where all changes are first stored in the state and updated during recomposition. In order to resolve various issues that arise during text input, the Compose state must be synchronised with the text input to provide an accurate result after each editing command. ## Release Notes N/A
1 parent debecae commit e7eb798

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/scene/BaseComposeScene.skiko.kt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ internal abstract class BaseComposeScene(
155155
recomposer.performScheduledRecomposerTasks()
156156
}
157157

158+
override fun recomposeAndLayout(nanoTime: Long) {
159+
if (isClosed) return
160+
postponeInvalidation("BaseComposeScene:drainPendingWork") {
161+
recompose(nanoTime)
162+
doMeasureAndLayout()
163+
}
164+
}
165+
158166
override fun render(canvas: Canvas, nanoTime: Long) {
159167
// This is a no-op if the scene is closed, this situation can happen if the scene is
160168
// in the list for rendering, but recomposition in another scene from the same list
@@ -164,15 +172,9 @@ internal abstract class BaseComposeScene(
164172

165173
postponeInvalidation("BaseComposeScene:render") {
166174
// We try to run the phases here in the same order Android does.
175+
recompose(nanoTime)
167176

168-
// Flush composition effects (e.g. LaunchedEffect, coroutines launched in
169-
// rememberCoroutineScope()) before everything else
170-
recomposer.performScheduledEffects()
171-
172-
recomposer.performScheduledRecomposerTasks()
173-
frameClock.sendFrame(nanoTime) // withFrameMillis/Nanos and recomposition
174-
175-
doMeasureAndLayout() // Layout
177+
doMeasureAndLayout()
176178

177179
// Schedule synthetic events to be sent after `render` completes
178180
if (inputHandler.needUpdatePointerPosition) {
@@ -294,6 +296,15 @@ internal abstract class BaseComposeScene(
294296
}
295297
}
296298

299+
private fun recompose(nanoTime: Long) {
300+
// Flush composition effects (e.g. LaunchedEffect, coroutines launched in
301+
// rememberCoroutineScope()) before everything else
302+
recomposer.performScheduledEffects()
303+
304+
recomposer.performScheduledRecomposerTasks()
305+
frameClock.sendFrame(nanoTime) // withFrameMillis/Nanos and recomposition
306+
}
307+
297308
protected fun doMeasureAndLayout() {
298309
snapshotInvalidationTracker.onMeasureAndLayout()
299310
measureAndLayout()

compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/scene/ComposeScene.skiko.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ sealed interface ComposeScene : AutoCloseable {
189189
*/
190190
fun render(canvas: Canvas, nanoTime: Long)
191191

192+
/**
193+
* Performs pending recompositions and layout passes without rendering.
194+
*
195+
* @param nanoTime the time to use for animations and any other code that uses [withFrameNanos]
196+
*/
197+
fun recomposeAndLayout(nanoTime: Long)
198+
192199
/**
193200
* Send pointer event to the content.
194201
*

0 commit comments

Comments
 (0)