In React Native on Android, what do BaseReactPackage's getModule and getReactModuleInfoProvider each contribute when registering a Turbo Module?
answer
- one is a menu, one a kitchen
- getModule: lazy factory by name
- ReactModuleInfo consulted first
- isTurboModule and needsEagerInit flags
- TurboReactPackage is the deprecated alias
basics
~20 sgetReactModuleInfoProvider advertises which module names the package can supply and how to treat them (isTurboModule, needsEagerInit, canOverrideExistingModule). getModule is the lazy factory React Native calls with one of those names to create the instance the first time JavaScript asks.
solid answer
~40 sA `BaseReactPackage` is the unit a Turbo Module is registered through. `getReactModuleInfoProvider()` returns a map from module name to `ReactModuleInfo(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)`. React Native reads that map first: a name must be listed there with `isTurboModule = true` before the package is asked for it. `getModule(name, reactContext)` is then the **factory** — it returns a new `StepCounterModule(reactContext)` for `NativeStepCounterSpec.NAME` and `null` for anything else — and runs lazily, when JavaScript first requests the module, unless `needsEagerInit` asks for creation at startup. `TurboReactPackage` is a deprecated alias of `BaseReactPackage`, and `createNativeModules()` on it throws. An app-local package is added to the list `MainApplication` builds from `PackageList`.
code
kotlin · 28 linespackage com.walkchallenge.steps
import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.walkchallenge.specs.NativeStepCounterSpec
class StepCounterPackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
if (name == NativeStepCounterSpec.NAME) StepCounterModule(reactContext) else null
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
mapOf(
NativeStepCounterSpec.NAME to
ReactModuleInfo(
name = NativeStepCounterSpec.NAME,
className = StepCounterModule::class.java.name,
canOverrideExistingModule = false,
needsEagerInit = false,
isCxxModule = false,
isTurboModule = true,
),
)
}
}go deeper
Remember that a package registers modules: the info map lists what it offers, and getModule creates a module when asked by name.
Explain every ReactModuleInfo flag, the lazy creation order, and why the info map is consulted before getModule is ever called.
Diagnose could-not-be-found and wrong-module problems through the info map, override rules and package list, and keep needsEagerInit off unless startup truly needs it.
Set conventions for in-house packages, such as one package per feature and generated names, and weigh eager initialisation against a measured startup budget.
## Why a package exists at all On Android, React Native does not scan your code for modules. Every native module reaches the runtime through a **package** — an object implementing `ReactPackage` — and for Turbo Modules the base class to extend is **`BaseReactPackage`**. A package answers two questions for the runtime: *which modules can you provide?* and *give me this one*. Those are the two methods it asks you to implement. ## getReactModuleInfoProvider: the advertisement `getReactModuleInfoProvider()` returns a `ReactModuleInfoProvider`, a functional interface whose `getReactModuleInfos()` yields a `Map<String, ReactModuleInfo>`. Each entry describes one module without creating it: | `ReactModuleInfo` field | Meaning | |---|---| | `name` | The module name JavaScript requests; also the map key | | `className` | A descriptive class name (the official tutorial simply passes the module name) | | `canOverrideExistingModule` | Whether this module may replace a same-named module from another package | | `needsEagerInit` | Create the module at startup instead of on first use | | `isCxxModule` | Marks a C++ module; `false` for a Kotlin module | | `isTurboModule` | `true` for a module that extends a generated spec | The older seven-argument constructor with a `hasConstants` flag is deprecated; the six-argument form above is current. ## getModule: the factory `getModule(name: String, reactContext: ReactApplicationContext): NativeModule?` creates an instance for a requested name, or returns `null` when the name is not this package's. It should do nothing but construct — no sensor registration, no I/O — because it runs on the path of a JavaScript call. ## How React Native combines them When JavaScript first calls `TurboModuleRegistry.getEnforcing('NativeStepCounter')`: 1. React Native walks the registered packages and looks up `'NativeStepCounter'` in each package's module-info map. 2. Only a package whose entry says `isTurboModule = true` is asked, via `getModule`, to create the instance; a later package can replace an earlier result only if its entry sets `canOverrideExistingModule`. 3. The instance is cached, so `getModule` is not called again for that name. 4. Separately, at startup, React Native creates every Turbo Module whose entry sets `needsEagerInit`. Two consequences matter in practice. **Creation is lazy**: a walking-challenge app does not pay for the step-counter module until a screen uses it — the legacy `createNativeModules()` path, by contrast, instantiated every module when the app started. And **the map is the gatekeeper**: a `getModule` that would happily return an instance is never called for a name missing from the map or listed with `isTurboModule = false`. ## One package, several modules A package is not limited to one module. A walking-challenge app might ship `NativeStepCounter` and `NativeHeartRate` from one `FitnessPackage`: the info map gains a second entry, and `getModule` becomes a `when (name)` with one branch per module and `else -> null`. Keying each branch on the generated `NativeXxxSpec.NAME` constants means a changed `getEnforcing` name flows through automatically, and a renamed spec file surfaces as a compile error in the package instead of a silent lookup miss. The reverse layout — one package per module — is equally valid and makes each feature's registration self-contained; what matters is that every advertised name has a matching factory branch. ## Getting the package into the app - **Autolinked libraries** are picked up by the build; that mechanism is a separate topic. - **App-local packages** are added by hand to the list `MainApplication` builds, as React Native's tutorial shows: `PackageList(this).packages.apply { add(StepCounterPackage()) }`. ## Legacy names you will meet - **`TurboReactPackage`** — now declared `@Deprecated("Use BaseReactPackage instead")` and simply extends `BaseReactPackage`. Rename the superclass and nothing else changes. - **`ReactPackage.createNativeModules()`** — the legacy eager list. `BaseReactPackage` overrides it to throw `UnsupportedOperationException`, pointing you to `getModule()`. ## Mistakes interviewers listen for - Listing the module in `getModule` but not in the info map, or with a different key. - Leaving `isTurboModule = false` on a spec-based module; React Native then treats it as a legacy module and the Turbo lookup skips it. - Setting `needsEagerInit = true` "to be safe" and adding startup cost for a module first used three screens in. - Doing work in `getModule` beyond constructing the module.
- getModule returns the module for its name, yet JavaScript says it could not be found. What do you check first?The module-info map, because React Native consults it before calling `getModule`. Check that the key and `name` equal the requested name, that `isTurboModule` is `true`, and that the package is actually in the app's package list. A `getModule` that is never called cannot help.
- When would you set needsEagerInit to true?Only when the module must exist before JavaScript first asks for it, for example to start listening for a system event at launch. React Native then creates it during startup, which costs launch time on every cold start; for a step counter first used on a challenge screen, lazy creation is the better default.
- What does canOverrideExistingModule control?Whether this package's module may replace a same-named module supplied by another package. Resolution walks the packages, and a later match replaces an earlier one only if its `ReactModuleInfo` sets the flag. The old `NativeModule.canOverrideExistingModule()` method is deprecated and not used by the New Architecture.
The module-info map is a restaurant's menu and getModule is its kitchen. A customer (JavaScript) can only order what is printed on the menu; the kitchen cooks a dish only when it is ordered. A dish the kitchen could make but the menu omits is never served.
saying these in an interview costs you the question
- getModule is called at startup for every module the package can create
- The module-info map is optional metadata that React Native ignores for lookup
- isTurboModule should be false unless the module is written in C++
- TurboReactPackage is the current base class to extend for Turbo Modules
- createNativeModules is still the way to register modules in a BaseReactPackage