Skip to content

App crashes by initialization of ReactNativeHostWrapper: ClassNotFoundException: expo.modules.ExpoModulesPackageList #131

@jmurga97

Description

@jmurga97

App crashes with ClassNotFoundException: expo.modules.ExpoModulesPackageList when integrating Expo bare-minimum template with native Android app

Description

I've been trying for several days to create a simple integration demo between a native Android application and an Expo CLI app created with the bare-minimum template.

I've successfully created the AAR package and included it as a local dependency in my native application. The native app builds successfully, but crashes on startup with an error originating from ReactNativeHostManager initialization.

Error

2025-09-04 13:12:56.079  5655-5655  ExpoModulesPackage      com.example.bkt_fe_native_coex_poc   E  Couldn't get expo package list. (Ask Gemini)
                                                                                                    java.lang.ClassNotFoundException: expo.modules.ExpoModulesPackageList
                                                                                                    	at java.lang.Class.classForName(Native Method)
                                                                                                    	at java.lang.Class.forName(Class.java:597)
                                                                                                    	at java.lang.Class.forName(Class.java:502)
                                                                                                    	at expo.modules.ExpoModulesPackage.packageList_delegate$lambda$1(ExpoModulesPackage.kt:23)
                                                                                                    	at expo.modules.ExpoModulesPackage.$r8$lambda$QFBV9-FY5jLEWS4oBZ76Yb8QIkI(Unknown Source:0)
                                                                                                    	at expo.modules.ExpoModulesPackage$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)
                                                                                                    	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
                                                                                                    	at expo.modules.ExpoModulesPackage$Companion.getPackageList(ExpoModulesPackage.kt:21)
                                                                                                    	at expo.modules.ApplicationLifecycleDispatcher.getCachedListeners(ApplicationLifecycleDispatcher.kt:13)
                                                                                                    	at expo.modules.ApplicationLifecycleDispatcher.onApplicationCreate(ApplicationLifecycleDispatcher.kt:20)
                                                                                                    	at com.bmb.demorn.ReactNativeHostManager.initialize(ReactNativeHostManager.kt:154)
                                                                                                    	at com.example.bkt_fe_native_coex_poc.MainActivity.onCreate(MainActivity.kt:21)

Code

Native MainActivity

import com.bmb.demorn.ReactNativeHostManager

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //ReactNativeHostManager.initialize(this.application)
        ReactNativeHostManager.shared.initialize(this.application)
        // improved conf Edge-to-Edge
        enableEdgeToEdge()
        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            BktfenativecoexpocTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    BaseScreenLayout(
                        title = "Demo",
                    ) {
                        HomeScreen()
                    }
                }
            }
        }
    }
}

ReactNativeHostManager

class ReactNativeHostManager {
    companion object {
        val shared: ReactNativeHostManager by lazy { ReactNativeHostManager() }
        private var reactNativeHost: ReactNativeHost? = null
        private var reactHost: ReactHost? = null
    }

    fun getReactNativeHost(): ReactNativeHost? {
        return reactNativeHost
    }

    fun getReactHost(): ReactHost? {
        return reactHost
    }

    fun initialize(application: Application) {
        if (reactNativeHost != null && reactHost != null) {
            return
        }

        SoLoader.init(application, OpenSourceMergedSoMapping)
        if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
            load()
        }
        ApplicationLifecycleDispatcher.onApplicationCreate(application)

        val jsMainModuleName = ".expo/.virtual-metro-entry"

        val reactApp = object : ReactApplication {
            override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(application,
                object : DefaultReactNativeHost(application) {
                    override fun getPackages(): MutableList<ReactPackage> {
                        return PackageList(application).packages
                    }

                    override fun getJSMainModuleName(): String = jsMainModuleName
                    override fun getBundleAssetName(): String = "index.android.bundle"

                    override fun getUseDeveloperSupport() = BuildConfig.DEBUG

                    override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
                    override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
                })

            override val reactHost: ReactHost
                get() = getDefaultReactHost(application, reactNativeHost)
        }

        reactNativeHost = reactApp.reactNativeHost
        reactHost = reactApp.reactHost

    }
}

Library build.gradle.kts

import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.gradle.jvm.toolchain.JavaLanguageVersion

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("com.facebook.react")
    id("com.callstack.react.brownfield")
    `maven-publish`
}

reactBrownfield {
    /**
     * This will be available from `com.callstack.react.brownfield` version > 3.0.0
     * It takes care of linking expo dependencies like expo-image with your AAR module.
     */
    isExpo = true
}

react {
    autolinkLibrariesWithApp()
}

android {
    namespace = "com.bmb.demorn"
    compileSdk = 35

    defaultConfig {
        minSdk = 24

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
        
        buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", properties["newArchEnabled"].toString())
        buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString())
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}

dependencies {
    
    api("com.facebook.react:react-android:0.79.6")
    api("com.facebook.react:hermes-android:0.79.6")

    implementation("androidx.core:core-ktx:1.17.0")
    implementation("androidx.appcompat:appcompat:1.7.1")
    implementation("com.google.android.material:material:1.12.0")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.3.0")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")

}

// ... rest of the build configuration

Build Script

set -e

trap 'echo "Build interrupted"; exit 1' INT

pushd android
./gradlew clean
./gradlew demorn:bundleReleaseAar
./gradlew demorn:bundleDebugAar

echo -e "✅ AARs built successfully: \n android/react-brownfield/build/outputs/aar/react-brownfield-debug.arr \n android/react-brownfield/build/outputs/aar/react-brownfield-release.arr"

 ./gradlew publishToMavenLocal

echo "✅ AARs published to mavenLocal: com.callstack.react:react-brownfield"

Additional Information

  • I followed the guide at https://www.rockjs.dev/docs/brownfield/android strictly and manually (npm create rock gives me errors when generating the AAR package)
  • I had to add the following modifications to build.gradle.kts, otherwise the AAR package wouldn't be generated:
afterEvaluate {
    tasks.named("releaseSourcesJar") {
        dependsOn("copyAutolinkingSources")
        dependsOn("generateCodegenArtifactsFromSchema")
    }
    
    // Handle debug sources jar if it exists
    tasks.findByName("debugSourcesJar")?.let { task ->
        task.dependsOn("copyAutolinkingSources")
        task.dependsOn("generateCodegenArtifactsFromSchema")
    }
}

Environment

  • React Native: 0.79.6
  • Expo CLI with bare-minimum template
  • Android compileSdk: 36
  • minSdk: 24
  • Kotlin
  • New Architecture: Enabled

The error seems to be related to missing expo.modules.ExpoModulesPackageList class during initialization. Any guidance on how to resolve this would be greatly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions