Skip to content

Commit

Permalink
chore(llc): regular push notifications handling (#762)
Browse files Browse the repository at this point in the history
* custom data docs

* description added

* title fix

* tweaks

* regular push notification handling

* tweaks

* tweaks

* typos fix
  • Loading branch information
Brazol authored Sep 17, 2024
1 parent 3789be0 commit 20a9aa2
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 40 deletions.
5 changes: 0 additions & 5 deletions docusaurus/docs/Flutter/05-advanced/02-ringing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ allows you to add an immersive calling experience to your application.

Ringing requires push/VoIP notifications to be sent to your device. Stream Video sends push/VoIP notifications to members that have at least one registered device.

Push notifications are sent in the following scenarios:
- you create a call with the `ring` value set to true. In this case, a notification that shows a ringing screen is sent.
- you create a call with the `notify` value set to true. In this case, a regular push notification is sent.
- you haven't answered a call. In this case, a missed call notification is sent (regular push notification).

To receive push notifications from Stream Video, you'll need to:

1. Configure your push notification provider on the Stream Dashboard.
Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Flutter/05-advanced/03-screen-sharing.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: screen_sharing
sidebar_position: 3
sidebar_position: 4
title: Screen Sharing
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: picture_in_picture
sidebar_position: 4
sidebar_position: 5
title: Picture in Picture (PiP)
---

Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Flutter/05-advanced/05-call-recording.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
slug: /recording
sidebar_position: 5
sidebar_position: 6
title: Recording
---
A key feature of modern communication tools is the ability to quickly and easily record calls. This functionality is used for everything from quality assurance and training to legal compliance or simply as a matter of convenience for keeping track of conversations and later reviewing them.
Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Flutter/05-advanced/06-screenshots.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
slug: /screenshots
sidebar_position: 6
sidebar_position: 7
title: Screenshots
---
You can take a picture of a VideoTrack at highest possible resolution. This can be useful for example if you want to take a screenshot of a screenshare at full resolution.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Background modes
slug: /background-modes
sidebar_position: 7
sidebar_position: 8
description: How to keep the call alive in the background
---

Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Flutter/05-advanced/08-custom-data.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Custom Data
slug: /custom-data
sidebar_position: 8
sidebar_position: 9
description: Learn how to add and read custom data in the Stream Video Flutter SDK.
---

Expand Down
163 changes: 163 additions & 0 deletions docusaurus/docs/Flutter/05-advanced/09-push-notifications.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
title: Push Notifications
slug: /push-notifications
sidebar_position: 3
description: Learn how to enable push notifications in the Stream Video Flutter SDK.
---

Apart from the VoIP notifications for call ringing features, Stream Video Flutter SDK also supports standard push notifications. This guide will show you how to enable push notifications in your Flutter app using the Stream Video SDK.

Push notifications are sent in the following scenarios:
- you create a call with the `ring` value set to true. In this case, a notification that shows a ringing screen is sent. (covered in the [ringing documentation](https://getstream.io/video/docs/flutter/advanced/ringing_and_callkit/))
- you create a call with the `notify` value set to true. In this case, a regular push notification is sent.
- you haven't answered a call. In this case, a missed call notification is sent (regular push notification).

## Android and Firebase Cloud Messaging (FCM)

For FCM the steps taken in [ringing documentation](https://getstream.io/video/docs/flutter/advanced/ringing_and_callkit/) are enough and will also handle standard push notifications.

In case of missed call the notification will be shown using [flutter_callkit_incoming](https://pub.dev/packages/flutter_callkit_incoming) package. It can be configured by `pushParams` when initializing `StreamVideo`:

```dart
StreamVideo(
apiKey,
user: user,
pushNotificationManagerProvider: StreamVideoPushNotificationManager.create(
iosPushProvider: const StreamVideoPushProvider.apn(
name: 'apn',
),
androidPushProvider: const StreamVideoPushProvider.firebase(
name: 'firebase',
),
pushParams: const StreamVideoPushParams(
appName: kAppName,
ios: IOSParams(iconName: 'IconMask'),
missedCallNotification: NotificationParams(
showNotification: true,
subtitle: 'Missed Call',
callbackText: 'Call Back',
)
),
),
);
```

If you want to handle missed call notification differently or handle the notification about incoming call (when `notify` is set to true) use the existing `_handleRemoteMessage()` method (see [ringing documentation](https://getstream.io/video/docs/flutter/advanced/ringing_and_callkit/)):

```dart
Future<bool> _handleRemoteMessage(RemoteMessage message) async {
final payload = message.data;
final sender = payload['sender'] as String?;
final type = payload['type'] as String?;
if (sender == 'stream.video' && type == 'call.notification') {
final callCid = payload['call_cid'] as String?;
// Show notification, for example using `flutter_local_notifications` package
}
final streamVideo = locator.get<StreamVideo>();
return streamVideo.handleVoipPushNotification(
message.data,
handleMissedCall: false, //<-- Add this flag if you dont want a default missed call notification handling
);
}
```

## iOS and Apple Push Notification Service (APNs)

For APNs the standard push notifications have to be handled separately of VoIP notifications. When APN push provider is registered for iOS, both VoIP and standard push notifications are send using APN by Stream Video SDK.

### Registering APN device token

First you need to register APN device token as it is separate from the VoIP token (registered out-of-the-box by the Stream Video SDK). To do this just set `registerApnDeviceToken` to true when initializing `StreamVideo` instance:

```dart
StreamVideo(
apiKey,
user: user,
pushNotificationManagerProvider: StreamVideoPushNotificationManager.create(
iosPushProvider: const StreamVideoPushProvider.apn(
name: 'flutter-apn',
),
androidPushProvider: const StreamVideoPushProvider.firebase(
name: 'flutter-firebase',
),
pushParams: const StreamVideoPushParams(
appName: kAppName,
ios: StreamIOSParams(iconName: 'IconMask'),
),
registerApnDeviceToken: true, // <--- Add this line
),
);
```

### Handling standard push notifications

Next, you need to handle the push notifications in your app.

To do this, you need to add the following code to your `AppDelegate.swift` file:

```swift
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

// Register for push notifications.
StreamVideoPKDelegateManager.shared.registerForPushNotifications() // <--- Add will only handle VoIP notifications
UNUserNotificationCenter.current().delegate = self // <--- Add this line to handle standard push notifications

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

// This method will be called when notification is received
override func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let streamDict = notification.request.content.userInfo["stream"] as? [String: Any]
if(streamDict?["sender"] as? String != "stream.video") {
return completionHandler([])
}

if #available(iOS 14.0, *) {
completionHandler([.list, .banner, .sound])
} else {
completionHandler([.alert])
}
}
}
```

If you want to handle the notification tap event, for example to navigate to the call screen when you `notify` about it, you can add the following code to your `AppDelegate.swift` file:

```swift
// This method will be called when notification is tapped
override func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {

let streamDict = response.notification.request.content.userInfo["stream"] as? [String: Any]
if(streamDict?["sender"] as? String != "stream.video") {
return;
}

if(streamDict?["type"] as? String == "call.notification") {
let callCid = streamDict?["call_cid"] as? String
print("Call notification received with call cid: \(callCid)")
//Navigate to call, for example implementing method channel
}

completionHandler()
}
```

### Push notification permission

Remember, that in order to receive push notifications, you need to ask the user for relevant permission. One way of doing it is using [permission_handler](https://pub.dev/packages/permission_handler) plugin.

```dart
Permission.notification.request();
```
12 changes: 12 additions & 0 deletions dogfooding/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,16 @@ post_install do |installer|
end
end
end

installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CAMERA=1',
'PERMISSION_MICROPHONE=1',
'PERMISSION_NOTIFICATIONS=1',
]

end
end
end
19 changes: 19 additions & 0 deletions dogfooding/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,26 @@ import stream_video_push_notification

// Register for push notifications.
StreamVideoPKDelegateManager.shared.registerForPushNotifications()
UNUserNotificationCenter.current().delegate = self

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

// This method will be called when notification is received
override func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let streamDict = notification.request.content.userInfo["stream"] as? [String: Any]
if(streamDict?["sender"] as? String != "stream.video") {
return completionHandler([])
}

if #available(iOS 14.0, *) {
completionHandler([.list, .banner, .sound])
} else {
completionHandler([.alert])
}
}


}
2 changes: 2 additions & 0 deletions dogfooding/lib/di/injector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Future<void> _backgroundVoipCallHandler() async {
appName: kAppName,
ios: IOSParams(iconName: 'IconMask'),
),
registerApnDeviceToken: true,
),
);
}
Expand Down Expand Up @@ -207,6 +208,7 @@ StreamVideo _initStreamVideo(
ios: IOSParams(iconName: 'IconMask'),
),
backgroundVoipCallHandler: _backgroundVoipCallHandler,
registerApnDeviceToken: true,
),
);

Expand Down
5 changes: 5 additions & 0 deletions packages/stream_video/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.4

🚧 Breaking changes
* The regular push notification handling has been removed from iOS, providing more control over the implementation. VoIP push notifications will continue to be handled as before. For more details, refer to the documentation.

## 0.5.3

🐞 Fixed
Expand Down
7 changes: 5 additions & 2 deletions packages/stream_video/lib/src/stream_video.dart
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,10 @@ class StreamVideo extends Disposable {
/// Handle incoming VoIP push notifications.
///
/// Returns `true` if the notification was handled, `false` otherwise.
Future<bool> handleVoipPushNotification(Map<String, dynamic> payload) async {
Future<bool> handleVoipPushNotification(
Map<String, dynamic> payload, {
bool handleMissedCall = true,
}) async {
_logger.d(() => '[handleVoipPushNotification] payload: $payload');
final manager = pushNotificationManager;
if (manager == null) {
Expand Down Expand Up @@ -716,7 +719,7 @@ class StreamVideo extends Disposable {
final hasVideo = payload['video'] as String?;

final type = payload['type'] as String?;
if (type == 'call.missed') {
if (handleMissedCall && type == 'call.missed') {
unawaited(
manager.showMissedCall(
uuid: callUUID,
Expand Down
5 changes: 5 additions & 0 deletions packages/stream_video_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.4

🚧 Breaking changes
* The regular push notification handling has been removed from iOS, providing more control over the implementation. VoIP push notifications will continue to be handled as before. For more details, refer to the documentation.

## 0.5.3

🐞 Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,11 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate, UNU
}

@objc public func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter
.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}

pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
pushRegistry?.delegate = self
pushRegistry?.desiredPushTypes = [.voIP]
}

public func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let payloadDict = notification.request.content

if #available(iOS 14.0, *) {
completionHandler([.list, .banner, .sound])
} else {
completionHandler([.alert])
}
}

public func initChannel(mainChannel: FlutterMethodChannel) {
self.mainChannel = mainChannel
}
Expand Down
Loading

0 comments on commit 20a9aa2

Please sign in to comment.