Skip to content

fix(android): app crashes if the phone rotates while it's starting - #525

Merged
simonhamp merged 2 commits into
NativePHP:mainfrom
SRWieZ:fix/android-native-event-before-bridge-loads
Oct 26, 2026
Merged

simonhamp merged 2 commits into
NativePHP:mainfrom
SRWieZ:fix/android-native-event-before-bridge-loads

Conversation

@SRWieZ

@SRWieZ SRWieZ commented Oct 3, 2026

Copy link
Copy Markdown
Member

What's wrong

On Android, if the phone rotates (or switches dark mode) in the first moments after launch, the app crashes:

java.lang.UnsatisfiedLinkError: No implementation found for void
  com.nativephp.mobile.ui.nativerender.NativeElementBridge.nativeElementWriteEvent(int, int, int, byte[])
  at NativeElementBridge$Companion.sendNativeEvent(NativeElementBridge.kt:873)
  at MainActivity.sendOrientationChanged(MainActivity.kt:513)
  at MainActivity.onConfigurationChanged(MainActivity.kt:508)

Seen on a Xiaomi running Android 16, 1.5 s after launch. It depends on timing, so it's hard to trigger on purpose: turn the phone right as the app opens.

Why

sendNativeEvent() calls a native (C) function. That function only exists once the PHP runtime has loaded, and the runtime loads lazily, a moment after the screen appears. A rotation or dark-mode change that arrives before that point calls a function that isn't there yet.

What this does

If the runtime isn't loaded yet, the event is dropped (with a log line) instead of crashing. Nothing is lost: PHP reads the orientation and appearance itself when it starts. One file, iOS untouched.

…runtime loads

NativeElementBridge.sendNativeEvent called nativeElementWriteEvent
unguarded, but the JNI library only loads (and registers that method)
lazily with the PHP runtime. A configuration change in the first moments
of launch — a rotation, a dark-mode flip — reached onConfigurationChanged
first and crashed the app with UnsatisfiedLinkError (seen on a Xiaomi
running Android 16, 1.5 s after launch). The event is now dropped instead;
PHP reads orientation and appearance fresh when it boots.
@simonhamp

Copy link
Copy Markdown
Member

Does it need to return if it fails? Can't we just log it and continue as we were before?

…available

Handle UnsatisfiedLinkError in postNativeEventToQueue and return false only from the queue helper. Keep sendNativeEvent's independent web delivery and internal-event filtering unchanged, and include the underlying linkage error in the warning.
@SRWieZ

SRWieZ commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

You’re right.

I’ve moved the catch into postNativeEventToQueue().

Updated against main and pushed.

@simonhamp
simonhamp merged commit 9a21e08 into NativePHP:main Oct 26, 2026
5 checks passed
gwleuverink added a commit that referenced this pull request Nov 2, 2026
Covers mobile PRs #525, #527, #528, #560, #605, #608, #609, #611, #613, #614, #615, #616, #617, #619, #621, #622, #623, #624, #625, #626, #627, #628, #629, #632, #633, #634, #635 and #636, and the commits on main without a PR: 4082538 (bounded()'s docblock), 49528ba (the Android logging fix from a fork), b23aba9, 2a773cc and b9316b6 (security policy, issue template and README links). Main b107ed9..767a7c8, 33 commits, 4.6.1 among them.

Ported to core on branch sync/mobile-main-2026-11-01, which this merge needs: #608 (scrollVirtualListTo()), #625 with 4082538 (snap points on a gesture area), #621 (:context-menu on a pressable, matched through core's ConvertsToElement as :menu is), #617's farewell frame through a hook, and #614's ValidatesInput and FieldErrors, which move to core. The old names of those two resolve to core's through the alias shim.

Kept on this branch: #616 in full. EVENT_SYSTEM_BACK sits on NativeComponent next to EVENT_HOT_RELOAD and EVENT_SHUTDOWN, the router half is in NativeRouter, and InteractsWithAppShell's back button handler checks the constant, as main's runLoop() does. #617's element types: NativeComponent answers core's typesClosedInFarewellFrame() hook with bottom_sheet and modal. BroadcastsGlobally extends core's marker and #609's DispatchableFromNative.

Removed again in the merge: HasVirtualListWindow, GestureArea and Pressable (main changed them, core has them), and ValidatesInput and FieldErrors (main added them, core has them). Main's import of the old dispatcher name came out of DispatchEventFromAppController, where it clashed with core's.

Adapted for the extraction: BroadcastsGloballyNamesTest expects DispatchableFromNative among the old marker's interfaces, as #609 makes it.

#609 is merged as main has it and is not ported.

Needs NativePHP/core#18.

Tests, all on PHP 8.4.27, with dev dependencies updated for #628 (Laravel 12.70.2, Pest 3.8.7): mobile on this merge against the core sync branch (445049b), 2,467 passed, 4 risky, 4 skipped. PHPStan clean. Mobile main (767a7c8), 2,412 passed, 4 risky, 4 skipped. Desktop main (fd40580) against the core sync branch, test:php 2,240 passed, as against core main. Desktop #57 (977a2af), 2,245 passed against the earlier core sync head (59c73fc), which differs only by the constant moved to mobile; not rerun.
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