1.32.0

Ringing functionality has been revamped bringing calling experience with CallKit and Telecom integration.

Parts of this guide (in particular the native iOS AppDelegate integration using StreamVideoReactNative.didUpdate(...), .didReceiveIncomingPush(...), and .didReceiveIncomingVoIPPush(...)) were superseded by the 1.37.0 migration guide. Those native APIs were removed in 1.37.0. If you are on 1.37.0 or newer, follow that guide instead.

🔨 What changed?

The SDK does not depend on react-native-callkeep for CallKit integration and on react-native-voip-push-notification for handling VoIP push notifications. From now we're using internal package which provides CallKit and Android Telecom integration for both platforms - @stream-io/react-native-callingx.

💡 How to migrate?

Setup

Remove the react-native-callkeep library from your app's dependencies

npm uninstall react-native-callkeep react-native-voip-push-notification
# or using yarn
yarn remove react-native-callkeep react-native-voip-push-notification

Optional: remove @notifee/react-native from your dependencies if it was used only for the ringing flow:

npm uninstall @notifee/react-native
# or using yarn
yarn remove @notifee/react-native

This step is optional. @notifee/react-native is not required for ringing functionality to work with @stream-io/react-native-callingx.

If your app still uses @notifee/react-native for other notifications, keep it installed and skip this step. You should remove it only when it was used exclusively for the old ringing flow.

Install the @stream-io/react-native-callingx library

npm install @stream-io/react-native-callingx
# or using yarn
yarn add @stream-io/react-native-callingx

The ringing flow with @stream-io/react-native-callingx requires @stream-io/react-native-webrtc version 137.1.2 or higher. Please make sure to update the dependency if you are using an older version.

React Native CLI setup

After installing dependencies, run npx pod-install to link the new native modules on iOS.

If your Android project has a manual implementation dependency on react-native-callkeep in your build.gradle, you need to remove it:

android/app/build.gradle
dependencies {
    implementation (project(':react-native-callkeep')) 
    // ...
}

You also need to make changes to your AppDelegate file. Below you will see exact changes that need to be done:

Note: If your AppDelegate previously had CXProvider delegate methods (didActivateAudioSession / didDeactivateAudioSession) for CallKit audio session handling, these are now managed internally by @stream-io/react-native-callingx and can be removed.

AppDelegate.swift
import RNCallKeep
import RNVoipPushNotification

// ...
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
  let localizedAppName = Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String
  let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as! String
  RNCallKeep.setup([ 
    "appName": localizedAppName ?? appName, 
    "supportsVideo": true, 
    "includesCallsInRecents": false, 
  ]) 
  RNVoipPushNotificationManager.voipRegistration() 
  StreamVideoReactNative.voipRegistration() 
  // the rest
}

// ...
func pushRegistry(
  _ registry: PKPushRegistry,
  didUpdate credentials: PKPushCredentials,
  for type: PKPushType
) {
  RNVoipPushNotificationManager.didUpdate(credentials, forType: type.rawValue) 
  StreamVideoReactNative.didUpdate(credentials, forType: type.rawValue) 
}

// ...
func pushRegistry(
  _ registry: PKPushRegistry,
  didReceiveIncomingPushWith payload: PKPushPayload,
  for type: PKPushType,
  completion: @escaping () -> Void
) {
  // process the payload
  guard
      let stream = payload.dictionaryPayload["stream"] as? [String: Any], 
      let createdCallerName = stream["created_by_display_name"] as? String, 
      let cid = stream["call_cid"] as? String
  else { 
      completion() 
      return
  } 

  let shouldReject = StreamVideoReactNative.shouldRejectCallWhenBusy() 
  let hasAnyActiveCall = StreamVideoReactNative.hasAnyActiveCall() 

  if shouldReject && hasAnyActiveCall { 
      completion() 
      return
  } 

  let uuid = UUID().uuidString 
  let videoIncluded = stream["video"] as? String
  let hasVideo = videoIncluded == "false" ? false : true

  StreamVideoReactNative.registerIncomingCall(cid, uuid: uuid) 

  // set the completion handler - this one is called by the JS SDK
  RNVoipPushNotificationManager.addCompletionHandler(cid, completionHandler: completion) 

  // send event to JS - the JS SDK will handle the rest and call the 'completionHandler'
  RNVoipPushNotificationManager.didReceiveIncomingPush( 
      with: payload, 
      forType: type.rawValue
  ) 

  // display the incoming call notification
  RNCallKeep.reportNewIncomingCall( 
      uuid, 
      handle: createdCallerName, 
      handleType: "generic", 
      hasVideo: hasVideo, 
      localizedCallerName: createdCallerName, 
      supportsHolding: false, 
      supportsDTMF: false, 
      supportsGrouping: false, 
      supportsUngrouping: false, 
      fromPushKit: true, 
      payload: stream, 
      withCompletionHandler: nil
  ) 
  StreamVideoReactNative.didReceiveIncomingPush(payload, forType: type.rawValue, completionHandler: completion) 
}

Expo setup

Remove the @config-plugins/react-native-callkeep package:

npm uninstall @config-plugins/react-native-callkeep
# or using yarn
yarn remove @config-plugins/react-native-callkeep

Remove @config-plugins/react-native-callkeep from the plugins array in your app.json:

app.json
{
  "expo": {
    "plugins": [
      "@config-plugins/react-native-callkeep"
      // ... other plugins
    ]
  }
}

After all dependency changes, regenerate your native projects:

npx expo prebuild --clean

Expo users do not need to make any native iOS or Android changes manually. The @stream-io/video-react-native-sdk package includes an Expo config plugin that handles the native setup automatically.

Usage

From the usage perspective there are no significant changes - you still need to call StreamVideoRN.setPushConfig. CallKit/Telecom integration will work with no changes in configurations that are passed to StreamVideoRN.setPushConfig method by using default parameters.

However there some changes in StreamVideoConfig type itself. For exploring new parameters please check push notification configuration section. Below you'll see the list of fields that are deprecated and not supported from this version:

  • push.android.incomingCallChannel
  • push.android.incomingCallNotificationTextGetters
  • push.navigateAcceptCall
  • push.navigateToIncomingCall

Please visit our Ringing Setup page for more detailed information.

✨ New Features

  • CallKit support for both incoming and outgoing calls
  • CallKit muting capabilities
  • Android Telecom support for both incoming and outgoing calls
  • Android system unified notifications for displaying incoming/outgoing calls

Skip ringing notification when the app is in the foreground (Android & iOS 26.4+)

skipIncomingPushInForeground and the optional iOS PushKit delegate below require @stream-io/video-react-native-sdk 1.36.0 or newer.

A new push option, skipIncomingPushInForeground, hides the system incoming-call notification when the app is already in the foreground, so the app can show its own ringing UI instead. Background pushes are unaffected.

setPushConfig.ts
StreamVideoRN.setPushConfig({
  ios: {
    skipIncomingPushInForeground: true,
  },
  android: {
    skipIncomingPushInForeground: true,
  },
});

Works on every supported Android version. On iOS, requires iOS 26.4+ (no-op on older versions) and a new PushKit delegate in your AppDelegate.swift:

AppDelegate.swift
@available(iOS 26.4, *)
public func pushRegistry(
  _ registry: PKPushRegistry,
  didReceiveIncomingVoIPPushWith payload: PKPushPayload,
  metadata: AnyObject,
  withCompletionHandler completion: @escaping () -> Void
) {
  StreamVideoReactNative.didReceiveIncomingVoIPPush(
    payload,
    metadata: metadata,
    completionHandler: completion
  )
}

Expo apps do not need to add this delegate by hand — the Stream Video SDK config plugin injects it automatically when ringing: true is set.