From 6941db58a9ff18b67c498b9b00b2308882999f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Tue, 6 Feb 2024 13:25:26 +0100 Subject: [PATCH] feat: added background voip handle called in terminated app state when voip push is received (#570) * fixes push notification cancelation * starting flutter engine from voip push in iOS * minor fixes for pub scoreing * tweaks * cleanup * background voip handler cleanup, dogfooding direct call added * commented closing of ws connection when paused * fixes push notification cancelation * starting flutter engine from voip push in iOS * cleanup * background voip handler cleanup, dogfooding direct call added * commented closing of ws connection when paused * provider changes * dogfooding flutter engine * documentation * linter fixes * Update docusaurus/docs/Flutter/05-advanced/02-ringing.mdx Co-authored-by: Deven Joshi * fixes --------- Co-authored-by: Deven Joshi --- .../docs/Flutter/05-advanced/02-ringing.mdx | 44 ++++++++- dogfooding/lib/app/user_auth_controller.dart | 1 + .../lib/core/repos/app_preferences.dart | 12 ++- dogfooding/lib/di/injector.dart | 39 ++++++++ dogfooding/lib/screens/home_screen.dart | 93 ++++++++++++++++++- .../src/coordinator/coordinator_client.dart | 1 + .../open_api/coordinator_client_open_api.dart | 4 + .../retry/coordinator_client_retry.dart | 3 + .../stream_video/lib/src/stream_video.dart | 1 + .../stream_video_flutter/android/build.gradle | 2 +- .../example/android/build.gradle | 2 +- .../android/build.gradle | 2 +- .../background_example/android/build.gradle | 2 +- .../StreamVideoPKDelegateManager.swift | 29 ++++-- .../StreamVideoPushNotificationPlugin.swift | 14 ++- .../src/stream_video_push_notification.dart | 33 ++++++- ...ideo_push_notification_method_channel.dart | 9 ++ ..._push_notification_platform_interface.dart | 3 + 18 files changed, 269 insertions(+), 25 deletions(-) diff --git a/docusaurus/docs/Flutter/05-advanced/02-ringing.mdx b/docusaurus/docs/Flutter/05-advanced/02-ringing.mdx index 7315c2bb7..534cefdb0 100644 --- a/docusaurus/docs/Flutter/05-advanced/02-ringing.mdx +++ b/docusaurus/docs/Flutter/05-advanced/02-ringing.mdx @@ -378,7 +378,49 @@ say you want to end all calls on the CallKit side, you can end them this way: StreamVideo.instance.pushNotificationManager?.endAllCalls(); ``` -#### Step 6 - Add native code to the iOS project +#### Step 6 - Add callback to handle call in terminated state + +When an iOS app is terminated, the Flutter engine is not running. The engine needs to be started up to handle Stream call events whenever a call is received by the app. The Stream SDK performs the job of running a Flutter engine instance whenever a call is received. However, on the app side, a callback handle needs to be registered that will connect to `StreamVideo`. + +```dart +@pragma('vm:entry-point') +Future _backgroundVoipCallHandler() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Get stored user credentials + var credentials = yourUserCredentialsGetMethod(); + if (credentials == null) return; + + // Initialise StreamVideo + StreamVideo( + // ... + // Make sure you initialise push notification manager + pushNotificationManagerProvider: StreamVideoPushNotificationManager.create( + iosPushProvider: const StreamVideoPushProvider.apn( + name: 'your-ios-provider-name', + ), + androidPushProvider: const StreamVideoPushProvider.firebase( + name: 'your-fcm-provider', + ), + pushParams: const StreamVideoPushParams( + appName: kAppName, + ios: IOSParams(iconName: 'IconMask'), + ), + ), + ); +} +``` + +The `_backgroundVoipCallHandler` method should then be set when StreamVideo is initialised: + +```dart + StreamVideo( + ..., + backgroundVoipCallHandler: _backgroundVoipCallHandler, + ); +``` + +#### Step 7 - Add native code to the iOS project In your iOS project, add the following imports to your `AppDelegate.swift`: diff --git a/dogfooding/lib/app/user_auth_controller.dart b/dogfooding/lib/app/user_auth_controller.dart index dee64bd2d..86f679218 100644 --- a/dogfooding/lib/app/user_auth_controller.dart +++ b/dogfooding/lib/app/user_auth_controller.dart @@ -45,6 +45,7 @@ class UserAuthController extends ChangeNotifier { /// Logs in the given [user] and returns the user credentials. Future login(User user) async { final tokenResponse = await _tokenService.loadToken(userId: user.id); + await _prefs.setApiKey(tokenResponse.apiKey); _authRepo ??= locator.get(param1: user, param2: tokenResponse); diff --git a/dogfooding/lib/core/repos/app_preferences.dart b/dogfooding/lib/core/repos/app_preferences.dart index 1ab2ba39f..f03fc034f 100644 --- a/dogfooding/lib/core/repos/app_preferences.dart +++ b/dogfooding/lib/core/repos/app_preferences.dart @@ -15,6 +15,7 @@ class AppPreferences { final SharedPreferences _prefs; static const String _kUserCredentialsPref = 'user_credentials'; + static const String _kApiKeyPref = 'api_key'; UserCredentials? get userCredentials { final jsonString = _prefs.getString(_kUserCredentialsPref); @@ -24,10 +25,19 @@ class AppPreferences { return UserCredentials.fromJson(json); } + String? get apiKey => _prefs.getString(_kApiKeyPref); + Future setUserCredentials(UserCredentials? credentials) { final jsonString = jsonEncode(credentials?.toJson()); return _prefs.setString(_kUserCredentialsPref, jsonString); } - Future clearUserCredentials() => _prefs.remove(_kUserCredentialsPref); + Future setApiKey(String apiKey) { + return _prefs.setString(_kApiKeyPref, apiKey); + } + + Future clearUserCredentials() async { + return await _prefs.remove(_kUserCredentialsPref) && + await _prefs.remove(_kApiKeyPref); + } } diff --git a/dogfooding/lib/di/injector.dart b/dogfooding/lib/di/injector.dart index e63414f27..153bd1f15 100644 --- a/dogfooding/lib/di/injector.dart +++ b/dogfooding/lib/di/injector.dart @@ -1,5 +1,6 @@ // 📦 Package imports: import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; // 🌎 Project imports: import 'package:flutter_dogfooding/core/repos/app_preferences.dart'; import 'package:flutter_dogfooding/core/repos/user_chat_repository.dart'; @@ -19,6 +20,43 @@ import '../utils/consts.dart'; GetIt locator = GetIt.instance; +@pragma('vm:entry-point') +Future _backgroundVoipCallHandler() async { + WidgetsFlutterBinding.ensureInitialized(); + final prefs = await SharedPreferences.getInstance(); + final appPrefs = AppPreferences(prefs: prefs); + + final apiKey = appPrefs.apiKey; + final userCredentials = appPrefs.userCredentials; + + if (apiKey == null || userCredentials == null) { + return; + } + + StreamVideo( + apiKey, + user: User(info: userCredentials.userInfo), + userToken: userCredentials.token.rawValue, + options: const StreamVideoOptions( + logPriority: Priority.info, + muteAudioWhenInBackground: true, + muteVideoWhenInBackground: true, + ), + pushNotificationManagerProvider: StreamVideoPushNotificationManager.create( + iosPushProvider: const StreamVideoPushProvider.apn( + name: 'flutter-apn', + ), + androidPushProvider: const StreamVideoPushProvider.firebase( + name: 'flutter-firebase', + ), + pushParams: const StreamVideoPushParams( + appName: kAppName, + ios: IOSParams(iconName: 'IconMask'), + ), + ), + ); +} + /// This class is responsible for registering dependencies /// and injecting them into the app. class AppInjector { @@ -160,6 +198,7 @@ StreamVideo _initStreamVideo( appName: kAppName, ios: IOSParams(iconName: 'IconMask'), ), + backgroundVoipCallHandler: _backgroundVoipCallHandler, ), ); diff --git a/dogfooding/lib/screens/home_screen.dart b/dogfooding/lib/screens/home_screen.dart index f6e4f3413..472254f9b 100644 --- a/dogfooding/lib/screens/home_screen.dart +++ b/dogfooding/lib/screens/home_screen.dart @@ -55,7 +55,7 @@ class _HomeScreenState extends State { super.initState(); } - Future _getOrCreateCall() async { + Future _getOrCreateCall({List memberIds = const []}) async { var callId = _callIdController.text; if (callId.isEmpty) callId = generateAlphanumericString(12); @@ -63,7 +63,10 @@ class _HomeScreenState extends State { _call = _streamVideo.makeCall(type: kCallType, id: callId); try { - await _call!.getOrCreate(); + await _call!.getOrCreate( + memberIds: memberIds, + ringing: memberIds.isNotEmpty, + ); } catch (e, stk) { debugPrint('Error joining or creating call: $e'); debugPrint(stk.toString()); @@ -75,6 +78,56 @@ class _HomeScreenState extends State { } } + Future _directCall(BuildContext context) async { + TextEditingController controller = TextEditingController(); + + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Enter ID of user you want to call'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: controller, + decoration: const InputDecoration(hintText: "User id"), + ), + const SizedBox( + height: 8, + ), + Align( + alignment: Alignment.centerRight, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + backgroundColor: const MaterialStatePropertyAll( + Color(0xFF005FFF), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + _getOrCreateCall(memberIds: [controller.text]); + }, + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: Text( + 'Call', + style: TextStyle(color: Colors.white), + ), + ), + ), + ) + ], + ), + ); + }); + } + @override void dispose() { _callIdController.dispose(); @@ -171,9 +224,6 @@ class _HomeScreenState extends State { borderRadius: BorderRadius.circular(8), ), ), - backgroundColor: const MaterialStatePropertyAll( - Color(0xFF005FFF), - ), ), onPressed: _getOrCreateCall, child: const Padding( @@ -182,6 +232,34 @@ class _HomeScreenState extends State { ), ), ), + const SizedBox(height: 8), + Align( + alignment: Alignment.centerLeft, + child: Text( + "Want to directly call someone?", + style: theme.textTheme.bodyMedium?.copyWith( + fontSize: 12, + ), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + onPressed: () => _directCall(context), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: Text('Direct Call'), + ), + ), + ), ], ), ), @@ -213,14 +291,18 @@ class _JoinForm extends StatelessWidget { controller: callIdController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 8), isDense: true, border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), hintText: 'Enter call id', + // suffix button to generate a random call id suffixIcon: IconButton( icon: const Icon(Icons.refresh), + color: Colors.white, + padding: EdgeInsets.zero, onPressed: () { // generate a 10 character nanoId for call id final callId = generateAlphanumericString(10); @@ -256,6 +338,7 @@ class _JoinForm extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 14), child: Text( 'Join call', + style: TextStyle(color: Colors.white), ), ), ); diff --git a/packages/stream_video/lib/src/coordinator/coordinator_client.dart b/packages/stream_video/lib/src/coordinator/coordinator_client.dart index b89bb63f9..4d6065e1d 100644 --- a/packages/stream_video/lib/src/coordinator/coordinator_client.dart +++ b/packages/stream_video/lib/src/coordinator/coordinator_client.dart @@ -19,6 +19,7 @@ import 'models/coordinator_events.dart'; import 'models/coordinator_models.dart' as models; abstract class CoordinatorClient { + bool get isConnected; SharedEmitter get events; Future> connectUser( diff --git a/packages/stream_video/lib/src/coordinator/open_api/coordinator_client_open_api.dart b/packages/stream_video/lib/src/coordinator/open_api/coordinator_client_open_api.dart index 92d64010f..bd535092c 100644 --- a/packages/stream_video/lib/src/coordinator/open_api/coordinator_client_open_api.dart +++ b/packages/stream_video/lib/src/coordinator/open_api/coordinator_client_open_api.dart @@ -86,6 +86,10 @@ class CoordinatorClientOpenApi extends CoordinatorClient { @override SharedEmitter get events => _events; + + @override + bool get isConnected => _ws?.isConnected ?? false; + final _events = MutableSharedEmitterImpl(); final _connectionState = MutableStateEmitterImpl( diff --git a/packages/stream_video/lib/src/coordinator/retry/coordinator_client_retry.dart b/packages/stream_video/lib/src/coordinator/retry/coordinator_client_retry.dart index 794afc2bd..3b04689b2 100644 --- a/packages/stream_video/lib/src/coordinator/retry/coordinator_client_retry.dart +++ b/packages/stream_video/lib/src/coordinator/retry/coordinator_client_retry.dart @@ -116,6 +116,9 @@ class CoordinatorClientRetry extends CoordinatorClient { @override SharedEmitter get events => _delegate.events; + @override + bool get isConnected => _delegate.isConnected; + @override Future> getCall({ required StreamCallCid callCid, diff --git a/packages/stream_video/lib/src/stream_video.dart b/packages/stream_video/lib/src/stream_video.dart index 1781dc936..759cac954 100644 --- a/packages/stream_video/lib/src/stream_video.dart +++ b/packages/stream_video/lib/src/stream_video.dart @@ -400,6 +400,7 @@ class StreamVideo { _logger.d(() => '[onAppState] state: $state'); try { final activeCallCid = _state.activeCall.valueOrNull?.callCid; + if (state.isPaused && activeCallCid == null) { _logger.i(() => '[onAppState] close connection'); _subscriptions.cancel(_idEvents); diff --git a/packages/stream_video_flutter/android/build.gradle b/packages/stream_video_flutter/android/build.gradle index e6642e151..b80390790 100644 --- a/packages/stream_video_flutter/android/build.gradle +++ b/packages/stream_video_flutter/android/build.gradle @@ -2,7 +2,7 @@ group 'io.getstream.video.flutter.stream_video_flutter' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() diff --git a/packages/stream_video_flutter/example/android/build.gradle b/packages/stream_video_flutter/example/android/build.gradle index 1858b31fc..299562902 100644 --- a/packages/stream_video_flutter/example/android/build.gradle +++ b/packages/stream_video_flutter/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.10' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() diff --git a/packages/stream_video_flutter_background/android/build.gradle b/packages/stream_video_flutter_background/android/build.gradle index 61802effd..05f9e54a5 100644 --- a/packages/stream_video_flutter_background/android/build.gradle +++ b/packages/stream_video_flutter_background/android/build.gradle @@ -2,7 +2,7 @@ group 'io.getstream.video.flutter.background.stream_video_flutter_background' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() diff --git a/packages/stream_video_flutter_background/background_example/android/build.gradle b/packages/stream_video_flutter_background/background_example/android/build.gradle index 83ae22004..2274bde1a 100644 --- a/packages/stream_video_flutter_background/background_example/android/build.gradle +++ b/packages/stream_video_flutter_background/background_example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() diff --git a/packages/stream_video_push_notification/ios/Classes/StreamVideoPKDelegateManager.swift b/packages/stream_video_push_notification/ios/Classes/StreamVideoPKDelegateManager.swift index 3cb7800c9..f328ddd5a 100644 --- a/packages/stream_video_push_notification/ios/Classes/StreamVideoPKDelegateManager.swift +++ b/packages/stream_video_push_notification/ios/Classes/StreamVideoPKDelegateManager.swift @@ -8,7 +8,7 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate { private var pushRegistry: PKPushRegistry? private var defaultData: [String: Any]? - private var methodChannel: FlutterMethodChannel? + private var mainChannel: FlutterMethodChannel? private override init() { super.init() @@ -20,8 +20,8 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate { pushRegistry?.desiredPushTypes = [.voIP] } - public func initChannel(channel: FlutterMethodChannel) { - methodChannel = channel + public func initChannel(mainChannel: FlutterMethodChannel) { + self.mainChannel = mainChannel } public func initData(data: [String: Any]) { @@ -44,13 +44,28 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate { return completion() } + let defaults = UserDefaults.standard + let callbackHandle = defaults.object(forKey: "callback_handle") as? Int64 + var streamDict = payload.dictionaryPayload["stream"] as? [String: Any] let state = UIApplication.shared.applicationState if state == .background || state == .inactive { + if state == .inactive, callbackHandle != nil { + DispatchQueue.main.async { + let engine = FlutterEngine(name: "StreamVideoIsolate", project: nil, allowHeadlessExecution: true) + let callbackInfo = FlutterCallbackCache.lookupCallbackInformation(callbackHandle!) + let entrypoint = callbackInfo?.callbackName + let uri = callbackInfo?.callbackLibraryPath + + let isRunning = engine.run(withEntrypoint: entrypoint, libraryURI: uri) + } + } + + handleIncomingCall(streamDict: streamDict, state: state, completion: completion) } else if state == .active { - methodChannel?.invokeMethod("customizeCaller", arguments: streamDict) { (response) in + mainChannel?.invokeMethod("customizeCaller", arguments: streamDict) { (response) in if let customData = response as? [String: Any] { streamDict?["created_by_display_name"] = customData["name"] as? String streamDict?["created_by_id"] = customData["handle"] as? String @@ -98,9 +113,9 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate { // Complete after a delay to ensure that the incoming call notification // is displayed before completing the push notification handling. - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - completion() - } + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + completion() + } } } diff --git a/packages/stream_video_push_notification/ios/Classes/StreamVideoPushNotificationPlugin.swift b/packages/stream_video_push_notification/ios/Classes/StreamVideoPushNotificationPlugin.swift index 056f6be7a..3c94a1322 100644 --- a/packages/stream_video_push_notification/ios/Classes/StreamVideoPushNotificationPlugin.swift +++ b/packages/stream_video_push_notification/ios/Classes/StreamVideoPushNotificationPlugin.swift @@ -3,19 +3,23 @@ import UIKit import flutter_callkit_incoming public class StreamVideoPushNotificationPlugin: NSObject, FlutterPlugin { + let persistentState: UserDefaults = UserDefaults.standard public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "stream_video_push_notifications", binaryMessenger: registrar.messenger()) + let mainChannel = FlutterMethodChannel(name: "stream_video_push_notifications", binaryMessenger: registrar.messenger()) let instance = StreamVideoPushNotificationPlugin() - - registrar.addMethodCallDelegate(instance, channel: channel) - StreamVideoPKDelegateManager.shared.initChannel(channel: channel) + + registrar.addMethodCallDelegate(instance, channel: mainChannel) + StreamVideoPKDelegateManager.shared.initChannel(mainChannel: mainChannel) } - + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "initData": if let arguments = call.arguments as? [String: Any] { + let handle = arguments["callbackHandler"] as? Int64 + persistentState.set(handle, forKey: "callback_handle") + StreamVideoPKDelegateManager.shared.initData(data: arguments) result(nil) } else { diff --git a/packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart b/packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart index 5bf1c9a8e..d36226392 100644 --- a/packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart +++ b/packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart @@ -27,14 +27,18 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { required StreamVideoPushProvider iosPushProvider, required StreamVideoPushProvider androidPushProvider, CallerCustomizationFunction? callerCustomizationCallback, + BackgroundVoipCallHandler? backgroundVoipCallHandler, StreamVideoPushParams? pushParams, }) { return (CoordinatorClient client) { final params = _defaultPushParams.merge(pushParams); if (CurrentPlatform.isIos) { - StreamVideoPushNotificationPlatform.instance - .init(params.toJson(), callerCustomizationCallback); + StreamVideoPushNotificationPlatform.instance.init( + params.toJson(), + callerCustomizationCallback, + backgroundVoipCallHandler, + ); } return StreamVideoPushNotificationManager._( @@ -54,10 +58,29 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { required this.pushParams, this.callerCustomizationCallback, }) : _client = client { + //if there are active calls (for iOS) when connecting, subscribe to end call event + FlutterCallkitIncoming.activeCalls().then((value) { + if (value is List && value.isNotEmpty) { + _subscriptions.add( + _idCallEnded, + client.events.on( + (event) { + FlutterCallkitIncoming.endCall(event.callCid.id); + }, + ), + ); + } + }); + _subscriptions.add( _idCallKitIncoming, onCallEvent.whereType().listen( (_) { + if (!client.isConnected) { + _wasWsConnected = client.isConnected; + client.openConnection(); + } + _subscriptions.add( _idCallEnded, client.events.on( @@ -89,6 +112,11 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { onCallEvent.whereType().map((_) => null), ]).listen( (_) { + if (_wasWsConnected == false) { + _wasWsConnected = null; + client.closeConnection(); + } + _subscriptions.cancel(_idCallAccepted); _subscriptions.cancel(_idCallEnded); }, @@ -103,6 +131,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { final CallerCustomizationFunction? callerCustomizationCallback; final _logger = taggedLogger(tag: 'SV:PNManager'); + bool? _wasWsConnected; final Subscriptions _subscriptions = Subscriptions(); diff --git a/packages/stream_video_push_notification/lib/stream_video_push_notification_method_channel.dart b/packages/stream_video_push_notification/lib/stream_video_push_notification_method_channel.dart index 122421839..e39d942a2 100644 --- a/packages/stream_video_push_notification/lib/stream_video_push_notification_method_channel.dart +++ b/packages/stream_video_push_notification/lib/stream_video_push_notification_method_channel.dart @@ -1,5 +1,8 @@ +import 'dart:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'stream_video_push_notification_platform_interface.dart'; @@ -38,8 +41,14 @@ class MethodChannelStreamVideoPushNotification Future init( Map pushParams, CallerCustomizationFunction? callerCustomizationCallback, + BackgroundVoipCallHandler? backgroundVoipCallHandler, ) async { this.callerCustomizationCallback = callerCustomizationCallback; + if (backgroundVoipCallHandler != null) { + final backgroundCallback = + PluginUtilities.getCallbackHandle(backgroundVoipCallHandler); + pushParams['callbackHandler'] = backgroundCallback?.toRawHandle(); + } await methodChannel.invokeMethod('initData', pushParams); } } diff --git a/packages/stream_video_push_notification/lib/stream_video_push_notification_platform_interface.dart b/packages/stream_video_push_notification/lib/stream_video_push_notification_platform_interface.dart index 58e4d68c5..45a6d631d 100644 --- a/packages/stream_video_push_notification/lib/stream_video_push_notification_platform_interface.dart +++ b/packages/stream_video_push_notification/lib/stream_video_push_notification_platform_interface.dart @@ -23,6 +23,8 @@ typedef CallerCustomizationFunction = CallerCustomizationResponse Function({ String? callerName, }); +typedef BackgroundVoipCallHandler = Future Function(); + abstract class StreamVideoPushNotificationPlatform extends PlatformInterface { StreamVideoPushNotificationPlatform() : super(token: _token); @@ -47,6 +49,7 @@ abstract class StreamVideoPushNotificationPlatform extends PlatformInterface { Future init( Map pushParams, CallerCustomizationFunction? callerCustomizationCallback, + BackgroundVoipCallHandler? backgroundVoipCallHandler, ) { throw UnimplementedError('platformVersion() has not been implemented.'); }