# 1.32.0

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

## 🔨 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

```bash
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:

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

<admonition type="info">

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.

</admonition>

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

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

<admonition type="info">
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.
</admonition>

### 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:

```groovy title="android/app/build.gradle"
dependencies {
    implementation (project(':react-native-callkeep')) // [!code --]
    // ...
}
```

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.

<Tabs>

```objc title="AppDelegate.mm" label="Objective-C"
#import "RNCallKeep.h" // [!code --]
#import "RNVoipPushNotificationManager.h" // [!code --]

//...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSString *localizedAppName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"]; // [!code --]
  NSString *appName = [[[NSBundle mainBundle] infoDictionary]objectForKey :@"CFBundleDisplayName"]; // [!code --]
  [RNCallKeep setup:@{ // [!code --]
    @"appName": localizedAppName != nil ? localizedAppName : appName, // [!code --]
    @"supportsVideo": @YES, // [!code --]
    @"includesCallsInRecents": @NO, // [!code --]
  }]; // [!code --]

  [RNVoipPushNotificationManager voipRegistration]; // [!code --]
  [StreamVideoReactNative voipRegistration]; // [!code ++]
  // the rest
}

// ...
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type]; // [!code --]
  [StreamVideoReactNative didUpdatePushCredentials:credentials forType:(NSString *)type]; // [!code ++]
}

//...
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
  NSDictionary *stream = payload.dictionaryPayload[@"stream"]; // [!code --]
  NSString *uuid = [[NSUUID UUID] UUIDString]; // [!code --]
  NSString *createdByDisplayName = stream[@"created_by_display_name"]; // [!code --]
  NSString *cid = stream[@"call_cid"]; // [!code --]
  NSString *videoIncluded = stream[@"video"]; // [!code --] // [!code --]
  BOOL hasVideo = [videoIncluded isEqualToString:@"false"] ? NO : YES; // [!code --]

  [StreamVideoReactNative registerIncomingCall:cid uuid:uuid]; // [!code --]

  // set the completion handler - this one is called by the JS SDK
  [RNVoipPushNotificationManager addCompletionHandler:cid completionHandler:completion]; // [!code --]

  // send event to JS - the JS SDK will handle the rest and call the 'completionHandler'
  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; // [!code --]

  // display the incoming call notification
  [RNCallKeep reportNewIncomingCall: uuid // [!code --]
                             handle: createdCallerName // [!code --]
                         handleType: @"generic" // [!code --]
                           hasVideo: hasVideo // [!code --]
                localizedCallerName: createdCallerName // [!code --]
                    supportsHolding: NO // [!code --]
                       supportsDTMF: NO // [!code --]
                   supportsGrouping: NO // [!code --]
                 supportsUngrouping: NO // [!code --]
                        fromPushKit: YES // [!code --]
                            payload: stream // [!code --]
              withCompletionHandler: nil]; // [!code --]
  [StreamVideoReactNative didReceiveIncomingPush:payload forType:(NSString *)type completionHandler:completion]; // [!code ++]
}
```

```swift title="AppDelegate.swift" label="Swift"
import RNCallKeep // [!code --]
import RNVoipPushNotification // [!code --]

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

// ...
func pushRegistry(
  _ registry: PKPushRegistry,
  didUpdate credentials: PKPushCredentials,
  for type: PKPushType
) {
  RNVoipPushNotificationManager.didUpdate(credentials, forType: type.rawValue) // [!code --]
  StreamVideoReactNative.didUpdate(credentials, forType: type.rawValue) // [!code ++]
}

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

  let shouldReject = StreamVideoReactNative.shouldRejectCallWhenBusy() // [!code --]
  let hasAnyActiveCall = StreamVideoReactNative.hasAnyActiveCall() // [!code --]

  if shouldReject && hasAnyActiveCall { // [!code --]
      completion() // [!code --]
      return // [!code --]
  } // [!code --]

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

  StreamVideoReactNative.registerIncomingCall(cid, uuid: uuid) // [!code --]

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

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

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

</Tabs>

### Expo setup

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

```bash
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`:

```json title="app.json"
{
  "expo": {
    "plugins": [
      "@config-plugins/react-native-callkeep" // [!code --]
      // ... other plugins
    ]
  }
}
```

After all dependency changes, regenerate your native projects:

```bash
npx expo prebuild --clean
```

<admonition type="info">

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.

</admonition>

### 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](/video/docs/react-native/incoming-calls/ringing-setup/react-native/#setup-the-push-notifications-configuration-for-the-sdk) 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](/video/docs/react-native/incoming-calls/ringing-setup/react-native/) 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+)

<admonition type="info">

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

</admonition>

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.

```ts title="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`:

```swift title="AppDelegate.swift"
private func pushRegistry(
  _ registry: PKPushRegistry,
  didReceiveIncomingVoIPPushWith payload: PKPushPayload,
  metadata: AnyObject,
  withCompletionHandler completion: @escaping () -> Void
) {
  StreamVideoReactNative.didReceiveIncomingVoIPPush(
    payload,
    metadata: metadata,
    completionHandler: completion
  )
}
```

<admonition type="note">

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.

</admonition>


---

This page was last updated at 2026-05-26T09:07:23.372Z.

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react-native/migration-guides/1.32.0/](https://getstream.io/video/docs/react-native/migration-guides/1.32.0/).