Skip to content

feat!: migrate to TurboModules#9080

Draft
mikehardy wants to merge 10 commits into
mainfrom
new-architecture
Draft

feat!: migrate to TurboModules#9080
mikehardy wants to merge 10 commits into
mainfrom
new-architecture

Conversation

@mikehardy

Copy link
Copy Markdown
Collaborator

Summary

Coordinated New Architecture migration: legacy NativeModules → Codegen TurboModules (NativeRNFBTurbo*), unified module resolver, committed generated artifacts, and shared test/validation infrastructure.

Land so far: app (foundation + utils) and functions (reference). Remaining native packages follow the same per-package loop before the coordinated semver major.

Infra in this branch: architecture decisions (OKF), gitignored harness overrides, implementation/review gate hardening, Jest TurboModule contract test, compare:types config for app.

Related #8584

Migration checklist

  • @react-native-firebase/app
  • @react-native-firebase/functions
  • @react-native-firebase/auth
  • @react-native-firebase/firestore
  • @react-native-firebase/database
  • @react-native-firebase/messaging
  • @react-native-firebase/storage
  • @react-native-firebase/crashlytics
  • @react-native-firebase/analytics
  • @react-native-firebase/remote-config
  • @react-native-firebase/app-check
  • @react-native-firebase/perf
  • @react-native-firebase/installations
  • @react-native-firebase/in-app-messaging
  • @react-native-firebase/app-distribution
  • @react-native-firebase/ml
  • @react-native-firebase/phone-number-verification

Test plan

  • CI green on this draft (lint, Jest, compare-types, native builds as configured)
  • Follow-up: area-focused e2e per migrated package before merge-ready

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request initiates the coordinated migration of the React Native Firebase monorepo to the React Native New Architecture using TurboModules. It establishes the foundational infrastructure, including a unified module resolver, committed generated artifacts, and updated type-testing configurations. The changes focus on migrating the core app and functions packages while providing the necessary tooling and documentation to support the subsequent migration of remaining native packages.

Highlights

  • TurboModules Migration: Migrated legacy NativeModules to Codegen TurboModules for @react-native-firebase/app and @react-native-firebase/functions, establishing the foundation for the coordinated New Architecture migration.
  • Modular API Alignment: Updated TypeScript type comparison configurations (compare:types) to ensure modular API parity with the firebase-js-sdk, including new exports for SDK_VERSION and various helper types.
  • Documentation & Workflow: Added comprehensive migration documentation for v26 (namespaced API removal) and updated internal OKF bundles to include new architecture decisions, implementation workflows, and agent command policies.
  • Test Infrastructure: Enhanced Jest setup with TurboModuleRegistry mocks and added a new contract test suite (nativeModuleContract.test.ts) to validate the TurboModule wrapper contract.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes the deprecated namespaced JavaScript API across multiple packages (such as analytics, app-check, and app-distribution) to transition to a modular-only architecture for v26. It also introduces the foundational TurboModule migration for the app package under the New Architecture, updating native iOS/Android configurations, JNI codegen, and test harness setups. Feedback on these changes highlights several stability improvements, including preventing a TypeError in initializeAppCheck when options are omitted, ensuring correct context and null-safety when resolving native module constants, adding try-catch protection to app-destroy callbacks, and guarding against potential null pointer exceptions when querying external storage directories in Android.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

return new ReactNativeFirebaseAppCheckProvider();
}

initializeAppCheck(options: AppCheckOptions): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If options is not provided (which is allowed by the modular initializeAppCheck signature), calling options.isTokenAutoRefreshEnabled will throw a TypeError: Cannot read properties of undefined. Defaulting options to an empty object prevents this crash and allows the subsequent hasProviderOptions check to throw a clean, descriptive error instead.

Suggested change
initializeAppCheck(options: AppCheckOptions): Promise<void> {
initializeAppCheck(options: AppCheckOptions = {} as AppCheckOptions): Promise<void> {

Comment on lines +21 to +25
let constants = memoizedModuleConstants.get(moduleName);
if (!constants) {
constants = (getConstants as () => Record<string, unknown>)();
memoizedModuleConstants.set(moduleName, constants);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling getConstants without a this context can lead to unexpected behavior or errors if the underlying JSI/native implementation expects the host object context. Using getConstants.call(module) ensures the correct context is preserved. Additionally, guarding against a null or undefined return value from getConstants() prevents a TypeError when calling Object.keys(constants) later.

Suggested change
let constants = memoizedModuleConstants.get(moduleName);
if (!constants) {
constants = (getConstants as () => Record<string, unknown>)();
memoizedModuleConstants.set(moduleName, constants);
}
let constants = memoizedModuleConstants.get(moduleName);
if (!constants) {
constants = getConstants.call(module) || {};
memoizedModuleConstants.set(moduleName, constants);
}

Comment on lines +260 to +262
for (let i = 0; i < onAppDestroyCallbacks.length; i++) {
onAppDestroyCallbacks[i]?.(app);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If any of the onAppDestroyCallbacks throws an error, the loop will be interrupted, preventing subsequent callbacks from executing and leaving the app registry in an inconsistent state. Wrapping each callback execution in a try...catch block ensures robust cleanup.

    for (let i = 0; i < onAppDestroyCallbacks.length; i++) {
      try {
        onAppDestroyCallbacks[i]?.(app);
      } catch (e) {
        console.error('Error during onAppDestroy callback:', e);
      }
    }

Comment on lines +163 to +171
constants.put(
KEY_PICS_DIRECTORY,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
.getAbsolutePath());

constants.put(
KEY_MOVIES_DIRECTORY,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
.getAbsolutePath());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Environment.getExternalStoragePublicDirectory can return null if the external storage is unmounted or unavailable. Calling .getAbsolutePath() directly on the returned value without a null check poses a NullPointerException risk. Guarding these calls ensures defensive stability.

Suggested change
constants.put(
KEY_PICS_DIRECTORY,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
.getAbsolutePath());
constants.put(
KEY_MOVIES_DIRECTORY,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
.getAbsolutePath());
File picsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
constants.put(KEY_PICS_DIRECTORY, picsDir != null ? picsDir.getAbsolutePath() : "");
File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
constants.put(KEY_MOVIES_DIRECTORY, moviesDir != null ? moviesDir.getAbsolutePath() : "");

Add TurboModule migration OKF bundle with work queue, implementation
workflow extending change authoring, and package-extensions cross-link.
…n infra

BREAKING CHANGE: App/Core modules native bridge requires New Architecture.

Migrate RNFBAppModule and RNFBUtilsModule to Codegen TurboModules with
unified resolver, lazy Proxy wrapper, and committed generated artifacts.
Includes functions codegenConfig rename (NewArch-AD-7), test harness
overrides, architecture decisions, and validation workflow hardening.
Register @react-native-firebase/app in compare:types and document 25
modular API deltas against firebase-js-sdk. Type SDK_VERSION as string.
BREAKING CHANGE: Firestore native bridge requires New Architecture.
Legacy NativeModules bridge removed; four Codegen TurboModule specs
(NativeRNFBTurboFirestore{,Collection,Document,Transaction}) with
committed generated artifacts, Android/iOS turbo shells, and JS wiring.
Environment.getExternalStoragePublicDirectory can return null when
external storage is unavailable; guard the pictures and movies
directory constants instead of dereferencing getAbsolutePath directly.
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