WikifitaGitHub live67e8de5
outro · camdom/camdom-stack-migrations

CAMDOM -- Stack Migrations & Build Evolution

Complete evolution of the technology stack: React Native versions, Expo SDK, JSI migration, TurboModules, build system, patches, CI/CD.

Baixar raw

CAMDOM -- Stack Migrations & Build Evolution

Repository: p4bventures/condom-app Total commits: 429 (2024-06-06 to 2025-11-16) Package name: condom-app (npm) / com.billyboy.condomapp (Android) / innocean-camdom (Expo slug) Current version: 4.44.44 (app.json) / 7.7.7 (package.json)


1. Timeline Overview

2024-06-06  Initial commit ........... Expo 51, RN 0.74.2, React 18.2.0, Reanimated 3.x
2024-06-16  Pipeline added .......... EAS Build, GitHub Actions v1
2024-07-01  Skia + Reanimated V3 .... @shopify/react-native-skia ^1.3.7
2024-07-01  Native Modules defined .. Expo Modules API (JSI) for BLE
2024-07-07  iOS/Android native modules BLE Manager (Swift + Kotlin)
2024-07-10  BLE cross-platform ...... Android + iOS BLE working
2024-07-29  V1.0.0 Android .......... Firebase, Google Auth, Apple Auth added
2024-08-02  V2.0.0 .................. Peripheral manager feature branch merged
2024-08-04  V2.x rapid iteration .... Expo Updates configured, CI/CD stabilized
2024-08-18  Unistyles introduced .... react-native-unistyles ^2.9.1
2024-09-01  Android SDK 34 .......... compileSdk/targetSdk bumped
2024-09-05  V3.x series ............. Alarm/disconnection rework, session replay
2024-09-20  V3.33.0 ................. Last Expo 51 release before naming reset
2024-10-11  CamdomApp 1.0.0 ......... Version naming reset (CamdomApp era)
2024-10-15  Google login removed ..... Firebase auth stripped
2024-11-07  1.0.2 ................... itusexexempt encryption added
2025-01-31  1.2.0 ................... Package encryption
2025-06-09  Android versions update .. compileSdk 36, Gradle update, expo-modules-core patch
2025-10-06  Workbranch merge ......... Version bumps to 3.34.0 (Expo 51 still)
2025-11-12  **MAJOR MIGRATION** ...... Expo 54, RN 0.82, React 19, Reanimated 4
2025-11-16  Migration stabilized ..... "it builds fuck", final fixes

2. React Native Version History

DateCommitRN VersionReact VersionExpo SDKNotes
2024-06-0627d64d60.74.218.2.0~51.0.11Initial scaffold (Expo create-template)
2024-07-01b1f54f30.74.218.2.0~51.0.11+ Skia, ReanimatedV3, GestureHandler
2024-08-026078d0f0.74.318.2.0~51.0.11V2.0.0 - Peripheral manager merged
2024-08-1873b74780.74.318.2.0~51.0.24+ Unistyles ^2.9.1
2024-11-07bfc816c0.74.318.2.0~51.0.24Stable Expo 51 build
2025-06-09010d6d70.74.318.2.0~51.0.24Android SDK 36, still Expo 51
2025-10-06c82e02d0.74.318.2.0~51.0.24Last pre-migration release
2025-11-12231cbb50.82.119.1.1^54.0.23Big bang migration
2025-11-1623ef9900.82.119.1.1^54.0.23Migration stabilized

Key Observations

  • The project stayed on RN 0.74 + Expo 51 for 17 months (Jun 2024 -- Nov 2025).
  • The migration to RN 0.82 + Expo 54 happened in a single burst over 4 days (Nov 12--16, 2025).
  • React jumped from 18.2.0 to 19.1.1 (major version bump).
  • The commit messages during migration tell the story: wip: almost alive babe -> working shit -> yaaay fita! -> it builds fuck -> final bitch - wip -> yaaay olddd.

3. Expo SDK Evolution

Phase 1: Expo 51 (Jun 2024 -- Nov 2025)

Initial SDK 51.0.11 with minimal plugins. Grew to include:

{
  "plugins": [
    "expo-router",
    "expo-font",
    "expo-localization",
    "react-native-ble-plx",
    "expo-splash-screen",
    "expo-build-properties",
    "expo-haptics",
    "expo-updates"
  ]
}

Removed during cleanup: expo-status-bar, expo-system-ui, expo-web-browser, expo-dev-client, expo-updates.

Phase 2: Expo 54 (Nov 2025 -- present)

Full SDK jump. Module versions jumped to Expo 54-compatible:

ModuleExpo 51Expo 54
expo~51.0.24^54.0.23
expo-router~3.5.20~6.0.14
expo-av~14.0.6~16.0.7
expo-font~12.0.9~14.0.9
expo-haptics~13.0.1~15.0.7
expo-constants~16.0.2~18.0.10
expo-splash-screen~0.27.5~31.0.10
expo-localization~15.0.3~17.0.7
expo-linking~6.3.1~8.0.8
expo-build-properties~0.12.4~1.0.9

4. JSI Migration: Bridge to JavaScript Interface

Why JSI Was Needed

CAMDOM is a BLE (Bluetooth Low Energy) consent protection app. The core loop requires:

  • Real-time BLE peripheral advertising and central scanning
  • Sub-second bidirectional packet exchange between paired devices
  • Native AppleScript/Alarm integration on iOS
  • Background BLE operation with WakeLock on Android

The React Native Bridge architecture adds ~5-10ms per native call due to JSON serialization. For BLE state machines handling 60-second polling intervals with alarm triggers, this latency compounds and causes disconnection race conditions.

The Custom BLE Manager Module (Expo Modules API / JSI)

Located at modules/ble-manager/, this is a custom Expo Module (not a TurboModule, not a Bridge module) that uses the Expo Modules API with JSI bindings.

Structure:

modules/ble-manager/
  expo-module.config.json     # Platform registration
  index.ts                    # requireNativeModule('BleManager')
  src/
    BleManager.ts             # JS API surface
    BleManagerModule.ts       # requireNativeModule bridge
    BlePeripheralManager.ts   # Peripheral API
    Events.types.ts           # Type definitions
    Utils.ts                  # Helpers
  android/
    build.gradle              # Android build (compileSdk 36)
    src/main/java/p4b/modules/blemanager/
      BleManagerModule.kt     # Kotlin Expo Module
      BleManager.kt           # Central manager logic
      BlePeripheralManager.kt # Peripheral logic
  ios/
    BleManagerModule.swift    # Swift Expo Module
    BlePeripheralManager.swift
    BleManager.podspec

Key JS interface (modules/ble-manager/src/BleManagerModule.ts):

import {requireNativeModule} from 'expo-modules-core';
// It loads the native module object from the JSI or falls back to
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
export default requireNativeModule('BleManager');

This explicitly uses requireNativeModule which loads via JSI, bypassing the Bridge entirely. The fallback to NativeModulesProxy only activates during remote debugging.

Module registration (expo-module.config.json):

{
  "platforms": ["ios", "android"],
  "ios": { "modules": ["BleManagerModule"] },
  "android": { "modules": ["p4b.modules.blemanager.BleManagerModule"] }
}

Evolution of Native Module Architecture

PhaseDateArchitectureCommit
Definition2024-07-0191c4be0 - Native Modules definitionInitial Expo Modules API types
Android config2024-07-08c986dda - Android native module configKotlin module registration
iOS config2024-07-085b98522 / 7a4664f - iOS native module configSwift module registration
BLE working2024-07-10681eb80 - 0.1.6 ANDROID & iPhone BLECross-platform BLE verified
Android scan2024-07-2243d273a - Android Scanning and connectBLE scan + connect flow
Android 100%2024-07-2268c2810 - Android Finally 100%Full BLE peripheral + central
Peripheral merged2024-08-026078d0f - V2.0.0Peripheral manager feature branch
New Arch enabled2025-11-12231cbb5TurboModules + Fabric enabled
Nitro Modules added2025-11-1623ef990react-native-nitro-modules ^0.31.4

5. TurboModules / New Architecture

Activation

New Architecture was enabled during the Expo 54 migration (Nov 2025):

android/gradle.properties:

newArchEnabled=true
hermesEnabled=true
edgeToEdgeEnabled=true

MainActivity.kt references:

import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate

The activity delegates Fabric rendering via fabricEnabled flag from DefaultNewArchitectureEntryPoint.

MainApplication.kt uses the new React Native 0.82 initialization pattern:

import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.common.ReleaseLevel

class MainApplication : Application(), ReactApplication {
  override fun onCreate() {
    super.onCreate()
    DefaultNewArchitectureEntryPoint.releaseLevel = ReleaseLevel.STABLE
    loadReactNative(this)
    ApplicationLifecycleDispatcher.onApplicationCreate(this)
  }
}

This is the RN 0.82+ ReactNativeApplicationEntryPoint pattern, replacing the older SoLoader.init() approach. The ReleaseLevel.STABLE flag indicates full New Architecture adoption.

ReactHost (New Architecture)

override val reactHost: ReactHost by lazy {
    getDefaultReactHost(
      context = applicationContext,
      packageList = PackageList(this).packages,
      jsMainModulePath = ".expo/.virtual-metro-entry",
      useDevSupport = BuildConfig.DEBUG
    )
}

The jsMainModulePath = ".expo/.virtual-metro-entry" is the Expo 54 virtual entry point pattern.

Nitro Modules

Added in the final migration commit (23ef990): react-native-nitro-modules: ^0.31.4. This provides高性能 native bindings as an alternative to TurboModules for specific use cases.

ProGuard Rules (TurboModules)

-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }

Both Reanimated and TurboModule classes are preserved from R8 minification.


6. Build System

Gradle Configuration

Root android/build.gradle:

ext {
    buildToolsVersion = "36.0.0"
    minSdkVersion = 24
    compileSdkVersion = 36
    targetSdkVersion = 36
    ndkVersion = "27.1.12297006"
}

App android/app/build.gradle:

android {
    buildToolsVersion "36.0.0"
    compileSdk 36
    
    defaultConfig {
        multiDexEnabled true
        applicationId 'com.billyboy.condomapp'
        minSdkVersion 24
        targetSdkVersion 36
        versionCode 7777777
        versionName "7.7.7"
    }
}

The versionCode/versionName in build.gradle are placeholder values -- actual versions are injected by scripts/version_bumper.js at build time.

Gradle Wrapper: gradle-8.14.3-bin.zip

Kotlin Gradle Plugin: Applied via classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')

Expo CLI Integration in Gradle

react {
    entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", ...].execute().text.trim())
    cliFile = new File(["node", "--print", "require.resolve('@expo/cli', ...)"].execute().text.trim())
    bundleCommand = "export:embed"
    autolinkLibrariesWithApp()
}

The build uses Expo CLI (@expo/cli) for bundling instead of the standard React Native CLI. The bundleCommand = "export:embed" ensures Metro configuration from Expo is respected.

Hermes Engine

Enabled by default:

if (hermesEnabled.toBoolean()) {
    implementation("com.facebook.react:hermes-android")
} else {
    implementation jscFlavor
}

JSC fallback configured as io.github.react-native-community:jsc-android:2026004.+.

Metro Bundler

Minimal configuration -- delegates entirely to Expo defaults:

const {getDefaultConfig} = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;

Babel

presets: ['babel-preset-expo'],
plugins: [
    ['react-native-unistyles/plugin', { root: 'app' }],
    'react-native-reanimated/plugin',
]

Reanimated plugin is last (required by Reanimated docs). Unistyles Babel plugin processes files under app/.

Expo Autolinking

// settings.gradle
plugins {
    id("com.facebook.react.settings")
    id("expo-autolinking-settings")
}
expoAutolinking.useExpoModules()
expoAutolinking.useExpoVersionCatalog()

Uses Expo's own autolinking system, not the community @react-native-community/cli autolinking.


7. Patches Directory

expo-modules-core@1.12.24.patch

File: patches/expo-modules-core@1.12.24.patch

Problem: Expo Modules Core's PermissionsService.kt had a null-safety issue on Android when targeting SDK 34+. The requestedPermissions field from PackageInfo became nullable in newer Android API levels.

The fix (commit 672821d, 2025-06-09):

- return requestedPermissions.contains(permission)
+ return requestedPermissions?.contains(permission) ?: false

Why it was needed: When compileSdkVersion was bumped to 36, the Android SDK's PackageInfo.requestedPermissions field changed from String[] (non-null in Java) to String[]? (nullable in Kotlin). The Expo Modules Core library had not yet updated for this change, causing a Kotlin compilation error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver.

Impact: Without this patch, the entire Android build would fail. The patch was applied via pnpm patch expo-modules-core (standard pnpm patching mechanism).

Commit: 672821d "add patch to expo-modules-core-permissions due to kotlin check"


8. Android SDK Evolution

DateCommitcompileSdktargetSdkminSdkbuildToolsKotlin/GradleNotes
2024-06-0627d64d634342434.0.0Kotlin pluginInitial Expo prebuild
2024-09-013bc416334342434.0.0SameExplicit SDK 34 bump commit
2025-06-09010d6d736362436.0.0Updated"update app to newer android versions"
2025-11-12231cbb536362436.0.0SameExpo 54 migration

Key Changes

  • minSdkVersion 24 (Android 7.0 Nougat) -- maintained throughout the entire project lifecycle. This is the minimum for BLE peripheral mode on Android.
  • NDK version: 27.1.12297006
  • multiDexEnabled: true -- required for Firebase + all native modules
  • Edge-to-edge: Enabled in the final migration (edgeToEdgeEnabled=true in gradle.properties, edgeToEdgeEnabled: true in app.json)

9. iOS Configuration

Podfile

The iOS configuration uses Expo's managed Podfile approach. No custom Podfile modifications were found in the repository -- the Podfile is generated by expo prebuild.

Entitlements

No .entitlements files were found in the repository. iOS entitlements are managed through Expo's prebuild system and the app.json configuration.

Background Modes

BLE background operation is configured via the react-native-ble-plx plugin in app.json:

["react-native-ble-plx", {
    "isBackgroundEnabled": true,
    "modes": ["peripheral", "central"],
    "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to other devices..."
}]

The isBackgroundEnabled: true setting triggers Expo prebuild to add bluetooth-central and bluetooth-peripheral background modes to the iOS Info.plist.


10. CI/CD

GitHub Actions

The CI/CD pipeline evolved through multiple iterations:

Phase 1 (Jul 2024): android_build_googleplay.yml -- initial EAS Build workflow Phase 2 (Aug 2024): EAS Update configured (583bcb0), Expo Updates auto-publish Phase 3 (Sep 2024): AppCenter deploy added (b5eab2e) Phase 4 (Jun 2025): New main.yml created (4e07cd9), modernized CI Phase 5 (Oct 2025): Workbranch updates (b75f182)

Version Bumping Strategy

Two-mode version bumper at scripts/version_bumper.js:

Dev mode (pnpm version:android:dev):

  • Auto-increments minor version (e.g., 4.44.44 -> 4.45.44)
  • Increments versionCode by 1
  • Non-interactive

Final mode (pnpm version:android:final):

  • Interactive wizard -- prompts for versionName and versionCode
  • Validates versionCode > current versionCode (Play Store requirement)
  • Blocks build if validation fails

Build Scripts

{
    "android:fast": "pnpm expo run:android -- --active-arch-only -PreactNativeArchitectures=arm64-v8a",
    "build:android": "cd android && ./gradlew bundleRelease && cd ..",
    "release:android": "pnpm build:android && pnpm check:sdk-details && pnpm open-aab-folder",
    "release:android:dev": "pnpm version:android:dev && pnpm build:android && pnpm check:sdk-details && pnpm open-aab-folder",
    "release:android:final": "pnpm version:android:final && pnpm build:android && pnpm check:sdk-details && pnpm open-aab-folder"
}

The android:fast script builds only for arm64-v8a (development device), skipping x86/x86_64/armeabi-v7a architectures. The check:sdk-details script validates the output AAB file.


11. Dependency Highlights

BLE & Connectivity

PackageVersionRoleEvolution
react-native-ble-plx^3.5.0BLE central scanning + connection^3.2.1 -> ^3.5.0 (Expo 54 compat)
Custom ble-manager module0.6.0BLE peripheral advertising (JSI)Expo Modules API, never changed architecture
react-native-permissions^4.1.5Runtime permission handlingSame version throughout

State & Storage

PackageVersionRoleEvolution
react-native-mmkv4.0.0Key-value storage (fast)^2.12.2 -> 4.0.0 (Expo 54 compat)
react-native-volume-manager^2.0.8System volume controlAdded in v2.18 era

UI & Animation

PackageVersionRoleEvolution
react-native-reanimated~4.1.5Smooth animations, layout animations~3.10.1 -> ~4.1.5 (major version bump)
react-native-gesture-handler~2.28.0Touch gesture system~2.16.1 -> ~2.28.0
@shopify/react-native-skia2.3.10Canvas rendering (background balls)^1.3.7 -> ^1.2.3 -> 2.3.10 (major bump)
react-native-unistyles3.0.17Runtime theming/styles^2.9.1 -> 3.0.17 (major version bump)
react-native-screens~4.18.0Native screen containers3.31.1 -> ~4.18.0 (major bump)

Navigation

PackageVersionRoleEvolution
expo-router~6.0.14File-based routing~3.5.20 -> ~6.0.14 (major bump)
@react-navigation/native^7.1.19Navigation primitives^6.0.2 -> ^7.1.19 (major bump)

Audio & Haptics

PackageVersionRoleEvolution
expo-av~16.0.7Audio playback (alarm sounds)~14.0.6 -> ~16.0.7
expo-haptics~15.0.7Haptic feedback~13.0.1 -> ~15.0.7
react-native-snackbar^2.6.2Toast notificationsSame version throughout

Device Info & Utilities

PackageVersionRoleEvolution
react-native-device-info^11.1.0Device metadataSame version throughout
react-native-nitro-modules^0.31.4High-perf native bindingsAdded in Expo 54 migration
i18next / react-i18next^23.12.2 / ^15.0.0InternationalizationSame versions throughout

Removed During Migration

PackageLast SeenReason
react-native-firebase/*Expo 51 eraFirebase auth removed (Oct 2024)
@react-native-google-signin/google-signinExpo 51 eraGoogle login removed (Oct 2024)
@invertase/react-native-apple-authenticationExpo 51 eraApple auth removed
expo-dev-clientExpo 51 eraNo longer needed in Expo 54
expo-status-barExpo 51 eraDeprecated/merged
expo-system-uiExpo 51 eraDeprecated/merged
expo-web-browserExpo 51 eraNot used
react-native-awesome-sliderExpo 51 eraReplaced or removed
react-native-keyboard-controllerExpo 51 eraReplaced or removed
react-native-quick-base64Expo 51 eraReplaced or removed
react-native-get-random-valuesExpo 51 eraPolyfill no longer needed
expo-updatesExpo 51 eraRemoved from dependencies

12. Migration Challenges

Expo 51 -> 54 Migration (Nov 12--16, 2025)

This was the largest migration in the project's history. Evidence from commit messages and diffs:

Day 1 (Nov 12) -- "the painful day":

  • 4aa6cfe "version bumps" -- Initial dependency updates
  • aeaf642 "bump one version yaay" -- Version bump after first successful change
  • 231cbb5 "working shit" -- First build that compiled
  • d822683 "yaaay fita!" -- Breakthrough moment
  • 5efba89 "update splash" -- expo-splash-screen config changed (0.27 -> 31.x)
  • b19cdc4 "final bitch - wip" -- Still fixing issues
  • d94d64c "it builds fuck" -- Finally building

Day 2 (Nov 14):

  • 1ac485c "latex" -- Unknown but likely asset-related

Day 3 (Nov 16) -- "cleanup day":

  • 259fb54 "idk" -- Confusion during cleanup
  • bef7ef5 "update versions" -- Version reconciliation
  • 57487ad "betterscripts" -- Script improvements
  • c379064 "android 6" -- Android build number 6
  • 8db976f "wip" -> 3c21891 "yup" -> 988ca99 "idk bro" -> 23ef990 "yaaay olddd" -- Final stabilization

What broke:

  1. Reanimated 3 -> 4: Major API changes. The project was using Reanimated 3.10.1; Reanimated 4 has breaking changes in layout animations and shared element transitions.
  2. Unistyles 2 -> 3: Major version bump with new plugin API (react-native-unistyles/plugin with root option).
  3. Skia 1.x -> 2.x: Complete API overhaul for @shopify/react-native-skia.
  4. expo-splash-screen 0.27 -> 31.x: The splash screen API changed dramatically (now SplashScreenManager.registerOnActivity(this) pattern in Kotlin).
  5. React 18 -> 19: New rendering behavior, potential breaking changes in hooks.
  6. expo-router 3 -> 6: File-based routing API changes.
  7. MMKV 2.x -> 4.0: Storage API changes.
  8. MainApplication.kt rewrite: New ReactNativeApplicationEntryPoint.loadReactNative() pattern replaced SoLoader.init().
  9. Firebase/Google auth removal: The entire auth stack was stripped, simplifying the dependency tree but requiring code removal.
  10. expo-modules-core patch: The null-safety fix for Android SDK 36 compatibility was carried forward.

Earlier Migration Challenges

V1 -> V2 (Jul-Aug 2024): The Peripheral Manager feature branch (feature/peripheral-manager) required three Android build fix commits (0c38e44, 45dc77c, 63af0dd) before merging. BLE peripheral mode on Android required careful handling of BluetoothGattServer lifecycle.

Android SDK 34 bump (Sep 2024): Required version matching across all CI steps (51c5d7c "Ensure Android Versions matching in all steps + SetupGradle Action before using gradle").

Expo Updates crashes (Sep 2024): Multiple crash-fix iterations: ExoPlayer crash (a8c0c8c), Android crash (aa21d5a), Expo Updates on Android (ff44afa). The rapid 2.x -> 3.x version progression (v2.47.3 -> v3.33.0 in one day) indicates aggressive hotfixing.


13. Current State Summary

AspectValue
React Native0.82.1
React19.1.1
Expo SDK54
TypeScript~5.9.3
Package Managerpnpm 9.6.0
Android compileSdk36
Android targetSdk36
Android minSdk24
Gradle8.14.3
NDK27.1.12297006
New ArchitectureEnabled (Fabric + TurboModules)
HermesEnabled
Edge-to-edgeEnabled
JSICustom BLE Manager (Expo Modules API)
Nitro Modules^0.31.4 (added Nov 2025)
App version4.44.44 (app.json) / 7.7.7 (build.gradle placeholder)
Package IDcom.billyboy.condomapp
Patchesexpo-modules-core@1.12.24.patch (null-safety fix)
CI/CDGitHub Actions (main.yml)
Version managementCustom version_bumper.js (dev + final modes)

References

  • Commit 27d64d6: Initial commit (2024-06-06)
  • Commit 91c4be0: Native Modules definition (2024-07-01)
  • Commit 6078d0f: V2.0.0 - Peripheral manager merged (2024-08-02)
  • Commit 3bc4163: Android SDK 34 bump (2024-09-01)
  • Commit 7c1ec63: CamdomApp 1.0.0 version reset (2024-10-11)
  • Commit 672821d: expo-modules-core patch (2025-06-09)
  • Commit 010d6d7: Android SDK 36 update (2025-06-09)
  • Commit 231cbb5: Expo 54 migration begins (2025-11-12)
  • Commit d94d64c: "it builds fuck" - first successful build (2025-11-12)
  • Commit 23ef990: Migration stabilized (2025-11-16)

This document is a living reference. As new migrations occur, update the timeline and tables accordingly.