Eximia Download

Eximia docs

Lifecycle

How OS-level events flow from the system → SLM Eximia runtime → plugins → JS.

The runtime defines a small set of canonical events. Plugins subscribe to the ones they need; JS code listens via SLMEximia.on(event, handler).


Canonical events

EventTriggered byCancellable?Payload
resumeApp returns to foregroundno{ wasInBackgroundMs: number }
pauseApp moves to backgroundnonull
onlineNetwork becomes reachableno`{ type: "wifi"
offlineNetwork becomes unreachablenonull
keyboardShowSoftware keyboard appearsno{ heightPx: number }
keyboardHideSoftware keyboard hidesnonull
backbuttonHardware back pressed (Android only)yesnull
deeplinkApp opened via universal/app linkno{ url: string, source: string }
notificationPush notification tappedno{ payload: object, action?: string }
lowMemoryOS signals low memorynonull

Cancellable events: the handler returns true to cancel the default OS action (e.g. for backbutton, returning true prevents the app from backgrounding).


Subscription on the JS side

SLMEximia.on("pause", () => {
    saveDraft();
});

SLMEximia.on("backbutton", () => {
    if (router.canGoBack()) {
        router.back();
        return true;       // cancel default; don't background the app
    }
    return false;
});

const off = SLMEximia.on("resume", ({ wasInBackgroundMs }) => {
    if (wasInBackgroundMs > 60_000) refreshData();
});
// later
off();   // unsubscribe

Multiple subscribers per event are allowed; they're called in registration order. For cancellable events, any subscriber returning true cancels the default action.


Subscription on the native side (plugins)

Plugins suscribe by overriding lifecycle hooks on SLMEximiaPlugin:

Android (Kotlin):

class SLMCamera : SLMEximiaPlugin() {
    override fun onActivityResult(
        requestCode: Int, resultCode: Int, data: Intent?
    ) {
        // resume the pending callback
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults: IntArray
    ) { ... }
}

iOS (Swift):

class SLMCamera: SLMEximiaPlugin {
    override func onResume() { ... }
    override func onDeepLink(url: URL) { ... }
}

The full set of overridable hooks is in ../android/docs/SLMEximiaPlugin.md and ../ios/docs/SLMEximiaPlugin.md.


How an OS event reaches a plugin

1. OS signals event (e.g. Activity.onPause, viewWillDisappear)
2. SLMEximiaActivity / SLMEximiaViewController catches it
3. SLMEximiaLifecycle dispatches to all subscribed plugins, in registration order
4. Each plugin runs its hook
5. SLMEximiaLifecycle then emits an event envelope to JS via the bridge
6. JS subscribers fire

Plugins are called before JS. This is intentional: a plugin (e.g. a session manager) can mutate global state that JS handlers then observe in the same dispatch cycle.


Android: how OS callbacks map

SLMEximia eventActivity callback
resumeonResume() (after onStart if app was stopped)
pauseonPause()
online / offlineConnectivityManager.NetworkCallback registered by the runtime
keyboardShow / keyboardHideViewCompat.setOnApplyWindowInsetsListener on the root view, observing IME insets
backbuttonOnBackPressedDispatcher callback
deeplinkonNewIntent(intent) when intent has Intent.ACTION_VIEW
notificationIntent extras when the activity is launched via a PendingIntent from a notification
lowMemoryonTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL) and friends

In addition, the runtime forwards onActivityResult and onRequestPermissionsResult to plugins (these are not JS-visible — they're delivery mechanisms for in-flight plugin calls).

iOS: how OS callbacks map

SLMEximia eventiOS source
resumeapplicationDidBecomeActive / sceneDidBecomeActive
pauseapplicationWillResignActive / sceneWillResignActive
online / offlineNWPathMonitor started by the runtime
keyboardShow / keyboardHideUIResponder.keyboardWillShowNotification etc.
backbuttonnot applicable — no event emitted on iOS
deeplinkscene(_:openURLContexts:) or application(_:continue:restorationHandler:)
notificationuserNotificationCenter(_:didReceive:withCompletionHandler:)
lowMemoryUIApplicationMain memory warning notification

Ordering and timing

  • All native plugin hooks for a given event run synchronously on the main thread before the JS event envelope is emitted.
  • If a plugin hook is suspend / async, it runs on its dispatcher; the runtime awaits all subscribed hooks before emitting to JS.
  • JS subscribers run on the WebView's main thread (same as DOM).

A misbehaving plugin that blocks the main thread for >100ms in a lifecycle hook will trigger an SLMEximiaLifecycleSlow warning in debug builds and a logged warning in release.


Testing

Each lifecycle event has an instrumentation test that:

  1. Subscribes a fixture plugin and a JS handler.
  2. Triggers the OS event (or its closest simulation — e.g. moving the activity to background via Lifecycle.State).
  3. Asserts both the plugin hook and the JS handler ran, in the right order, with the right payload.