Skip to content

Commit

Permalink
fix(react-native)!: make notifee to be optional (#1456)
Browse files Browse the repository at this point in the history
- [x] update codebase of the SDK
- [x] documentation changes
- [ ] test on android 24 emulator without notifee installed

---------

Co-authored-by: Oliver Lazoroski <[email protected]>
  • Loading branch information
santhoshvai and oliverlaz authored Aug 8, 2024
1 parent 23c0758 commit 0b3f787
Show file tree
Hide file tree
Showing 22 changed files with 202 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Stream Video React Native SDK requires installing some peer dependencies to prov
```bash title=Terminal
yarn add @stream-io/react-native-webrtc \
react-native-incall-manager react-native-svg \
@react-native-community/netinfo @notifee/react-native
@react-native-community/netinfo
npx pod-install
```

Expand All @@ -35,7 +35,6 @@ So what did we install precisely?
- `react-native-incall-manager` handles media-routes/sensors/events during an audio/video call.
- `react-native-svg` provides SVG support to React Native, SVRN's components and it's icons are reliant on this dependency.
- `@react-native-community/netinfo` - is used to detect the device's connectivity state, type and quality.
- `@notifee/react-native` - is used to keep calls alive in the background on Android.

### Android Specific installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ npx expo install @config-plugins/react-native-webrtc
npx expo install react-native-incall-manager
npx expo install react-native-svg
npx expo install @react-native-community/netinfo
npx expo install @notifee/react-native
```

So what did we install precisely?
Expand All @@ -33,7 +32,6 @@ So what did we install precisely?
- `react-native-incall-manager` handles media-routes/sensors/events during an audio/video call.
- `react-native-svg` provides SVG support to React Native, SVRN's components and it's icons are reliant on this dependency.
- `@react-native-community/netinfo` - is used to detect the device's connectivity state, type and quality.
- `@notifee/react-native` - is used to keep calls alive in the background on Android.

### Android Specific installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,32 @@ Head over to the documentation [here](../../advanced/pip/) on how to picture-in-

### Android 7

There is no support for Picture-in-picture (PiP) mode below Android 8. Hence in those platforms, we use a [foreground service](https://developer.android.com/guide/components/foreground-services) to keep the call alive. The SDK will manage the foreground service. In Expo, the config plugin adds it up. But if Expo is not used, be sure to add the following:

#### Add declarations in AndroidManifest
There is no support for Picture-in-picture (PiP) mode below Android 8. Hence in those platforms, we use a [foreground service](https://developer.android.com/guide/components/foreground-services) to keep the call alive. The SDK will automatically create and manage the foreground service. The only requirement is to install the `Notifee` library so that SDK can handle a foreground service. To install the [`Notifee`](https://github.com/invertase/notifee) library, run the following command in your terminal of choice:

<Tabs groupId="current-platform" queryString>
<TabItem value="expo" label="Expo">

In Expo, declarations in Android Manifest are automatically done by the expo config plugin of the SDK, so nothing needs to be added manually.
```bash title=Terminal
npx expo install @notifee/react-native
```

</TabItem>
<TabItem value="reactnative" label="React Native">

Add the following in `AndroidManifest.xml`:

```xml title="AndroidManifest.xml"
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- We declare the permissions to for using foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<service
android:name="app.notifee.core.ForegroundService"
tools:replace="android:foregroundServiceType"
android:stopWithTask="true"
android:foregroundServiceType="dataSync" />
```bash title=Terminal
yarn add @notifee/react-native
npx pod-install
```

</TabItem>
</Tabs>


#### Optional: override the default configuration of the notifications
#### Optional: override the default configuration of the foreground service notifications

You can also optionally override the default configuration of the notification used by the SDK. Below we give an example of that:

```ts
import { StreamVideoRN } from '@stream-io/video-react-native-sdk';
import { AndroidImportance } from '@notifee/react-native';

StreamVideoRN.updateConfig({
foregroundService: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn add @react-native-firebase/app
yarn add @react-native-firebase/messaging
yarn add react-native-callkeep
yarn add react-native-voip-push-notification
yarn add @notifee/react-native
npx pod-install
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ npx expo install @react-native-firebase/app
npx expo install @react-native-firebase/messaging
npx expo install react-native-voip-push-notification
npx expo install react-native-callkeep
npx expo install @notifee/react-native
```

So what did we install precisely?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Please follow the below guides for adding appropriate push providers to Stream:
yarn add @react-native-firebase/app
yarn add @react-native-firebase/messaging
yarn add @react-native-community/push-notification-ios
yarn add @notifee/react-native
npx pod-install
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Please follow the below guides for adding appropriate push providers to Stream:
```bash title=Terminal
npx expo install expo-notifications
npx expo install expo-task-manager
npx expo install @notifee/react-native
```

So what did we install precisely?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ To be able to use the foreground service, the permission must be declared in the
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- We declare the permissions to for using foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
// highlight-next-line
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const getMainActivityOrThrow = AndroidConfig.Manifest.getMainActivityOrThrow;
const sampleManifestPath = getFixturePath('AndroidManifest.xml');

const props: ConfigProps = {
ringingPushNotifications: {
disableVideoIos: false,
},
androidPictureInPicture: {
enableAutomaticEnter: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('withStreamVideoReactNativeSDKAndroidPermissions', () => {
};
const props: ConfigProps = {
enableScreenshare: true,
ringingPushNotifications: { disableVideoIos: false },
};

const updatedConfig = withStreamVideoReactNativeSDKAndroidPermissions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ type ManifestService = Unpacked<
>;

function getNotifeeService() {
/* Example:
/* We add this service to the AndroidManifest.xml:
<service
android:name="app.notifee.core.ForegroundService"
android:stopWithTask="true"
android:foregroundServiceType="mediaProjection" />
android:foregroundServiceType="dataSync" />
*/
const foregroundServiceType = 'dataSync';
let head = prefixAndroidKeys({
Expand All @@ -40,10 +39,10 @@ const withStreamVideoReactNativeSDKManifest: ConfigPlugin<ConfigProps> = (
props
) => {
return withAndroidManifest(configuration, (config) => {
try {
const androidManifest = config.modResults;
const mainApplication = getMainApplicationOrThrow(androidManifest);
/* Add the notifee Service */
const androidManifest = config.modResults;
const mainApplication = getMainApplicationOrThrow(androidManifest);
if (props?.ringingPushNotifications) {
/* Add the notifee foreground Service */
let services = mainApplication.service ?? [];
// we filter out the existing notifee service (if any) so that we can override it
services = services.filter(
Expand All @@ -52,39 +51,32 @@ const withStreamVideoReactNativeSDKManifest: ConfigPlugin<ConfigProps> = (
);
services.push(getNotifeeService());
mainApplication.service = services;
}

if (props?.androidPictureInPicture) {
const mainActivity = getMainActivityOrThrow(androidManifest);
('keyboard|keyboardHidden|orientation|screenSize|uiMode');
const currentConfigChangesArray = mainActivity.$[
'android:configChanges'
]
? mainActivity.$['android:configChanges'].split('|')
: [];
const neededConfigChangesArray =
'screenSize|smallestScreenSize|screenLayout|orientation'.split('|');
// Create a Set from the two arrays.
const set = new Set([
...currentConfigChangesArray,
...neededConfigChangesArray,
]);
const mergedConfigChanges = [...set];
mainActivity.$['android:configChanges'] = mergedConfigChanges.join('|');
mainActivity.$['android:supportsPictureInPicture'] = 'true';
}
if (props?.androidPictureInPicture) {
const mainActivity = getMainActivityOrThrow(androidManifest);
('keyboard|keyboardHidden|orientation|screenSize|uiMode');
const currentConfigChangesArray = mainActivity.$['android:configChanges']
? mainActivity.$['android:configChanges'].split('|')
: [];
const neededConfigChangesArray =
'screenSize|smallestScreenSize|screenLayout|orientation'.split('|');
// Create a Set from the two arrays.
const set = new Set([
...currentConfigChangesArray,
...neededConfigChangesArray,
]);
const mergedConfigChanges = [...set];
mainActivity.$['android:configChanges'] = mergedConfigChanges.join('|');
mainActivity.$['android:supportsPictureInPicture'] = 'true';
}

if (props?.ringingPushNotifications?.showWhenLockedAndroid) {
const mainActivity = getMainActivityOrThrow(androidManifest);
mainActivity.$['android:showWhenLocked'] = 'true';
mainActivity.$['android:turnScreenOn'] = 'true';
}
config.modResults = androidManifest;
} catch (error: any) {
console.log(error);
throw new Error(
'Cannot setup StreamVideoReactNativeSDK because the AndroidManifest is malformed'
);
if (props?.ringingPushNotifications?.showWhenLockedAndroid) {
const mainActivity = getMainActivityOrThrow(androidManifest);
mainActivity.$['android:showWhenLocked'] = 'true';
mainActivity.$['android:turnScreenOn'] = 'true';
}
config.modResults = androidManifest;
return config;
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,29 @@ import { ConfigProps } from './common/types';
const withStreamVideoReactNativeSDKAndroidPermissions: ConfigPlugin<
ConfigProps
> = (configuration, props) => {
const foregroundServicePermissions = [
'android.permission.FOREGROUND_SERVICE',
'android.permission.FOREGROUND_SERVICE_DATA_SYNC',
];
if (props?.enableScreenshare) {
foregroundServicePermissions.push(
'android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION'
);
}
const config = AndroidConfig.Permissions.withPermissions(configuration, [
'android.permission.POST_NOTIFICATIONS',
...foregroundServicePermissions,
const permissions = [
'android.permission.BLUETOOTH',
'android.permission.BLUETOOTH_CONNECT',
'android.permission.BLUETOOTH_ADMIN',
]);
];
if (props?.ringingPushNotifications || props?.enableScreenshare) {
permissions.push(
'android.permission.POST_NOTIFICATIONS',
'android.permission.FOREGROUND_SERVICE'
);
if (props?.ringingPushNotifications) {
permissions.push('android.permission.FOREGROUND_SERVICE_DATA_SYNC');
}
if (props?.enableScreenshare) {
permissions.push(
'android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION'
);
}
}
const config = AndroidConfig.Permissions.withPermissions(
configuration,
permissions
);
return config;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,33 @@ const withStreamVideoReactNativeSDKMainActivity: ConfigPlugin<ConfigProps> = (
return withMainActivity(configuration, (config) => {
const isMainActivityJava = config.modResults.language === 'java';

try {
config.modResults.contents = addImports(
config.modResults.contents = addImports(
config.modResults.contents,
[
'com.streamvideo.reactnative.StreamVideoReactNative',
'android.os.Build',
'android.util.Rational',
'androidx.lifecycle.Lifecycle',
'android.app.PictureInPictureParams',
'com.oney.WebRTCModule.WebRTCModuleOptions',
],
isMainActivityJava
);
config.modResults.contents = addOnPictureInPictureModeChanged(
config.modResults.contents,
isMainActivityJava
);
if (props?.androidPictureInPicture?.enableAutomaticEnter) {
config.modResults.contents = addOnUserLeaveHint(
config.modResults.contents,
[
'com.streamvideo.reactnative.StreamVideoReactNative',
'android.os.Build',
'android.util.Rational',
'androidx.lifecycle.Lifecycle',
'android.app.PictureInPictureParams',
'com.oney.WebRTCModule.WebRTCModuleOptions',
],
isMainActivityJava
);
config.modResults.contents = addOnPictureInPictureModeChanged(
}
if (props?.enableScreenshare) {
config.modResults.contents = addInsideOnCreate(
config.modResults.contents,
isMainActivityJava
);
if (props?.androidPictureInPicture?.enableAutomaticEnter) {
config.modResults.contents = addOnUserLeaveHint(
config.modResults.contents,
isMainActivityJava
);
}
if (props?.enableScreenshare) {
config.modResults.contents = addInsideOnCreate(
config.modResults.contents,
isMainActivityJava
);
}
} catch (error: any) {
throw new Error(
"Cannot add StreamVideoReactNativeSDK to the project's MainApplication because it's malformed."
);
}

return config;
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native-sdk/jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jest.mock('react-native/Libraries/Utilities/Platform', () => ({
OS: 'ios',
select: jest.fn((selector) => selector.ios),
Version: '16.2',
constants: {
osVersion: '16.2',
systemName: 'iOS',
},
}));

// Mock the notifee module using the mock provided by @notifee/react-native itself
Expand Down
5 changes: 4 additions & 1 deletion packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build:expo-plugin": "rimraf expo-config-plugin/dist && tsc --project expo-config-plugin/tsconfig.json",
"build": "yarn clean && yarn copy-version && bob build && yarn build:expo-plugin",
"test:expo-plugin": "jest expo-config-plugin --coverage",
"test": "jest --coverage && yarn test:expo-plugin",
"test": "yarn copy-version && jest --coverage && yarn test:expo-plugin",
"copy-version": "echo \"export const version = '$npm_package_version';\" > ./src/version.ts"
},
"files": [
Expand Down Expand Up @@ -75,6 +75,9 @@
"react-native-voip-push-notification": ">=3.3.1"
},
"peerDependenciesMeta": {
"@notifee/react-native": {
"optional": true
},
"@react-native-community/push-notification-ios": {
"optional": true
},
Expand Down
Loading

0 comments on commit 0b3f787

Please sign in to comment.