Native Modules Live at Runtime

A native module can compile perfectly and still be unable to do its job.

That is not a contradiction.

The module may need a foreground Android Activity to open a system-owned surface. The application may declare a framework feature in its build settings while the running process never activates it. Both failures are easy to miss because the source code looks complete.

Mobile integrations do not live in source files alone. They live in the moment an actual process has an actual UI context on an actual device.

An Activity is a runtime dependency

When a React Native Android module needs to show a review sheet, launch an intent, or call any Activity-bound API, the current Activity is not a convenience field to dereference. It is a nullable runtime dependency.

The Activity can be absent while a host is paused, recreated, restored, or simply not foregrounded yet. The JavaScript call may arrive at precisely the inconvenient moment. Naturally. Mobile lifecycle timing has a sense of humor and it rarely shares the punchline.

The module contract should acknowledge that possibility:

live Activity available → perform the UI-bound operation
no live Activity → return a deliberate, recoverable outcome

The second branch is not an edge case to bury under a non-null assertion. It is a normal state the JavaScript caller can handle with a retry, a fallback route, or an explanatory message.

That is why a lifecycle-aware accessor plus an explicit null check is more than a deprecation cleanup. It says the module understands that its UI host can disappear.

A build declaration is not a runtime observation

The same mistake appears at a larger scale when enabling a framework capability.

A config file, dependency scan, or build flag can answer useful questions:

  • Is the capability declared?
  • Do the installed packages claim to support it?
  • Did the build configuration contain the expected setting?

None of those answers proves that this running application has activated the capability.

Caches can preserve an older build. One platform can be configured differently from another. A setting can fail to propagate without leaving a dramatic compile error behind.

That is why runtime evidence deserves its own check. For a React Native example app, the development log from AppRegistry.runApplication, displayed in the Metro terminal, can show the parameters supplied when a surface starts. The native Fabric startup path supplies fabric: true. A concurrentRoot value inside initialProps is supplied by the project, so that value alone does not prove concurrent rendering is active. The app name is incidental. The observed runtime state is the point.

Give every important claim a reader

I find it helpful to name the reader behind a claim.

“The New Architecture is enabled” may have different readers:

  • a manifest inspector;
  • a dependency scanner;
  • a native build log;
  • a development startup log;
  • an in-process runtime probe.

Each reader licenses a different sentence.

A dependency scanner can say that the project appears ready. A runtime log can say that a particular session actually started with a particular renderer. Neither makes the other redundant.

This is not React Native-specific either. Any capability behind a flag has the same split: declaration versus execution. If the behavior matters to users, carry at least one runtime observation alongside the authoring-time check.

The small module that avoids a large crash

The healthy implementation is not complicated.

Treat host access as nullable. Return a controlled result when the host is absent. Make the JavaScript caller decide what a user should see next. Verify the framework capability from a running session, not only from a configuration file.

That is enough to turn an optimistic bridge into a lifecycle-aware one.

Native modules are often described as glue code. Glue is supposed to be invisible.

But when the glue crosses a runtime boundary, it needs a contract. Otherwise the first missing Activity writes the contract for you, usually in a crash report.