Skip to content

React Native quick start ​

This page is for React Native CLI (bare) apps. Using Expo? The Expo quick start does all the native steps below for you.

Callx supports React Native 0.76 and later, on the New Architecture (as a typed TurboModule) and on the legacy architecture.

1. Install ​

sh
npm install @bear-block/callx
cd ios && pod install

Raise Android's minimum SDK to 29 in android/build.gradle; the React Native template defaults to 24:

groovy
buildscript {
    ext {
        minSdkVersion = 29
    }
}

The package contains native code, so rebuild the app; a JavaScript reload is not enough.

2. Configure the native projects ​

iOS ​

In Xcode, under Signing & Capabilities:

  • Add Push Notifications.
  • Add Background Modes and tick Audio, AirPlay, and Picture in Picture and Voice over IP.

Add a microphone description to Info.plist:

xml
<key>NSMicrophoneUsageDescription</key>
<string>Example uses the microphone for calls.</string>

Android ​

Add the permissions your app requests at runtime to android/app/src/main/AndroidManifest.xml. Callx's own manifest already declares MANAGE_OWN_CALLS, USE_FULL_SCREEN_INTENT and its incoming-call screen.

xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Add Firebase to the Android app (google-services.json, the Google Services Gradle plugin and com.google.firebase:firebase-messaging).

3. Bootstrap the native core ​

The core starts with the process, before any push arrives and before the JavaScript bundle loads.

Android: MainApplication ​

kotlin
// android/app/src/main/java/com/example/calls/MainApplication.kt
import com.google.firebase.messaging.FirebaseMessaging
import dev.callx.reactnative.CallxModule
import dev.callx.telecom.CallxPushTokens

class MainApplication : Application(), ReactApplication {
    override fun onCreate() {
        super.onCreate()
        try {
            CallxModule.bootstrap(this)
            FirebaseMessaging.getInstance().token.addOnSuccessListener(CallxPushTokens::updateFcm)
        } catch (error: Exception) {
            // No Telecom on this device, or a storage error: calling is unavailable.
        }
        // …the rest of the template's onCreate (loadReactNative, etc.)
    }
}

Pass a CallxBootstrapConfig as the second argument to set an accountGeneration, a listener or your own media. See native host integration.

Android: forward FCM messages ​

kotlin
// android/app/src/main/java/com/example/calls/MessagingService.kt
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dev.callx.telecom.CallxBootstrap
import dev.callx.telecom.CallxPushTokens

class MessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        val ingress = CallxBootstrap.started?.ingress
        if (ingress?.handlePush(message.data, message.priority, message.originalPriority) == true) return
        // Not a Callx invitation: handle your app's other messages.
    }

    override fun onNewToken(token: String) = CallxPushTokens.updateFcm(token)
}
xml
<service android:name=".MessagingService" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Using @react-native-firebase/messaging?

Extend its service instead of FirebaseMessagingService, so your other messages still reach JavaScript: class MessagingService : ReactNativeFirebaseMessagingService(), call super.onMessageReceived(message) for non-Callx messages, and replace its manifest entry with yours (tools:node="replace"). The Expo plugin generates exactly this.

iOS: AppDelegate ​

Add two lines at the top of application(_:didFinishLaunchingWithOptions:), before the template starts React Native:

swift
// ios/Example/AppDelegate.swift
import callx_react_native

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
  do {
    try CallxReactNativeHost.bootstrap(CallxBootstrapConfig())
  } catch {
    NSLog("Callx: calling is unavailable: \(error)")
  }
  // …the template's code that starts React Native
}

Callx owns the PKPushRegistry, reports each VoIP push to CallKit before the handler returns, and records the VoIP token for getPushToken().

4. Use it from JavaScript ​

Keep one Callx instance for the whole app, for example in a module or a context provider.

ts
import {Callx} from '@bear-block/callx';

export const callx = new Callx();

export async function startCalling() {
  const capabilities = await callx.setup();
  if (!capabilities.nativeCalling) return; // Bootstrap failed or no Telecom.

  // Send this to your backend so it can push invitations to this device.
  const token = await callx.getPushToken(); // {type: 'voip' | 'fcm', token}
  if (token) await api.registerPushToken(token.type, token.token);

  // Your UI follows the native state.
  return callx.observe(({call}) => {
    if (!call) return showIdle();
    switch (call.state) {
      case 'incoming':   return showIncoming(call);
      case 'connecting': return showConnecting(call);
      case 'active':     return showInCall(call);
      case 'held':       return showOnHold(call);
      case 'ended':      return showEnded(call.endReason);
      case 'outgoing':   return showDialing(call);
    }
  });
}

export async function answer(callId: string) {
  const result = await callx.answer(callId);
  if (result.status !== 'applied') showError(result.error?.message);
}

Ask for the microphone permission (and notifications on Android 13+) while the app is in use, before the first call arrives.

5. Send a test call ​

sh
npx -p @bear-block/callx-testkit callx-push android \
  --service-account firebase-adminsdk.json --token <FCM token>

Kill the app, send the push, and the phone rings. The backend guide has the payloads your server sends in production.

Next steps ​

Optional call overlay and mini-call ​

Callx also exports an optional app UI layer. Incoming stays on Home until accepted, an expanded call overlays navigation, and Back minimizes to an in-app mini-call. The native core remains the call-state owner. See call UI for the exports and integration.

Released under the MIT License. No telemetry, in the library or on this site.