Fecha: 2026-03-17 Rama:
feature/spm-dependency-supportRepositorio:jsnavarroc/react-native-firebase(fork deinvertase/react-native-firebase) Firebase SDK: 12.10.0 React Native minimo para SPM: 0.75+
- Resumen Ejecutivo
- Arquitectura de la Solucion
- Cambios Realizados
- Referencia de Funciones y Parametros
- Guia de Integracion — Proyecto Legacy
- Guia de Integracion — Proyectos que requieren actualizar SPM
- Glosario
Cuando Apple lanzo Xcode 26 (2026), introdujo un cambio importante: el compilador Swift ahora usa "modulos explicitos" por defecto. Esto significa que el compilador necesita saber exactamente donde esta cada modulo (cada libreria) antes de compilar.
El problema es que Firebase iOS SDK, cuando se instala a traves de CocoaPods (el gestor de dependencias tradicional de iOS), tiene modulos internos (FirebaseCoreInternal, FirebaseSharedSwift) que no estan expuestos como productos publicos. En Xcode 16, esto no era un problema porque el compilador los encontraba automaticamente. En Xcode 26, el compilador ya no los busca por su cuenta y lanza errores de compilacion.
La solucion: Usar Swift Package Manager (SPM) como metodo principal para resolver dependencias de Firebase. SPM es el gestor de paquetes nativo de Apple y maneja correctamente la visibilidad de modulos internos. Como alternativa, se mantiene CocoaPods para proyectos que lo necesiten, con un workaround (SWIFT_ENABLE_EXPLICIT_MODULES=NO).
Un sistema de resolucion dual de dependencias que permite elegir entre SPM y CocoaPods para Firebase, de forma transparente, sin cambiar el codigo de la app. El sistema:
- Detecta automaticamente si SPM esta disponible (React Native >= 0.75)
- Usa SPM por defecto cuando esta disponible
- Cae a CocoaPods cuando SPM no esta disponible o cuando se desactiva explicitamente
- No requiere cambios en el codigo JavaScript/TypeScript de la app
- No requiere cambios en el codigo nativo (Objective-C/Swift) de la app
| Punto | Detalle |
|---|---|
| Linkage | SPM requiere dynamic linkage. CocoaPods requiere static linkage. No se pueden mezclar. |
| Xcode 26 | Si usas CocoaPods con Xcode 26, DEBES agregar SWIFT_ENABLE_EXPLICIT_MODULES = 'NO' en tu Podfile post_install. |
| React Native < 0.75 | Solo funciona con CocoaPods (SPM no esta disponible en versiones anteriores). |
| Simbolos duplicados | Si usas SPM con static linkage, cada pod embebe los productos SPM de Firebase → linker error por simbolos duplicados. Por eso SPM = dynamic. |
| FirebaseCoreExtension | Algunos paquetes (Messaging, Crashlytics) necesitan FirebaseCoreExtension como dependencia explicita en CocoaPods, pero SPM lo resuelve automaticamente como dependencia transitiva. |
┌─────────────────────────┐
│ pod install / build │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Podspec carga │
│ firebase_spm.rb │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ ¿spm_dependency() esta │
│ definida? (RN >= 0.75) │
└──────┬──────────┬───────┘
│ │
SI NO
│ │
┌────────────▼──┐ ┌──▼─────────────────┐
│ ¿$RNFirebase │ │ Usar CocoaPods │
│ DisableSPM │ │ spec.dependency() │
│ esta activo? │ └────────────────────┘
└───┬───────┬───┘
│ │
SI NO
│ │
┌────────────▼──┐ ┌─▼──────────────────┐
│ Usar CocoaPods│ │ Usar SPM │
│ (forzado) │ │ spm_dependency() │
└───────────────┘ └────────────────────┘
El sistema tiene 5 componentes que interactuan entre si:
┌─────────────────────────────────────────────────────────────────┐
│ COMPONENTE CENTRAL │
│ packages/app/firebase_spm.rb │
│ → Define la funcion firebase_dependency() │
│ → Lee la URL de SPM desde package.json │
│ → Decide automaticamente: SPM o CocoaPods │
└────────────────────────────┬────────────────────────────────────┘
│ es requerido por
┌──────────────────┼────────────────┐
│ │ │
┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐
│ 16 Podspecs │ │ package.json │ │ 43 archivos │
│ (*.podspec) │ │ sdkVersions │ │ nativos iOS │
│ │ │ │ │ (.h, .m) │
│ Cada uno │ │ Define: │ │ │
│ llama a │ │ - version │ │ Usan #if │
│ firebase_ │ │ Firebase │ │ __has_include│
│ dependency() │ │ - URL SPM │ │ para imports │
└──────────────┘ └──────────────┘ │ duales │
│ └──────────────┘
│
┌───────▼──────────────────────────────────────────┐
│ CI/CD: tests_e2e_ios.yml │
│ → Prueba AMBOS modos en cada PR │
│ → Matriz: spm × cocoapods × debug × release │
└──────────────────────────────────────────────────┘
| Decision | Justificacion |
|---|---|
| SPM como default | Apple impulsa SPM como el estandar. Xcode 26 funciona mejor con SPM. La comunidad iOS esta migrando a SPM. |
| Mantener CocoaPods como fallback | Muchos proyectos legacy dependen de CocoaPods. React Native < 0.75 no soporta SPM. Algunos setups (static frameworks) necesitan CocoaPods. |
| Una sola funcion helper | En lugar de modificar cada podspec individualmente, se centraliza la logica en firebase_dependency(). Si cambia la logica, se cambia en un solo lugar. |
| URL de SPM en package.json | Single source of truth: la version de Firebase y la URL de SPM estan en un solo archivo. Evita desincronizacion entre paquetes. |
Flag $RNFirebaseDisableSPM |
Escape hatch: si algo falla con SPM, el usuario puede volver a CocoaPods con una sola linea en el Podfile. |
| Dynamic linkage para SPM | Evita que cada pod embeba una copia de los productos SPM de Firebase (lo que causa "duplicate symbols" en static). |
SWIFT_ENABLE_EXPLICIT_MODULES=NO para CocoaPods |
Permite que el compilador Swift use descubrimiento implicito de modulos (como Xcode 16), evitando errores con modulos internos de Firebase. |
#if __has_include en codigo nativo |
Permite que el mismo archivo .m/.h compile tanto con SPM (headers de framework) como con CocoaPods (@import). Sin cambios en el codigo de la app. |
- Que es: Un archivo Ruby que define una funcion helper
- Donde:
packages/app/firebase_spm.rb - Por que existe: Porque cada paquete de react-native-firebase (auth, analytics, messaging, etc.) tiene un archivo
.podspecque declara sus dependencias iOS. Antes, cada uno hacias.dependency 'Firebase/Auth', versiondirectamente. Ahora, todos llaman afirebase_dependency()que decide si usar SPM o CocoaPods. - Para que sirve: Centralizar la logica de decision SPM vs CocoaPods en un solo lugar
- Como funciona:
# PASO 1: Lee la URL del repositorio SPM de Firebase desde package.json
# Esto se ejecuta UNA sola vez cuando se carga el archivo
$firebase_spm_url ||= begin
app_package_path = File.join(__dir__, 'package.json')
app_package = JSON.parse(File.read(app_package_path))
app_package['sdkVersions']['ios']['firebaseSpmUrl']
# Resultado: "https://github.com/firebase/firebase-ios-sdk.git"
end
# PASO 2: Funcion que cada podspec llama
def firebase_dependency(spec, version, spm_products, pods)
# Condicion 1: ¿Existe la funcion spm_dependency?
# → SI existe si React Native >= 0.75 (ellos la agregaron)
# → NO existe si React Native < 0.75
#
# Condicion 2: ¿El usuario NO definio $RNFirebaseDisableSPM?
# → Si el usuario puso $RNFirebaseDisableSPM = true en su Podfile,
# esta variable EXISTE y la condicion es falsa
if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM)
# RUTA SPM: Registra la dependencia via Swift Package Manager
spm_dependency(spec,
url: $firebase_spm_url,
requirement: { kind: 'upToNextMajorVersion', minimumVersion: version },
products: spm_products
)
else
# RUTA COCOAPODS: Registra la dependencia via CocoaPods tradicional
pods = [pods] unless pods.is_a?(Array) # Normaliza a array
pods.each do |pod|
spec.dependency pod, version
end
end
endDetalle linea por linea:
| Linea | Codigo | Que hace |
|---|---|---|
| 22 | $firebase_spm_url ||= begin |
Variable global Ruby. El ||= significa "asigna solo si no tiene valor". Se ejecuta una vez. |
| 23 | File.join(__dir__, 'package.json') |
Construye la ruta al package.json del paquete app. __dir__ es el directorio donde esta este archivo. |
| 24 | JSON.parse(File.read(...)) |
Lee y parsea el JSON del package.json |
| 25 | ['sdkVersions']['ios']['firebaseSpmUrl'] |
Extrae la URL: https://github.com/firebase/firebase-ios-sdk.git |
| 44 | def firebase_dependency(spec, version, spm_products, pods) |
Define la funcion con 4 parametros |
| 45 | if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM) |
Doble condicion: SPM disponible Y no deshabilitado |
| 46 | if defined?(Pod) && defined?(Pod::UI) |
Guard: solo imprime log si estamos dentro de CocoaPods (no en tests) |
| 50-54 | spm_dependency(spec, url:..., requirement:..., products:...) |
Llama a la funcion de React Native para registrar dependencia SPM |
| 65 | pods = [pods] unless pods.is_a?(Array) |
Si pods es un string, lo convierte a array de un elemento |
| 66-68 | pods.each { |pod| spec.dependency pod, version } |
Registra cada pod como dependencia CocoaPods |
- Que es: Archivo de tests en Ruby usando el framework Minitest
- Donde:
packages/app/__tests__/firebase_spm_test.rb - Por que existe: Para verificar que la logica de decision SPM vs CocoaPods funciona correctamente en CI sin necesidad de un proyecto iOS real
- Para que sirve: Detectar regresiones si alguien modifica
firebase_spm.rb - Como funciona:
# MockSpec simula un Pod::Specification de CocoaPods
# Captura las llamadas a .dependency() para poder verificarlas
class MockSpec
attr_reader :dependencies # Array donde se guardan las dependencias registradas
def dependency(name, version)
@dependencies << { name: name, version: version }
end
endLos 5 tests:
| Test | Que verifica | Como lo verifica |
|---|---|---|
test_cocoapods_single_pod |
Que cuando SPM NO esta disponible, se usa CocoaPods con un solo pod | Llama a firebase_dependency sin definir spm_dependency. Verifica que spec.dependencies tiene 1 entrada: Firebase/Auth |
test_cocoapods_multiple_pods |
Que cuando SPM NO esta disponible, se registran multiples pods | Pasa un array ['Firebase/Crashlytics', 'FirebaseCoreExtension']. Verifica que ambos se registran. |
test_spm_single_product |
Que cuando SPM SI esta disponible, se llama a spm_dependency |
Define un mock de spm_dependency como metodo global. Verifica que se llama con los parametros correctos (URL, version, products). |
test_spm_multiple_products_ignores_cocoapods_extras |
Que SPM solo usa los productos SPM, no los pods extra | Pasa ['FirebaseCrashlytics'] como SPM y ['Firebase/Crashlytics', 'FirebaseCoreExtension'] como CocoaPods. Verifica que solo FirebaseCrashlytics se registra via SPM. |
test_reads_spm_url_from_package_json |
Que la URL se lee correctamente del package.json | Verifica que $firebase_spm_url == 'https://github.com/firebase/firebase-ios-sdk.git' |
- Que es: El package.json del paquete
@react-native-firebase/app - Donde:
packages/app/package.json - Que se agrego: Un campo
firebaseSpmUrldentro desdkVersions.ios - Por que: Para que la URL del repositorio SPM de Firebase este en un solo lugar, no hardcodeada en multiples archivos
- Para que sirve:
firebase_spm.rblee este campo para saber de donde descargar Firebase via SPM
{
"sdkVersions": {
"ios": {
"firebase": "12.10.0",
"firebaseSpmUrl": "https://github.com/firebase/firebase-ios-sdk.git",
"iosTarget": "15.0",
"macosTarget": "10.15",
"tvosTarget": "15.0"
}
}
}| Campo | Tipo | Que es |
|---|---|---|
firebase |
String | Version del Firebase iOS SDK. Usada por todos los podspecs. |
firebaseSpmUrl |
String | URL del repositorio git de Firebase para SPM. |
iosTarget |
String | Version minima de iOS soportada. |
macosTarget |
String | Version minima de macOS soportada. |
tvosTarget |
String | Version minima de tvOS soportada. |
Cada paquete de react-native-firebase tiene un archivo .podspec que le dice a CocoaPods como instalarlo. Todos fueron modificados para usar firebase_dependency() en lugar de s.dependency directo.
Antes (metodo original):
# En RNFBAuth.podspec
s.dependency 'Firebase/Auth', firebase_sdk_versionDespues (con soporte SPM):
# En RNFBAuth.podspec
require '../app/firebase_spm' # Carga el helper
firebase_dependency(s, firebase_sdk_version, ['FirebaseAuth'], 'Firebase/Auth')Tabla completa de los 16 paquetes:
| Paquete | Podspec | Productos SPM | Pods CocoaPods | Notas |
|---|---|---|---|---|
| app | RNFBApp.podspec |
['FirebaseCore'] |
'Firebase/CoreOnly' |
Paquete base, requerido por todos |
| auth | RNFBAuth.podspec |
['FirebaseAuth'] |
'Firebase/Auth' |
Autenticacion |
| analytics | RNFBAnalytics.podspec |
['FirebaseAnalytics'] |
'FirebaseAnalytics/Core' |
Tiene logica extra para IdentitySupport |
| messaging | RNFBMessaging.podspec |
['FirebaseMessaging'] |
['Firebase/Messaging', 'FirebaseCoreExtension'] |
Necesita 2 pods en CocoaPods |
| crashlytics | RNFBCrashlytics.podspec |
['FirebaseCrashlytics'] |
['Firebase/Crashlytics', 'FirebaseCoreExtension'] |
Necesita 2 pods en CocoaPods |
| firestore | RNFBFirestore.podspec |
['FirebaseFirestore'] |
'Firebase/Firestore' |
Base de datos NoSQL |
| database | RNFBDatabase.podspec |
['FirebaseDatabase'] |
'Firebase/Database' |
Realtime Database |
| storage | RNFBStorage.podspec |
['FirebaseStorage'] |
'Firebase/Storage' |
Almacenamiento de archivos |
| functions | RNFBFunctions.podspec |
['FirebaseFunctions'] |
'Firebase/Functions' |
Cloud Functions |
| perf | RNFBPerf.podspec |
['FirebasePerformance'] |
'Firebase/Performance' |
Performance Monitoring |
| app-check | RNFBAppCheck.podspec |
['FirebaseAppCheck'] |
'Firebase/AppCheck' |
Verificacion de integridad |
| installations | RNFBInstallations.podspec |
['FirebaseInstallations'] |
'Firebase/Installations' |
IDs de instalacion |
| remote-config | RNFBRemoteConfig.podspec |
['FirebaseRemoteConfig'] |
'Firebase/RemoteConfig' |
Configuracion remota |
| in-app-messaging | RNFBInAppMessaging.podspec |
['FirebaseInAppMessaging-Beta'] |
'Firebase/InAppMessaging' |
Mensajes in-app |
| app-distribution | RNFBAppDistribution.podspec |
['FirebaseAppDistribution-Beta'] |
'Firebase/AppDistribution' |
Distribucion de apps |
| ml | RNFBML.podspec |
(deshabilitado) | (deshabilitado) | Machine Learning (comentado) |
¿Por que Messaging y Crashlytics necesitan 2 pods en CocoaPods pero solo 1 producto en SPM?
Porque FirebaseCoreExtension es una dependencia transitiva en SPM — cuando instalas FirebaseMessaging via SPM, SPM automaticamente incluye FirebaseCoreExtension. Pero en CocoaPods, cada dependencia debe declararse explicitamente.
- Que son: Archivos
.h(headers) y.m/.mm(implementacion) en Objective-C - Donde: Dentro de
packages/*/ios/RNFB*/ - Por que se modificaron: Porque SPM y CocoaPods exponen los headers de Firebase de formas diferentes
- Para que sirve: Para que el mismo codigo compile tanto con SPM como con CocoaPods
Patron de import dual:
// ANTES (solo CocoaPods):
#import <Firebase/Firebase.h> // Header umbrella que incluye todo
// DESPUES (SPM + CocoaPods):
#if __has_include(<Firebase/Firebase.h>)
// Ruta 1: CocoaPods — el header umbrella existe
#import <Firebase/Firebase.h>
#elif __has_include(<FirebaseAuth/FirebaseAuth.h>)
// Ruta 2: SPM — cada modulo tiene su propio header
#import <FirebaseAuth/FirebaseAuth.h>
#import <FirebaseCore/FirebaseCore.h>
#else
// Ruta 3: @import (modulos Clang) — fallback final
@import FirebaseCore;
@import FirebaseAuth;
#endifExplicacion del patron:
| Directiva | Que hace | Cuando se usa |
|---|---|---|
#if __has_include(<Firebase/Firebase.h>) |
Pregunta al compilador: "¿existe este header en el proyecto?" | En compilacion. Si CocoaPods instalo Firebase, este header existe. |
#import <Firebase/Firebase.h> |
Importa el header umbrella de Firebase (incluye TODO) | Solo con CocoaPods, porque CocoaPods crea este header que agrupa todo. |
#elif __has_include(<FirebaseAuth/FirebaseAuth.h>) |
Pregunta: "¿existe el header individual del modulo?" | En compilacion. Si SPM instalo FirebaseAuth, este header existe. |
#import <FirebaseAuth/FirebaseAuth.h> |
Importa el header especifico del modulo | Con SPM, porque cada producto SPM tiene su propio namespace. |
@import FirebaseAuth; |
Import de modulo Clang (Objective-C modules) | Fallback: funciona en ambos modos pero requiere que modules este habilitado. |
Archivos modificados por paquete:
| Paquete | Archivos | Headers importados |
|---|---|---|
| auth | RNFBAuthModule.h, RNFBAuthModule.m |
FirebaseCore, FirebaseAuth |
| analytics | RNFBAnalyticsModule.m |
FirebaseCore, FirebaseAnalytics |
| messaging | RNFBMessagingModule.m, RNFBMessagingSerializer.m |
FirebaseCore, FirebaseMessaging |
| crashlytics | RNFBCrashlyticsModule.m, RNFBCrashlyticsInitProvider.h, RNFBCrashlyticsInitProvider.m, RNFBCrashlyticsNativeHelper.m |
FirebaseCore, FirebaseCrashlytics, FirebaseCoreExtension |
| firestore | RNFBFirestoreCommon.h, RNFBFirestoreCollectionModule.h, RNFBFirestoreSerialize.h, RNFBFirestoreSerialize.m, RNFBFirestoreQuery.h |
FirebaseCore, FirebaseFirestore |
| database | RNFBDatabaseCommon.h, RNFBDatabaseQuery.h, RNFBDatabaseQueryModule.h, RNFBDatabaseReferenceModule.m, RNFBDatabaseOnDisconnectModule.m |
FirebaseCore, FirebaseDatabaseInternal |
| storage | RNFBStorageModule.m, RNFBStorageCommon.h |
FirebaseCore, FirebaseStorage |
| functions | RNFBFunctionsModule.mm |
FirebaseCore, FirebaseFunctions |
| perf | RNFBPerfModule.m |
FirebaseCore, FirebasePerformance |
| app-check | RNFBAppCheckProvider.h, RNFBAppCheckModule.m |
FirebaseCore, FirebaseAppCheck |
| installations | RNFBInstallationsModule.m |
FirebaseCore, FirebaseInstallations |
| remote-config | RNFBRemoteConfigModule.m |
FirebaseCore, FirebaseRemoteConfig |
| in-app-messaging | RNFBInAppMessagingModule.m |
FirebaseCore, FirebaseInAppMessaging |
| app-distribution | RNFBAppDistributionModule.m |
FirebaseCore, FirebaseAppDistribution |
| app | RNFBUtilsModule.m, RNFBJSON.m, RNFBMeta.m, RNFBPreferences.m, RNFBSharedUtils.m, RNFBRCTAppDelegate.m |
FirebaseCore |
- Que es: Archivo de GitHub Actions que define los tests end-to-end de iOS
- Donde:
.github/workflows/tests_e2e_ios.yml - Por que se modifico: Para probar AMBOS modos (SPM y CocoaPods) en cada Pull Request
- Para que sirve: Garantizar que ningun cambio rompa ninguno de los dos modos
Cambio clave 1 — Matriz ampliada:
# ANTES: solo probaba debug y release
let buildmode = ['debug', 'release'];
# DESPUES: tambien prueba SPM y CocoaPods
let buildmode = ['debug', 'release'];
let depResolution = ['spm', 'cocoapods']; # NUEVOEsto genera 4 combinaciones de jobs E2E:
iOS (debug, spm, 0)iOS (debug, cocoapods, 0)iOS (release, spm, 0)iOS (release, cocoapods, 0)
Cambio clave 2 — Step "Configure Dependency Resolution Mode":
- name: Configure Dependency Resolution Mode
run: |
if [[ "${{ matrix.dep-resolution }}" == "cocoapods" ]]; then
echo "Configuring CocoaPods-only mode (disabling SPM)"
cd tests/ios
# 1. Cambia linkage de dynamic a static
sed -i '' "s/^linkage = 'dynamic'/linkage = 'static'/" Podfile
# 2. Inyecta la flag $RNFirebaseDisableSPM = true al inicio del Podfile
printf '%s\n' '$RNFirebaseDisableSPM = true' | cat - Podfile > Podfile.tmp && mv Podfile.tmp Podfile
# 3. Remueve SWIFT_ENABLE_EXPLICIT_MODULES (no necesario sin SPM en CI)
sed -i '' "/SWIFT_ENABLE_EXPLICIT_MODULES/d" Podfile
echo "Podfile configured for CocoaPods-only mode"
else
echo "Using default SPM mode (dynamic linkage)"
fiExplicacion paso a paso de cada comando:
| # | Comando | Que hace | Por que |
|---|---|---|---|
| 1 | sed -i '' "s/^linkage = 'dynamic'/linkage = 'static'/" |
Busca la linea linkage = 'dynamic' y la reemplaza por linkage = 'static' |
CocoaPods necesita static linkage para evitar problemas con frameworks |
| 2 | printf '%s\n' '$RNFirebaseDisableSPM = true' | cat - Podfile > Podfile.tmp && mv Podfile.tmp Podfile |
Prepone $RNFirebaseDisableSPM = true al inicio del Podfile |
Activa el flag que hace que firebase_dependency() use CocoaPods |
| 3 | sed -i '' "/SWIFT_ENABLE_EXPLICIT_MODULES/d" |
Elimina cualquier linea que contenga SWIFT_ENABLE_EXPLICIT_MODULES |
En modo CocoaPods en CI, no necesitamos este workaround |
¿Por que printf | cat en lugar de sed -i?
El comando sed -i '' "/^platform :ios/i\\\n$RNFirebaseDisableSPM = true\n" (insertar antes de una linea) requiere texto multi-linea. Dentro de un bloque YAML run: |, las lineas sin indentacion rompen el YAML. printf | cat es un workaround portable que funciona dentro de YAML sin problemas de indentacion.
- Que es: El Podfile del proyecto de tests E2E
- Donde:
tests/ios/Podfile - Por que se modifico: Para soportar modo SPM por defecto y agregar el workaround de Xcode 26
- Para que sirve: Es el archivo que CocoaPods lee para saber que dependencias instalar en el proyecto de tests
Lineas clave:
# Linea 48 — Linkage dinamico para SPM
linkage = 'dynamic' # En modo CocoaPods, CI lo cambia a 'static'
# Lineas 100-110 — Workaround Xcode 26
# Xcode 26 habilita modulos explicitos por defecto, pero los targets
# internos de Firebase SPM (FirebaseCoreInternal, FirebaseSharedSwift)
# no estan expuestos como productos publicos.
# Esto NO desactiva SPM — solo le dice al compilador Swift que use
# descubrimiento implicito de modulos (comportamiento de Xcode 16).
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
end
end¿Que es SWIFT_ENABLE_EXPLICIT_MODULES?
Es un build setting de Xcode que controla como el compilador Swift encuentra los modulos:
YES(default en Xcode 26): El compilador SOLO encuentra modulos que estan explicitamente declarados. Si un modulo no esta listado como "publico", no lo encuentra.NO(default en Xcode 16): El compilador busca modulos automaticamente en todas las rutas de busqueda. Esto es mas permisivo pero menos estricto.
Firebase tiene modulos internos que no son publicos. Con YES, Xcode 26 no los encuentra → error de compilacion. Con NO, funciona como antes.
Proposito: Registra una dependencia de Firebase en un podspec, eligiendo automaticamente entre SPM y CocoaPods.
Parametros:
| Parametro | Tipo Ruby | Requerido | Descripcion | Ejemplo |
|---|---|---|---|---|
spec |
Pod::Specification |
Si | El objeto podspec (el s en el DSL de podspec). Representa al paquete que se esta configurando. |
s (viene del contexto del podspec) |
version |
String |
Si | Version del Firebase iOS SDK a usar. Debe coincidir con la version en package.json. | '12.10.0' |
spm_products |
Array<String> |
Si | Lista de nombres de productos SPM de Firebase. Estos nombres son los que aparecen en el Package.swift del firebase-ios-sdk. |
['FirebaseAuth'] o ['FirebaseCrashlytics'] |
pods |
String o Array<String> |
Si | Nombre(s) de las dependencias CocoaPods. Puede ser un string (1 dependencia) o un array (multiples). Estos nombres son los que aparecen en el Podspec de Firebase. | 'Firebase/Auth' o ['Firebase/Messaging', 'FirebaseCoreExtension'] |
Valor de retorno: nil — La funcion no retorna valor. Su efecto es lateral: registra la dependencia en el sistema (SPM o CocoaPods).
Ejemplo de uso en un podspec:
# RNFBAuth.podspec
require '../app/firebase_spm'
Pod::Spec.new do |s|
# ... configuracion del podspec ...
firebase_sdk_version = appPackage['sdkVersions']['ios']['firebase']
# Registra FirebaseAuth como dependencia
# - Si SPM: llama spm_dependency(s, url: "...", products: ['FirebaseAuth'])
# - Si CocoaPods: llama s.dependency('Firebase/Auth', '12.10.0')
firebase_dependency(s, firebase_sdk_version, ['FirebaseAuth'], 'Firebase/Auth')
endEjemplo con multiples dependencias CocoaPods:
# RNFBCrashlytics.podspec
firebase_dependency(s, firebase_sdk_version,
['FirebaseCrashlytics'], # SPM: solo necesita este
['Firebase/Crashlytics', 'FirebaseCoreExtension'] # CocoaPods: necesita ambos
)Proposito: Almacena la URL del repositorio git de Firebase iOS SDK para SPM.
| Propiedad | Valor |
|---|---|
| Tipo | String (variable global Ruby) |
| Valor default | nil (se asigna al cargar firebase_spm.rb) |
| Valor despues de cargar | 'https://github.com/firebase/firebase-ios-sdk.git' |
| Se puede sobreescribir | Si. Si defines $firebase_spm_url = 'otra-url' ANTES de cargar firebase_spm.rb, usara tu URL. |
Caso de uso para sobreescribir:
# En tu Podfile, antes de cualquier pod install:
$firebase_spm_url = 'https://github.com/mi-empresa/firebase-ios-sdk-fork.git'
# Ahora todos los paquetes RNFB usaran tu fork de FirebaseProposito: Flag para forzar el uso de CocoaPods y deshabilitar SPM.
| Propiedad | Valor |
|---|---|
| Tipo | Cualquiera (se checa con defined?(), no con valor) |
| Valor default | No definida (SPM habilitado) |
| Como activar | $RNFirebaseDisableSPM = true en tu Podfile |
| Efecto | firebase_dependency() siempre usara CocoaPods |
IMPORTANTE: La funcion checa defined?($RNFirebaseDisableSPM), NO el valor. Esto significa que incluso $RNFirebaseDisableSPM = false DESACTIVA SPM, porque la variable esta "definida". Para habilitar SPM, simplemente no definas esta variable.
NO esta definida en este proyecto. Es una funcion que React Native (>= 0.75) inyecta durante el proceso de pod install. Si existe, significa que el entorno soporta SPM.
| Parametro | Tipo | Descripcion |
|---|---|---|
spec |
Pod::Specification |
Podspec al que agregar la dependencia |
url: |
String |
URL del repositorio git del paquete Swift |
requirement: |
Hash |
Restriccion de version. Formato: { kind: 'upToNextMajorVersion', minimumVersion: '12.10.0' } |
products: |
Array<String> |
Lista de productos SPM a incluir |
Proyecto legacy = Un proyecto que usa React Native con CocoaPods y NO tiene soporte SPM.
| Requisito | Minimo | Recomendado |
|---|---|---|
| React Native | 0.73+ | 0.75+ (para SPM) |
| Xcode | 15.0 | 26+ |
| CocoaPods | 1.14+ | 1.16+ |
| iOS target | 15.0+ | 15.1+ |
| Ruby | 2.7+ | 3.0+ |
# En tu proyecto React Native
yarn add @react-native-firebase/app@latest
yarn add @react-native-firebase/auth@latest
# ... repite para cada modulo que usesUsa SPM si:
- React Native >= 0.75
- Xcode 26+
- No tienes dependencias que requieran static linkage
- Quieres el modo recomendado por Apple
Usa CocoaPods si:
- React Native < 0.75
- Tienes
use_frameworks! :linkage => :staticen tu Podfile - Tienes otras dependencias incompatibles con SPM
- Prefieres no cambiar nada (modo legacy)
# ios/Podfile
# Asegurate de tener dynamic linkage
linkage = 'dynamic'
use_frameworks! :linkage => linkage.to_sym
target 'TuApp' do
# ... tus pods ...
post_install do |installer|
# OBLIGATORIO para Xcode 26+
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
end
end
end
endLuego:
cd ios && pod installDeberias ver mensajes como:
[react-native-firebase] RNFBApp: Using SPM for Firebase dependency resolution (products: FirebaseCore)
[react-native-firebase] RNFBAuth: Using SPM for Firebase dependency resolution (products: FirebaseAuth)
# ios/Podfile — ANTES de las declaraciones de target
$RNFirebaseDisableSPM = true # Fuerza CocoaPods
# Static linkage (requerido para CocoaPods)
linkage = 'static'
use_frameworks! :linkage => linkage.to_sym
target 'TuApp' do
# ... tus pods ...
post_install do |installer|
# OBLIGATORIO si usas Xcode 26+
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
end
end
end
endDeberias ver:
[react-native-firebase] RNFBApp: SPM disabled ($RNFirebaseDisableSPM = true), using CocoaPods for Firebase dependencies
cd ios && xcodebuild -workspace TuApp.xcworkspace -scheme TuApp -sdk iphonesimulator build| Conflicto | Sintoma | Solucion |
|---|---|---|
| Duplicate symbols | duplicate symbol '_FIRApp' in ... |
Estas usando SPM con static linkage. Cambia a dynamic o activa $RNFirebaseDisableSPM |
| Module not found | No such module 'FirebaseAuth' |
Falta SWIFT_ENABLE_EXPLICIT_MODULES = 'NO' en Xcode 26 |
| Header not found | 'Firebase/Firebase.h' file not found |
El modo actual (SPM) no genera ese header umbrella. Los archivos nativos ya usan #if __has_include para manejar esto. |
| Pod::UI undefined | NameError: uninitialized constant Pod |
Estas ejecutando firebase_spm.rb fuera de CocoaPods (en tests). El guard defined?(Pod) ya lo maneja. |
| Version mismatch | unable to satisfy version requirement |
Asegurate que la version en package.json de @react-native-firebase/app coincide con tus pods. |
-
pod installcompleta sin errores - Los mensajes de log muestran el modo correcto (SPM o CocoaPods)
- El proyecto compila en Xcode sin errores
- La app inicia y
Firebase.configure()se ejecuta - Las funciones de Firebase (auth, analytics, etc.) funcionan
- Los tests existentes pasan
Swift Package Manager (SPM) es el gestor de paquetes nativo de Apple, integrado en Xcode. A diferencia de CocoaPods (que es una herramienta de terceros), SPM esta construido dentro de Xcode y Swift.
| Aspecto | CocoaPods | SPM |
|---|---|---|
| Instalacion | gem install cocoapods |
Ya viene con Xcode |
| Archivo config | Podfile |
Package.swift |
| Lock file | Podfile.lock |
Package.resolved |
| Resolucion | Centralizada (trunk server) | Descentralizada (git repos) |
| Tipo | Ruby gem externo | Herramienta nativa Apple |
| Dependencia | Version Minima | Razon |
|---|---|---|
react-native / react-native-tvos |
0.75.0 | Primera version que expone spm_dependency() en el runtime de CocoaPods |
@react-native-firebase/app |
Version con soporte SPM (esta PR) | Necesita firebase_spm.rb |
| Xcode | 15.0 (funcional), 26+ (recomendado) | SPM esta integrado desde Xcode 11, pero Xcode 26 cambia el compilador |
| Firebase iOS SDK | 12.10.0+ | Version testeada con este sistema |
| CocoaPods | 1.14+ | Para que spm_dependency() funcione correctamente en el contexto de pod install |
node -p "require('./package.json').dependencies['react-native']"
# o para tvOS:
node -p "require('./package.json').dependencies['react-native-tvos']"Si es < 0.75, necesitas hacer upgrade de React Native primero. SPM no esta disponible en versiones anteriores.
Abre ios/Podfile y busca:
use_frameworks! :linkage => :staticSi tienes :static, necesitas cambiarlo a :dynamic para SPM:
use_frameworks! :linkage => :dynamicADVERTENCIA: Cambiar de static a dynamic puede afectar otras dependencias. Verifica que todas tus dependencias soporten dynamic linkage.
Si tu Podfile tiene esta linea, eliminala:
$RNFirebaseDisableSPM = true # ELIMINAR ESTA LINEAEn tu Podfile post_install:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
end
end
endcd ios
rm -rf Pods
rm Podfile.lock
pod installBusca estas lineas:
[react-native-firebase] RNFBApp: Using SPM for Firebase dependency resolution (products: FirebaseCore)
Si ves "Using SPM", el modo SPM esta activo.
Si ves "SPM not available", tu version de React Native no soporta SPM.
| Error | Causa | Solucion |
|---|---|---|
duplicate symbol durante linkeo |
SPM + static linkage | Cambiar a dynamic linkage |
No such module 'FirebaseCore' |
Xcode 26 con explicit modules | Agregar SWIFT_ENABLE_EXPLICIT_MODULES = 'NO' |
spm_dependency is not defined |
RN < 0.75 | Actualizar React Native a >= 0.75 o usar CocoaPods con $RNFirebaseDisableSPM = true |
multiple commands produce Firebase.framework |
Conflicto entre SPM y CocoaPods para Firebase | Asegurate de NO tener pod 'Firebase/Core' manual en tu Podfile si SPM esta activo |
unable to resolve package |
URL de SPM incorrecta | Verificar firebaseSpmUrl en packages/app/package.json |
trueplatform :ios en CI |
Bug del sed multiline en YAML | Ya resuelto. Usar printf | cat en lugar de sed -i insert |
| Pod install loop / conflicto de versiones | Version de Firebase no coincide entre SPM y CocoaPods | Asegurate de usar la misma version en package.json y cualquier pod manual |
Si SPM no funciona, puedes volver a CocoaPods en 30 segundos:
# Agrega al inicio de tu Podfile:
$RNFirebaseDisableSPM = true
# Cambia linkage a static:
use_frameworks! :linkage => :staticcd ios && rm -rf Pods && pod install| Termino | Definicion |
|---|---|
| SPM (Swift Package Manager) | Herramienta de Apple para gestionar dependencias en proyectos iOS/macOS. Viene integrada en Xcode. Es el reemplazo moderno de CocoaPods. |
| CocoaPods | Herramienta de terceros (escrita en Ruby) para gestionar dependencias en iOS. Fue el estandar durante anos. Usa un archivo Podfile para declarar dependencias. |
| Podspec (.podspec) | Archivo de configuracion que describe una libreria distribuida via CocoaPods. Define nombre, version, archivos fuente, dependencias, etc. |
| Podfile | Archivo en la raiz del directorio ios/ que declara que dependencias necesita tu proyecto. CocoaPods lo lee durante pod install. |
| Linkage (static vs dynamic) | Define como se enlazan las librerias al binario final. Static: el codigo de la libreria se copia dentro de tu app. Dynamic: la libreria es un archivo separado que se carga en runtime. |
| Framework | En iOS, una forma de empaquetar una libreria con sus headers, recursos y metadatos. Puede ser static o dynamic. |
| Header (.h) | Archivo que declara la interfaz publica de una libreria en Objective-C/C. Le dice al compilador que funciones/clases existen. |
| Implementation (.m, .mm) | Archivo con el codigo real (implementacion) en Objective-C (.m) o Objective-C++ (.mm). |
#if __has_include |
Directiva del preprocesador de C/Objective-C que pregunta: "¿existe este archivo en las rutas de busqueda?" Retorna true/false. Se evalua en tiempo de compilacion. |
@import |
Forma moderna de importar un modulo en Objective-C. Equivalente a #import pero mas eficiente (usa modulos Clang). |
| Modulos explicitos | Feature de Xcode 26: el compilador solo reconoce modulos que estan explicitamente declarados. Modulos internos/transitivos no se encuentran automaticamente. |
| Modulos implicitos | Comportamiento anterior a Xcode 26: el compilador busca modulos en todas las rutas de busqueda, incluyendo transitivos. |
| Dependencia transitiva | Una dependencia que no declaras directamente, pero que es requerida por una dependencia que si declaraste. Ejemplo: si usas FirebaseMessaging y este necesita FirebaseCoreExtension, entonces FirebaseCoreExtension es transitiva. |
defined?() (Ruby) |
Operador Ruby que verifica si una expresion esta definida. Retorna una descripcion de la expresion (string) o nil. NO lanza error si no esta definida. |
Variable global Ruby ($var) |
Variable que comienza con $ en Ruby. Es accesible desde cualquier parte del programa. Se usa aqui para configuracion compartida entre archivos. |
spm_dependency() |
Funcion que React Native (>= 0.75) inyecta en el contexto de CocoaPods durante pod install. Permite que un podspec declare una dependencia SPM. |
YAML block scalar (|) |
En archivos YAML, | indica un bloque de texto multi-linea donde se preservan los saltos de linea. Usado en GitHub Actions para scripts multi-linea. |
| Umbrella header | Un archivo .h que importa todos los headers de un framework. CocoaPods crea Firebase/Firebase.h que incluye todo. SPM no genera este archivo. |
| Xcode build setting | Configuracion que controla como Xcode compila tu proyecto. Se define en el archivo .xcodeproj o via CocoaPods post_install. Ejemplo: SWIFT_ENABLE_EXPLICIT_MODULES. |
| CI/CD | Continuous Integration / Continuous Deployment. Sistema automatizado que compila, testea y despliega codigo en cada cambio. Aqui se usa GitHub Actions. |
| Matrix (CI) | Estrategia de GitHub Actions para ejecutar el mismo job con diferentes combinaciones de parametros. Ejemplo: buildmode: [debug, release] × dep-resolution: [spm, cocoapods] = 4 ejecuciones. |
| Interop layer | Capa de compatibilidad que permite que codigo antiguo funcione con APIs nuevas. React Native 0.81 con Old Architecture usa interop para que los bridges Objective-C funcionen con el nuevo sistema. |
| react-native-firebase | Libreria open-source que proporciona modulos de Firebase para React Native. Cada servicio de Firebase (Auth, Analytics, etc.) es un paquete separado. Repo original: invertase/react-native-firebase. |
| Fork | Copia de un repositorio git en otra cuenta. Se usa para hacer cambios sin afectar el original. Aqui: jsnavarroc/react-native-firebase es fork de invertase/react-native-firebase. |
| PR (Pull Request) | Solicitud para integrar cambios de una rama a otra. Aqui: PR #1 de feature/spm-dependency-support → main en el fork. |
| clang-format | Herramienta de formateo automatico para codigo C/C++/Objective-C. El CI la usa para verificar que el codigo sigue el estilo del proyecto. |
| Old Architecture (React Native) | Arquitectura original de React Native que usa bridge Objective-C (RCT_EXTERN_MODULE), NativeEventEmitter, y setNativeProps. La "New Architecture" usa TurboModules y Fabric. |
| Hash | Mensaje | Que cambio |
|---|---|---|
ca5604939 |
feat(ios): add SPM dependency resolution support alongside CocoaPods |
Commit principal: firebase_spm.rb, tests, 16 podspecs, 43 archivos nativos, CI matrix |
d5e423bec |
fix(ios): guard Pod::UI.puts calls for test environments |
Agrego if defined?(Pod::UI) para evitar error en tests Ruby |
dcb618b41 |
fix(ios): resolve CI failures — clang-format, sed indentation, Pod constant guard |
Formateo clang-format, fix sed en workflow, guard defined?(Pod) |
d21651adb |
fix(ci): use printf instead of sed multiline for Podfile flag insertion |
Reemplazo sed multiline por printf+cat para compatibilidad YAML |
react-native-firebase/
├── packages/
│ ├── app/
│ │ ├── firebase_spm.rb ← NUEVO: Helper central
│ │ ├── __tests__/
│ │ │ └── firebase_spm_test.rb ← NUEVO: Tests unitarios
│ │ ├── package.json ← MODIFICADO: agrego firebaseSpmUrl
│ │ └── RNFBApp.podspec ← MODIFICADO: usa firebase_dependency()
│ ├── auth/
│ │ ├── RNFBAuth.podspec ← MODIFICADO
│ │ └── ios/RNFBAuth/
│ │ ├── RNFBAuthModule.h ← MODIFICADO: #if __has_include
│ │ └── RNFBAuthModule.m ← MODIFICADO: #if __has_include
│ ├── analytics/
│ │ ├── RNFBAnalytics.podspec ← MODIFICADO
│ │ └── ios/RNFBAnalytics/
│ │ └── RNFBAnalyticsModule.m ← MODIFICADO
│ ├── messaging/
│ │ ├── RNFBMessaging.podspec ← MODIFICADO
│ │ └── ios/RNFBMessaging/
│ │ ├── RNFBMessagingModule.m ← MODIFICADO
│ │ └── RNFBMessagingSerializer.m← MODIFICADO
│ ├── crashlytics/
│ │ ├── RNFBCrashlytics.podspec ← MODIFICADO
│ │ └── ios/RNFBCrashlytics/
│ │ ├── RNFBCrashlyticsModule.m ← MODIFICADO
│ │ ├── RNFBCrashlyticsInitProvider.h ← MODIFICADO
│ │ ├── RNFBCrashlyticsInitProvider.m ← MODIFICADO
│ │ └── RNFBCrashlyticsNativeHelper.m ← MODIFICADO
│ ├── firestore/ ← MODIFICADO (5 archivos)
│ ├── database/ ← MODIFICADO (5 archivos)
│ ├── storage/ ← MODIFICADO (2 archivos)
│ ├── functions/ ← MODIFICADO (1 archivo .mm)
│ ├── perf/ ← MODIFICADO
│ ├── app-check/ ← MODIFICADO (2 archivos)
│ ├── installations/ ← MODIFICADO
│ ├── remote-config/ ← MODIFICADO
│ ├── in-app-messaging/ ← MODIFICADO
│ ├── app-distribution/ ← MODIFICADO
│ └── ml/ ← MODIFICADO (podspec deshabilitado)
├── tests/
│ └── ios/
│ └── Podfile ← MODIFICADO: linkage dynamic + SWIFT_ENABLE_EXPLICIT_MODULES
└── .github/
└── workflows/
└── tests_e2e_ios.yml ← MODIFICADO: matriz SPM/CocoaPods + step configuracion
Esta seccion documenta bugs reales encontrados al integrar esta solucion en un proyecto tvOS con React Native 0.77 → 0.81 (react-native-tvos). Util para diagnosticar problemas similares.
Bug 1: Linker error con APMETaskManager / APMMeasurement al usar $RNFirebaseAnalyticsWithoutAdIdSupport = true con SPM
Sintoma:
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_APMETaskManager"
"_OBJC_CLASS_$_APMMeasurement"
Cuando ocurre: Solo cuando se usan las tres condiciones simultaneamente:
- SPM habilitado (no
$RNFirebaseDisableSPM) $RNFirebaseAnalyticsWithoutAdIdSupport = trueen el PodfileFirebasePerformanceNO esta instalado
Causa raiz:
El producto SPM FirebaseAnalytics incluye GoogleAppMeasurement, que contiene referencias cruzadas a APMETaskManager y APMMeasurement (clases de Firebase Performance Monitoring). Cuando FirebasePerformance no esta instalado, esas clases no existen → error de linker.
El producto SPM FirebaseAnalyticsCore usa GoogleAppMeasurementCore en su lugar, que es la version sin IDFA y sin las referencias APM. Es exactamente lo que se necesita cuando se quiere analytics sin Ad ID support.
Archivo afectado: packages/analytics/RNFBAnalytics.podspec
Fix aplicado:
# ANTES (solo FirebaseAnalytics, siempre):
firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalytics'], 'FirebaseAnalytics/Core')
# DESPUES (condicional segun $RNFirebaseAnalyticsWithoutAdIdSupport + SPM):
if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM) &&
defined?($RNFirebaseAnalyticsWithoutAdIdSupport) && $RNFirebaseAnalyticsWithoutAdIdSupport
# FirebaseAnalyticsCore → GoogleAppMeasurementCore (sin IDFA, sin APM objects)
Pod::UI.puts "#{s.name}: Using FirebaseAnalyticsCore SPM product (no IDFA, uses GoogleAppMeasurementCore)."
firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalyticsCore'], 'FirebaseAnalytics/Core')
else
firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalytics'], 'FirebaseAnalytics/Core')
endCuando aplicar este fix: Siempre que se use $RNFirebaseAnalyticsWithoutAdIdSupport = true con SPM y sin FirebasePerformance.
Sintoma:
error: Packages are not supported when using legacy build locations.
El build falla inmediatamente al abrir el workspace o durante xcodebuild.
Cuando ocurre: Al usar SPM (con spm_dependency) en un proyecto que tiene CocoaPods. El proyecto de Pods (.xcodeproj generado por CocoaPods) usa "legacy build locations" por defecto, pero Xcode 26 no permite paquetes SPM con ese modo.
Causa raiz:
CocoaPods genera proyectos .xcodeproj que no tienen WorkspaceSettings.xcsettings con BuildSystemType = Latest. Sin este archivo, Xcode usa "legacy build locations". Xcode 26 agregó la restricción de que los paquetes SPM no pueden usarse con ese modo.
Archivo afectado: node_modules/react-native/scripts/cocoapods/spm.rb (parte de react-native, no de este fork)
Fix aplicado en spm.rb (metodo apply_on_post_install):
# Crear WorkspaceSettings.xcsettings para optar por modern build system
unless @dependencies_by_pod.empty?
shared_data_dir = File.join(project.path, 'project.xcworkspace', 'xcshareddata')
FileUtils.mkdir_p(shared_data_dir)
settings_path = File.join(shared_data_dir, 'WorkspaceSettings.xcsettings')
unless File.exist?(settings_path)
File.write(settings_path, <<~PLIST)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist>
PLIST
end
endNota importante: Este fix esta en react-native / react-native-tvos, no en este fork. Hay que verificar si esta incluido en react-native >= 0.81 o aplicarlo via post_install hook en el Podfile del proyecto.
Workaround alternativo (en Podfile del proyecto):
post_install do |installer|
shared_data_dir = File.join(installer.pods_project.path, 'project.xcworkspace', 'xcshareddata')
FileUtils.mkdir_p(shared_data_dir)
settings_path = File.join(shared_data_dir, 'WorkspaceSettings.xcsettings')
unless File.exist?(settings_path)
File.write(settings_path, <<~PLIST)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist>
PLIST
end
endBug 3: Crash "XCSwiftPackageProductDependency _setSavedArchiveVersion" o "Type checking error: got XCSwiftPackageProductDependency for mainGroup"
Sintoma:
Xcode crashea al abrir el workspace o el build falla con uno de estos errores:
XCSwiftPackageProductDependency _setSavedArchiveVersion: unrecognized selector
Type checking error: got XCSwiftPackageProductDependency for mainGroup
El Pods.xcodeproj resulta corrupto.
Cuando ocurre: En proyectos que ya tuvieron dependencias SPM y luego se ejecuta pod install de nuevo (por ejemplo, al cambiar versiones de Firebase o al hacer rm -rf Pods && pod install).
Causa raiz:
CocoaPods usa un contador secuencial para generar UUIDs de objetos en el .pbxproj. Cuando clean_spm_dependencies_from_target elimina los objetos SPM del proyecto, sus UUIDs vuelven al pool disponible. Al agregar nuevos objetos SPM, el contador puede reutilizar esos UUIDs, que coinciden con UUIDs de objetos no-SPM ya existentes (como rootObject o mainGroup).
En objects_by_uuid, el ultimo que escribe gana. El objeto SPM sobreescribe la entrada del objeto no-SPM → el proyecto queda corrupto.
Archivo afectado: node_modules/react-native/scripts/cocoapods/spm.rb (metodo apply_on_post_install)
Fix aplicado en spm.rb:
# ANTES de agregar objetos SPM, tomar snapshot de objects_by_uuid
pre_spm_snapshot = project.objects_by_uuid.dup
# ... agregar objetos SPM ...
# DESPUES, detectar y corregir colisiones
fix_spm_uuid_collisions(project, pre_spm_snapshot)El metodo fix_spm_uuid_collisions detecta objetos SPM cuyo UUID existia en el snapshot pre-SPM, les asigna un UUID aleatorio seguro via SecureRandom.hex, y restaura el objeto original en su UUID original.
Nota importante: Este fix esta en react-native / react-native-tvos, no en este fork. Reportar upstream o aplicar manualmente si se usa react-native < 0.81.
Sintoma:
RNFBAppModule not found. Did you forget to link the native module?
La app crashea inmediatamente al iniciar en tvOS.
Cuando ocurre: Especificamente en el target tvOS. En iOS el mismo build funciona correctamente.
Causa raiz:
El target tvOS en project.pbxproj tenia OTHER_LDFLAGS incompleto:
OTHER_LDFLAGS = ("$(inherited)", " "); // tvOS — FALTABA -ObjC
Mientras el target iOS tenia:
OTHER_LDFLAGS = ("$(inherited)", "-ObjC", "-lc++"); // iOS — correcto
Sin -ObjC, el linker no carga las categorias y clases Objective-C definidas en librerias estaticas (como los modulos nativos de Firebase). El modulo RNFBAppModule no se registra en el bridge de React Native → crash.
Archivo afectado: ios/NextPlay.xcodeproj/project.pbxproj (en el proyecto cliente, no en el fork)
Fix: Agregar -ObjC y -lc++ a OTHER_LDFLAGS de los targets tvOS (Debug y Release):
OTHER_LDFLAGS = ("$(inherited)", "-ObjC", "-lc++");
Despues del fix: Clean Build Folder (Cmd+Shift+K) y rebuild.
Como diagnosticar: Buscar en project.pbxproj todas las ocurrencias de OTHER_LDFLAGS y verificar que los targets tvOS (SDKROOT = appletvos) tengan -ObjC.
Sintoma:
Los eventos de Firebase Analytics no aparecen en Firebase DebugView. Si aparecen, lo hacen con 30-60 minutos de retraso.
Cuando ocurre: Sin las flags de debug, Firebase Analytics agrupa los eventos y los envia en batch cada ~30-60 minutos para optimizar bateria/red.
Causa raiz:
Dos problemas combinados:
AppDelegate.swiftsolo teniaFirebaseApp.configure()sin activar el modo debug- Se usaba
-FIRDebugEnableden lugar de-FIRAnalyticsDebugEnabled(flag especifica para Analytics DebugView) CommandLine.arguments.append(...)solo funciona si la app se lanza desde Xcode. Si se lanza manualmente en el simulador, no tiene efecto
Fix aplicado en AppDelegate.swift (ANTES de FirebaseApp.configure()):
#if DEBUG
// Metodo 1: funciona cuando se lanza desde Xcode
CommandLine.arguments.append("-FIRAnalyticsDebugEnabled")
// Metodo 2: funciona cuando se lanza manualmente en el simulador
UserDefaults.standard.set(true, forKey: "/google/firebase/debug_mode")
UserDefaults.standard.set(true, forKey: "/google/measurement/debug_mode")
UserDefaults.standard.synchronize()
#endif
FirebaseApp.configure()Nota: Para tvOS, usar el simulador "Apple TV 4K (2nd generation)" o posterior. El simulador "Apple TV" original no reporta eventos a Firebase DebugView.
| Bug | Archivo afectado | Repositorio | Fix incluido en este PR |
|---|---|---|---|
APM linker error con WithoutAdIdSupport |
packages/analytics/RNFBAnalytics.podspec |
este fork | Si |
| Legacy build locations Xcode 26 | node_modules/react-native/scripts/cocoapods/spm.rb |
react-native/react-native-tvos | No (upstream) |
| UUID collision en pbxproj | node_modules/react-native/scripts/cocoapods/spm.rb |
react-native/react-native-tvos | No (upstream) |
RNFBAppModule not found tvOS |
ios/*.xcodeproj/project.pbxproj |
proyecto cliente | No (fix manual) |
| Firebase DebugView sin eventos | ios/NextPlay/AppDelegate.swift |
proyecto cliente | No (fix manual) |