Course outline

React Native / Inclusive native systems and release evidence / Lesson 15

Native module contracts and Codegen boundaries

Treat a native capability as a versioned protocol with registration, payload, lifetime, and target-support obligations.

About 4 min readContent reviewed
On this page

Lesson goals

  • A native call crosses several independent boundaries
  • A specification is not a native implementation
  • Validate the application result even across typed interfaces

A native call crosses several independent boundaries

A JavaScript capability can depend on a generated interface, native implementation, registration, build integration, platform permission, and actual device support. A successful type check establishes only part of that path. The reviewed current React Native documentation uses the New Architecture and Turbo Native Module workflow; a project’s framework and native dependencies must support its selected configuration. Do not diagnose every unavailable method as a permission denial. First identify target support, module identity, registration, and protocol shape. Keep the adapter narrow so application policy can be tested with synthetic values without pretending native code exists.

A specification is not a native implementation

The first fragment declares a synthetic percentage capability and requests an optional module from TurboModuleRegistry. get can return null when the module is unavailable, while getEnforcing assumes availability and throws when it is missing. Codegen consumes supported specification shapes to generate integration artifacts, but native implementation and registration still need the compatible project workflow. The course does not run Codegen or create platform source. The name is synthetic and there is no matching module here. Check the selected documentation before extending types, events, or synchronous methods beyond the supported schema.

typescript Illustrative React Native module; requires the declared compatible application host.
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
  readPercent(): Promise<number>;
}
export default TurboModuleRegistry.get<Spec>('NativeLearningPercent');
Expected output
A compatible host resolves the registered module or returns null when unavailable.

This TypeScript specification is illustrative and unexecuted. It supplies no native implementation, generated artifacts, or registration.

Validate the application result even across typed interfaces

The second program accepts an injected reader and maps module absence separately from a valid zero measurement. A malformed numeric result throws a stable protocol failure instead of becoming an attractive default. Native code, plugin upgrades, or mismatched configurations can violate assumptions, so boundary checks remain useful despite a TypeScript signature. The fake reader resolves locally and does not access hardware. A passing adapter test establishes the application mapping only; it cannot establish Codegen compatibility, thread safety, permission, or native lifecycle behavior. Keep those claims separate and retain actual target checks for the selected integration.

javascript Standalone JavaScript model with synthetic local inputs.
async function readPercent(reader) {
  if (reader === null) return {kind: 'unavailable'};
  const value = await reader.readPercent();
  if (!Number.isSafeInteger(value) || value < 0 || value > 100) {
    throw new Error('INVALID_NATIVE_PERCENT');
  }
  return {kind: 'value', value};
}
async function demo() {
  console.log(JSON.stringify(await readPercent(null)));
  console.log(JSON.stringify(await readPercent({readPercent: async () => 0})));
  try { await readPercent({readPercent: async () => 101}); }
  catch (error) { console.log(error.message); }
}
demo().catch(error => { console.error(error.message); process.exitCode = 1; });
Expected output
{"kind":"unavailable"}
{"kind":"value","value":0}
INVALID_NATIVE_PERCENT

Only local fake results are mapped. Zero remains a real measurement and is never substituted for absence or protocol failure.

Own event streams and native resources explicitly

A native event source can outlive one component render. Define subscription cleanup, callback payload versions, cancellation, and whether queued callbacks may arrive after removal. Do not store transient screen state inside a module singleton merely because native registration is global. A large binary payload or frequent event stream also has transfer and scheduling costs; New Architecture does not erase every crossing cost or guarantee all APIs run on a particular thread. Follow the documented threading and ownership contract of the selected module. Do not move native UI access into arbitrary background JavaScript work to suppress a warning.

Diagnose the first failed integration layer

Capture the target, framework, dependency versions, build variant, exact module name, and first relevant failure. Compare native support and registration before changing business logic. A JavaScript refresh may not apply generated or native configuration changes, so a compatible authorized host must use its documented rebuild process. Keep denied permission, absent capability, unavailable module, and invalid payload distinguishable. This course installs no native dependency and runs no Codegen, parser, compiler, emulator, or device. Report fake adapter evidence accurately and avoid presenting a declaration as a working native feature.

Common errors and fixes

A missing module is treated as a zero reading

Why it happens
Availability and measurement were collapsed.
How to fix it
Return an explicit unavailable category and validate real values.
Prevent it next time
Test null module, zero, and invalid range separately.

A Spec file is reported as complete native support

Why it happens
Generated interface declarations were confused with implementation and registration.
How to fix it
Verify the actual target integration in its compatible host.
Prevent it next time
Track every native boundary in validation reports.

A removed screen receives obsolete native events

Why it happens
Resource detachment and publication lifetime were undefined.
How to fix it
Clean up owned subscriptions and gate callbacks where required.
Prevent it next time
Test removal while callbacks are pending.

Check your understanding

1. Why choose get rather than getEnforcing for an optional capability?

Show worked solution

get exposes absence as null so the application can present a supported fallback. getEnforcing treats absence as a configuration failure and throws. Choose from the product and integration contract; neither choice supplies the missing native implementation or proves permissions.

2. What does the fake0 reader establish?

Show worked solution

It establishes that the adapter accepts a valid zero and preserves it as a value category. It establishes nothing about actual hardware, native module registration, Codegen, or permission state. Those boundaries require separate authorized target evidence.

Key takeaways

  • A native call crosses several independent boundaries
  • Diagnose the first failed integration layer

Official references