Skip to content

Defer lightweight Picker traversal editing until dismiss completes#5092

Merged
shai-almog merged 1 commit into
codenameone:masterfrom
jsfan3:codex/defer-lightweight-picker-traversal-edit
May 29, 2026
Merged

Defer lightweight Picker traversal editing until dismiss completes#5092
shai-almog merged 1 commit into
codenameone:masterfrom
jsfan3:codex/defer-lightweight-picker-traversal-edit

Conversation

@jsfan3
Copy link
Copy Markdown
Contributor

@jsfan3 jsfan3 commented May 29, 2026

Summary

  • Defer lightweight Picker previous/next focus transfer until the popup dismiss animation has completed.
  • Start editing the next component from the disposeToTheBottom() completion callback instead of immediately after requesting the dismiss.

Problem

The lightweight Picker supports previous/next traversal buttons. When a traversal button is pressed, endEditing() currently calls dlg.disposeToTheBottom() and then immediately requests focus and calls startEditingAsync() on the next component.

InteractionDialog#disposeToTheBottom() is animated. On Android, startEditingAsync() creates and positions a native EditText overlay for the target TextField. If that happens while the picker popup is still being dismissed and the form content pane has only just been restored from the picker padding/invisible-area adjustment, the native editor can be positioned using stale geometry. In a reproduced Android case, traversing from a lightweight picker to a numeric TextField opened the numeric keyboard but rendered the field value outside of the field.

Fix

For traversal commands that resolve to a next/previous component, this patch passes a completion callback to disposeToTheBottom() and starts editing only after the lightweight picker has finished dismissing. Cancel and Done without traversal continue to dismiss normally.

This keeps the existing traversal behavior, but avoids starting the next native edit session while the outgoing picker popup is still in its dismiss/layout transition.

Verification

  • Reproduced on Android with a lightweight string Picker followed by numeric TextFields and picker.setTraversable(true).
  • Before this patch: pressing the picker down-arrow to reach a numeric text field opens the keyboard and can render the field value outside the field.
  • This patch changes the focus/edit timing so the next editor is created after the picker dismiss callback.
  • Ran git diff --check on the branch.

@jsfan3 jsfan3 force-pushed the codex/defer-lightweight-picker-traversal-edit branch from f39b770 to 30dcb33 Compare May 29, 2026 13:26
@jsfan3
Copy link
Copy Markdown
Contributor Author

jsfan3 commented May 29, 2026

@shai-almog Please wait before accepting this PR; I have a comment to make that I'll post shortly...

@jsfan3
Copy link
Copy Markdown
Contributor Author

jsfan3 commented May 29, 2026

@shai-almog Small clarification about verification status.

This fix is based on a reproduced Android issue and on code-path analysis of the lightweight Picker dismissal flow, but it has not yet been verified by rebuilding Codename One locally with this patch and running the patched framework on a device.

The diagnosis and patch were prepared with assistance from generative AI. The observed behavior is real and reproducible in my app: traversing from a lightweight Picker to a numeric TextField can open the Android native editor with the field value rendered outside of the field. The proposed fix is to defer requestFocus() / startEditingAsync() until the picker dismiss animation has completed, which appears consistent with the suspected race/layout timing issue.

If you think this needs direct validation before review/merge, I can test it properly by building Codename One locally with this patch and running the app against that patched build. That may take some time, but I can do it.

@shai-almog
Copy link
Copy Markdown
Collaborator

@jsfan3 we have CI that checks most of these things and is now pretty thorough (albeit not perfect). The problem is that it's really hard to run that CI on 3rd party PRs... Even when I accept the PR and let CI run some things fail due to the complexity of our CI scripts and security concerns.

Due to that I'd much rather get bug reports than PRs. I reviewed this and it looks safe so I'll merge it and it will run in CI post merging.

@shai-almog shai-almog merged commit 9feaf99 into codenameone:master May 29, 2026
11 of 15 checks passed
shai-almog added a commit that referenced this pull request May 29, 2026
…asserts

PR #5092 routed lightweight Picker next/prev focus transfer through
disposeToTheBottom(Runnable), and Done/Cancel still rely on the
disposeToTheBottom() animation finishing, so the Next/Prev focus
assertions and Done/Cancel "dialog closed" assertions in
testPickerNextPrevButtons can no longer be checked synchronously after
released() + flushEdt().

DisplayTest.flushEdt() by itself does not drive AnimationManager
animations: it only iterates edtLoopImpl while
shouldEDTSleepNoFormAnimation() is false, which is only the case when
there is pending input or serial work. Queueing a no-op callSerially
before each flushEdt forces one edtLoopImpl pass per loop iteration,
which in turn runs Form.repaintAnimations and advances the dispose
animation.

The runAnimations helper now uses that pattern (with a longer 600ms
budget to cover the 400ms dispose animation plus completion-callback
latency), and a new runUntilFocusedOrEditing helper polls until the
target picks up focus or starts editing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit that referenced this pull request May 29, 2026
#5097)

* feat(InteractionDialog): expose animation speed and custom show/dispose setup (#5072)

Allow callers to override the interactionDialogSpeedInt theme constant in
code and to replace the built-in grow/shrink animation with custom
positioning, addressing the request in #5072.

The arrow-border-missing-during-slow-animation note in #5072 is a
side-effect of the default reposition-from-1x1 animation: at the start
of the animation the dialog is too small for the arrow image to render.
The new setShowAnimationSetup docs include a translate-from-edge recipe
that keeps the dialog at full size throughout, so the arrow stays
visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(PickerCoverageTest): drive dispose animation before focus/close asserts

PR #5092 routed lightweight Picker next/prev focus transfer through
disposeToTheBottom(Runnable), and Done/Cancel still rely on the
disposeToTheBottom() animation finishing, so the Next/Prev focus
assertions and Done/Cancel "dialog closed" assertions in
testPickerNextPrevButtons can no longer be checked synchronously after
released() + flushEdt().

DisplayTest.flushEdt() by itself does not drive AnimationManager
animations: it only iterates edtLoopImpl while
shouldEDTSleepNoFormAnimation() is false, which is only the case when
there is pending input or serial work. Queueing a no-op callSerially
before each flushEdt forces one edtLoopImpl pass per loop iteration,
which in turn runs Form.repaintAnimations and advances the dispose
animation.

The runAnimations helper now uses that pattern (with a longer 600ms
budget to cover the 400ms dispose animation plus completion-callback
latency), and a new runUntilFocusedOrEditing helper polls until the
target picks up focus or starts editing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants