Skip to content

Fix Android crash-loop when native events dispatch before WebView is ready#130

Draft
simonhamp wants to merge 1 commit into
mainfrom
geolocation-crash-loop
Draft

Fix Android crash-loop when native events dispatch before WebView is ready#130
simonhamp wants to merge 1 commit into
mainfrom
geolocation-crash-loop

Conversation

@simonhamp
Copy link
Copy Markdown
Member

Summary

A customer report (geolocation plugin, permanently_denied branch) surfaced a crash-loop on Android. The plugin's auto-open-Settings call lets Android kill the app process; on cold boot the geolocation coordinator dispatches a PermissionRequestResult event before the WebView has loaded a NativePHP page. Three things then combine:

  1. NativeActionCoordinator.dispatch fetched a relative URL (/_native/api/events). Against about:blank it fails to parse, logging Failed to parse URL from /_native/api/events.
  2. There was no gate between "native code wants to dispatch" and "WebView has finished loading a NativePHP page" — plugin events fired during cold boot were silently dropped.
  3. MainActivity.injectJavaScript ran document.body.classList.add(...) synchronously after webView.loadUrl(...). Against the cleared document document.body is nullUncaught TypeError: Cannot read properties of null (reading 'classList') → WebView dies → app crashes → cold boot → same race → loop.

Changes

  • NativeActionCoordinator.kt — Added a process-wide readiness flag and event queue on the companion object. dispatch() now queues when the WebView isn't ready, uses the absolute http://127.0.0.1/_native/api/events, and wraps the JS body in try/catch so a broken page state can't throw an uncaught error. New markWebViewReady(activity) / markWebViewNotReady() entry points.
  • WebViewManager.ktonPageFinished calls NativeActionCoordinator.markWebViewReady(...) for 127.0.0.1 URLs, flushing queued events.
  • MainActivity.ktonCreate calls markWebViewNotReady() so cold boots inherit a clean gate. injectJavaScript now checks document.body and re-runs on DOMContentLoaded if the body isn't there yet, eliminating the classList crash.

iOS is intentionally untouched: LaravelBridge.shared.send is only assigned after WebView setup, so it already has implicit gating against the same race.

What this doesn't fix

The geolocation plugin's permanently_denied branch still auto-opens the system Settings activity — that's still the trigger for the process kill, and lives in nativephp/mobile-geolocation. With these changes the cold boot it provokes no longer crash-loops the app, but making that auto-open opt-in remains a reasonable UX change for the plugin.

Test plan

  • Android emulator (API 35): fresh install, call Geolocation::requestPermissions(), deny twice to trigger permanently_denied, return from Settings, confirm the app does not crash-loop.
  • Sanity-check normal page navigation still dispatches native-event and the _native/api/events POST lands (e.g. an alert button press still reaches Laravel).
  • Confirm Logcat shows ⏳ WebView not ready — queueing event followed by ▶️ Replaying N queued event(s) on cold boot when a plugin dispatches early.

🤖 Generated with Claude Code

…ready

Queue plugin event dispatches until the WebView has finished loading a
NativePHP page, use an absolute URL for the events fetch, and guard the
MainActivity JS injection against a null document.body. Together these
prevent the cold-boot crash-loop seen after the system kills the app
(e.g. when a plugin opens system Settings during a permission flow).

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.

1 participant