javaandroidreact-nativekotlintwilio

Twilio Voice React Native — App Crashes When Initiating Outgoing Call (`getMainActivityClass()` Returns Null)


After resolving a startup crash, I’m now facing a new crash when attempting to initiate an outgoing call using @twilio/voice-react-native-sdk.

When I call:

Voice.connect(...)

the app crashes with the following exception:

java.lang.NullPointerException
    at java.util.Objects.requireNonNull (Objects.java:233)
    at com.twiliovoicereactnative.VoiceApplicationProxy.getMainActivityClass (VoiceApplicationProxy.java:122)
    at com.twiliovoicereactnative.NotificationUtility.createOutgoingCallNotificationWithLowImportance (NotificationUtility.java:231)
    at com.twiliovoicereactnative.VoiceService.raiseOutgoingCallNotification (VoiceService.java:316)

It appears that getMainActivityClass() returns null during outgoing call notification creation.


Dependencies


📄 Relevant Android Code

MainActivity.kt

class MainActivity : ReactActivity() {
private val activityProxy = VoiceActivityProxy(

    this

) { permission: String -\>

    when (permission) {

        Manifest.permission.RECORD_AUDIO -\> {

            Toast.makeText(

                this@MainActivity,

                "Microphone permissions needed. Please allow in your application settings.",

                Toast.LENGTH_LONG

            ).show()

        }

        Manifest.permission.BLUETOOTH_CONNECT -\> {

            if (Build.VERSION.SDK_INT \>= Build.VERSION_CODES.S) {

                Toast.makeText(

                    this@MainActivity,

                    "Bluetooth permissions needed. Please allow in your application settings.",

                    Toast.LENGTH_LONG

                ).show()

            }

        }

        Manifest.permission.POST_NOTIFICATIONS -\> {

            if (Build.VERSION.SDK_INT \> Build.VERSION_CODES.S_V2) {

                Toast.makeText(

                    this@MainActivity,

                    "Notification permissions needed. Please allow in your application settings.",

                    Toast.LENGTH_LONG

                ).show()

            }

        }

    }

}

override fun onCreate(savedInstanceState: Bundle?) {

    setTheme(R.style.AppTheme)

    super.onCreate(null)

}

override fun getMainComponentName(): String = "main"

override fun createReactActivityDelegate(): ReactActivityDelegate {

    return ReactActivityDelegateWrapper(

        this,

        BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,

        object : DefaultReactActivityDelegate(

            this,

            mainComponentName,

            fabricEnabled

        ) {}

    )

}

override fun invokeDefaultOnBackPressed() {

    if (Build.VERSION.SDK_INT \<= Build.VERSION_CODES.R) {

        if (!moveTaskToBack(false)) {

            super.invokeDefaultOnBackPressed()

        }

        return

    }

    super.invokeDefaultOnBackPressed()

}}

MainReactNativeHost.kt

class MainReactNativeHost(application: Application) : DefaultReactNativeHost(application) {
private val voiceReactNativeHost = object : VoiceApplicationProxy.VoiceReactNativeHost(application) {

    override fun getUseDeveloperSupport(): Boolean = this@MainReactNativeHost.getUseDeveloperSupport()

    override fun getPackages(): List\<ReactPackage\> = this@MainReactNativeHost.getPackages()

    override fun getJSMainModuleName(): String = this@MainReactNativeHost.getJSMainModuleName()

}

override fun getUseDeveloperSupport(): Boolean {

    return BuildConfig.DEBUG

}

override fun getPackages(): List\<ReactPackage\> {

    val packages = PackageList(this).packages

    return packages

}

override fun getJSMainModuleName(): String {

    return "index"

}

fun getVoiceReactNativeHost(): VoiceApplicationProxy.VoiceReactNativeHost {

    return voiceReactNativeHost

}}

MainApplication.kt

class MainApplication : Application(), ReactApplication {
private val innerReactNativeHost = object : DefaultReactNativeHost(this) {

    override fun getPackages() = PackageList(this).packages

    override fun getJSMainModuleName() = ".expo/.virtual-metro-entry"

    override fun getUseDeveloperSupport() = BuildConfig.DEBUG

    override val isNewArchEnabled = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED

    override val isHermesEnabled = BuildConfig.IS_HERMES_ENABLED

}

override val reactNativeHost: ReactNativeHost =

    ReactNativeHostWrapper(this, innerReactNativeHost)

private val voiceApplicationProxy: VoiceApplicationProxy by lazy {

    VoiceApplicationProxy(this)

}

override val reactHost: ReactHost

    get() = DefaultReactHost.getDefaultReactHost(this.applicationContext, innerReactNativeHost)

override fun onCreate() {

    super.onCreate()

    SoLoader.init(this, OpenSourceMergedSoMapping)

    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) load()

    voiceApplicationProxy.onCreate()

    ApplicationLifecycleDispatcher.onApplicationCreate(this)

}

override fun onConfigurationChanged(newConfig: Configuration) {

    super.onConfigurationChanged(newConfig)

    ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)

}}

AndroidManifest.xml

<application
  android:name=".MainApplication"
  android:theme="@style/AppTheme"
  android:supportsRtl="true">

    <activity
      android:name=".MainActivity"
      android:launchMode="singleTask"
      android:exported="true"
      android:theme="@style/Theme.App.SplashScreen">

      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="com.ciptex.clientsdk"/>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

</application>

❓ Question

Is my Android configuration missing something that the Twilio Voice React Native SDK requires?

Or is there a known issue where getMainActivityClass() returns null when running in Expo Prebuild with the New Architecture enabled?

Any guidance, missing configuration, or fixes would be greatly appreciated!


Solution

  • Just sharing a quick update — the app is now running smoothly after updating the AndroidManifest.xml with the required permissions and intent filters.

    Huge thanks to everyone who helped troubleshoot and provided suggestions! 🙌

    Here what i did :

    <activity
    android:name=".MainActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode"
    android:launchMode="singleTask"
    android:windowSoftInputMode="adjustResize"
    android:theme="@style/Theme.App.SplashScreen"
    android:exported="true"
    android:screenOrientation="portrait">
    
    
    <!-- Deep Link Intent Filter -->
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.ciptex.clientsdk"/>
    </intent-filter>
    
    
    <!-- Proper Launcher Intent Filter -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>