Flutter Push Notification App Setup
Introduction
This document provides a guide on how to set up push notifications for incoming calls on the Telnyx Voice SDK Flutter Plugin.
It is important to understand how push notifications work in general in relation to Telnyx services. When you connect to the TelnyxClient, you establish a socket connection that can receive and handle incoming calls. However, if your application is in the background or terminated, the socket connection closes and can no longer receive invitations.
To address this limitation, the Telnyx Voice SDK uses push notifications to inform the device of incoming calls. When you log in to the TelnyxClient and provide an APNS or FCM token, the SDK sends a push notification to your device whenever an incoming call is received. Note that this notification only indicates that an invitation is on the way.
Once you respond to a push notification—for example, by tapping "Accept"—you must handle the logic to launch and reconnect to the TelnyxClient. After reconnection, the socket connection is re-established, and the backend will send the actual invitation to your device. In this scenario, it may be helpful to store the fact that the push notification was accepted so that when the invitation arrives (as soon as the socket is connected), you can automatically accept the call.
The following sections provide more detail on how to implement these steps.
Handling Foreground and Terminated Calls
When the app is in the foreground you do not need to use push notifications to receive calls, however it still might be beneficial to use CallKit to show native UI for the calls. When the app is terminated you will need to use push notifications to receive calls as described below.
Android
The Android platform makes use of Firebase Cloud Messaging in order to deliver push notifications. To receive notifications when receiving calls on your Android mobile device you will have to enable Firebase Cloud Messaging within your application.
The Demo app uses the FlutterCallkitIncoming plugin to show incoming calls. To show a notification when receiving a call, you can follow the steps below:
- Listen for Background Push Notifications, Implement the
FirebaseMessaging.onBackgroundMessage
method in yourmain
method
@pragma('vm:entry-point')
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (defaultTargetPlatform == TargetPlatform.android) {
// Android Only - Push Notifications
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
runApp(const MyApp());
}
- Optionally Add the
metadata
to CallKitParamsextra
field
static Future showNotification(RemoteMessage message) {
CallKitParams callKitParams = CallKitParams(
android:...,
ios:...,
extra: message.data,
)
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
}
- Handle the push notification in the
_firebaseMessagingBackgroundHandler
method
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Show notifcation
showNotification(message);
// Listen to action from FlutterCallkitIncoming
FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async {
switch (event!.event) {
case Event.actionCallAccept:
// Set the telnyx metadata for access when the app comes to foreground
TelnyxClient.setPushMetaData(
message.data, isAnswer: true, isDecline: false);
break;
case Event.actionCallDecline:
/*
* When the user declines the call from the push notification, the SDK now handles this automatically.
* Simply set the decline flag and the SDK will handle the rest using the new push_decline method.
* */
TelnyxClient.setPushMetaData(
message.data, isAnswer: false, isDecline: true);
break;
}});
}
-
Create a High-Importance Notification Channel (Android 8.0+)
For Android 8.0 (API level 26) and higher, notifications must be assigned to a channel. To ensure incoming call notifications are treated with high priority (e.g., shown as heads-up notifications), you need to create a dedicated channel with maximum importance.
First, add the
flutter_local_notifications
package to yourpubspec.yaml
:dependencies:
flutter_local_notifications: ^<latest_version>Gradle Setup (for Android): The
flutter_local_notifications
plugin may require Java 8+ features. To enable support for these, you need to configure Core Library Desugaring in your Android project.In your
android/app/build.gradle
file, ensure the following configurations are present:// android/app/build.gradle (Groovy DSL)
android {
// ... other settings
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}For projects using Kotlin DSL (
build.gradle.kts
):// android/app/build.gradle.kts (Kotlin DSL)
android {
// ... other settings
defaultConfig {
// multiDexEnabled = true // Only if you explicitly need multidex for other reasons
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8 // Or JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_1_8 // Or JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() // Or JavaVersion.VERSION_11.toString()
}
// ... other settings
}
dependencies {
// ... other dependencies
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") // Or a newer version like 2.1.4 for Java 11
}(Note: Adjust Java versions and
desugar_jdk_libs
version as needed. Whileflutter_local_notifications
might show Java 11, Java 8 withdesugar_jdk_libs:2.0.4
is often sufficient. For Java 11, usedesugar_jdk_libs:2.1.4
or newer.)Then, create the channel, typically during your app's initialization (e.g., in your
AndroidPushNotificationHandler.initialize
method):import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// Create high importance channel
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'telnyx_call_channel', // Unique ID for the channel
'Incoming Calls', // User-visible name
description: 'Notifications for incoming Telnyx calls.', // User-visible description
importance: Importance.max, // Crucial for heads-up display
playSound: true,
audioAttributesUsage: AudioAttributesUsage.notificationRingtone, // Use ringtone audio attributes
// Add other properties like vibration pattern if desired
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
try {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
print('High importance notification channel created/updated.');
} catch (e) {
print('Failed to create notification channel: $e');
}Finally, tell the Firebase SDK to use this channel by default for incoming FCM notifications by adding the following meta-data inside the
<application>
tag in yourandroid/app/src/main/AndroidManifest.xml
:<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="telnyx_call_channel" />(Note: The ID 'telnyx_call_channel' must match the ID used in the Dart code.)
-
Use the
TelnyxClient.getPushMetaData()
method to retrieve the metadata when the app comes to the foreground. This data is only available on 1st access and becomesnull
afterward.
Future<void> _handlePushNotification() async {
final data = await TelnyxClient.getPushMetaData();
PushMetaData? pushMetaData = PushMetaData.fromJson(data);
if (pushMetaData != null) {
_telnyxClient.handlePushNotification(pushMetaData, credentialConfig, tokenConfig);
}
}
- To Handle push calls on foreground, Listen for Call Events and invoke the
handlePushNotification
method
FlutterCallkitIncoming.onEvent.listen((CallEvent? event) {
switch (event!.event) {
case Event.actionCallIncoming:
// retrieve the push metadata from extras
final data = await TelnyxClient.getPushData();
...
_telnyxClient.handlePushNotification(pushMetaData, credentialConfig, tokenConfig);
break;
case Event.actionCallStart:
....
break;
case Event.actionCallAccept:
...
logger.i('Call Accepted Attach Call');
break;
});
Best Practices for Push Notifications on Android
- Request for Notification Permissions for android 13+ devices to show push notifications. More information can be found here
- Push Notifications only work in foreground for apps that are run in
debug
mode (You will not receive push notifications when you terminate the app while running in debug mode). - On Foreground calls, you can use the
FirebaseMessaging.onMessage.listen
method to listen for incoming calls and show a notification.
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
TelnyxClient.setPushMetaData(message.data);
NotificationService.showNotification(message);
mainViewModel.callFromPush = true;
});
- To handle push notifications on the background, use the
FirebaseMessaging.onBackgroundMessage
method to listen for incoming calls and show a notification and make sure to set theTelnyxClient.setPushMetaData
when user answers the call.
TelnyxClient.setPushMetaData(
message.data, isAnswer: true, isDecline: false);
-
When you call the
telnyxClient.handlePushNotification
it connects to thetelnyxClient
, make sure not to call thetelnyxClient.connect()
method after this. e.g an Edge case might be if you calltelnyxClient.connect()
on Widgetinit
method it will always call theconnect
method -
Early Answer/Decline : Users may answer/decline the call too early before a socket connection is established. To handle this situation, assert if the
IncomingInviteParams
is not null and only accept/decline if this is available.
bool waitingForInvite = false;
void accept() {
if (_incomingInvite != null) {
// accept the call if the incomingInvite arrives on time
_currentCall = _telnyxClient.acceptCall(
_incomingInvite!, _localName, _localNumber, "State");
} else {
// set waitingForInvite to true if we have an early accept
waitingForInvite = true;
}
}
_telnyxClient.onSocketMessageReceived = (TelnyxMessage message) {
switch (message.socketMethod) {
...
case SocketMethod.INVITE:
{
if (callFromPush) {
// For early accept of call
if (waitingForInvite) {
//accept the call
accept();
waitingForInvite = false;
}
callFromPush = false;
}
}
...
}
}
iOS
The iOS Platform makes use of the Apple Push Notification Service (APNS) and Pushkit in order to deliver and receive push notifications For a detailed tutorial, please visit our official Push Notification Docs
Native Swift Code Changes
For a full example please view the Demo Application Example
- In AppDelegate.swift setup the ability to receive VOIP push notifications by adding these lines to your application function
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
//Setup VOIP
let mainQueue = DispatchQueue.main
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
Note: It is important to add the following lines to your Info.plist
file to enable push notifications
<key>UIBackgroundModes</key>
<array>
<string>processing</string>
<string>remote-notification</string>
<string>voip</string>
</array>
Also, as we are using WebRTC we need to add the following lines to avoid a bug where there is no audio on iOS when using it with CallKit. Add the following lines to your AppDelegate.swift
file in the application function as demonstrated above
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
- Implement the CallkitIncomingAppDelegate within AppDelegate so that you can action on calls that are received. Callin action.fulfill() will allows us to listen to the events and act on them in our dart code.
@main
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
func onAccept(_ call: flutter_callkit_incoming.Call, _ action: CXAnswerCallAction) {
NSLog("onRunner :: Accept")
action.fulfill()
}
func onDecline(_ call: flutter_callkit_incoming.Call, _ action: CXEndCallAction) {
NSLog("onRunner :: Decline")
action.fulfill()
}
func onEnd(_ call: flutter_callkit_incoming.Call, _ action: CXEndCallAction) {
NSLog("onRunner :: onEnd")
action.fulfill()
}
func onTimeOut(_ call: flutter_callkit_incoming.Call) {
NSLog("onRunner :: TimeOut")
}
func didActivateAudioSession(_ audioSession: AVAudioSession) {
NSLog("onRunner :: Activate Audio Session")
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
}
func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
NSLog(":: DeActivate Audio Session")
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
....
Note: Notice for didActivateAudioSession and didDeactivateAudioSession that we are handling WebRTC manually. This is to handle the before mentioned bug where there is no audio on iOS when using it with CallKit.
- Register / Invalidate the push device token for iOS within AppDelegate.swift class
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
print(credentials.token)
let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
//Save deviceToken to your server
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}
- For foreground calls to work, you need to register with callkit on the restorationHandler delegate function. You can also choose to register with callkit using iOS official documentation on CallKit.
override func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let nameCaller = handleObj.getDecryptHandle()["nameCaller"] as? String ?? ""
let handle = handleObj.getDecryptHandle()["handle"] as? String ?? ""
let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
//set more data...
data.nameCaller = "dummy"
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.startCall(data, fromPushKit: true)
}
- Listen for incoming calls in AppDelegate.swift class and grab the relevant metadata from the push payload to pass to showCallkitIncoming (eg. the callerName, callerNumber, callID, etc)
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
print("didReceiveIncomingPushWith")
guard type == .voIP else { return }
if let metadata = payload.dictionaryPayload["metadata"] as? [String: Any] {
var callID = UUID.init().uuidString
if let newCallId = (metadata["call_id"] as? String),
!newCallId.isEmpty {
callID = newCallId
}
let callerName = (metadata["caller_name"] as? String) ?? ""
let callerNumber = (metadata["caller_number"] as? String) ?? ""
let id = payload.dictionaryPayload["call_id"] as? String ?? UUID().uuidString
let data = flutter_callkit_incoming.Data(id: id, nameCaller: callerName, handle: callerNumber, type: isVideo ? 1 : 0)
data.extra = payload.dictionaryPayload as NSDictionary
data.normalHandle = 1
let caller = callerName.isEmpty ? (callerNumber.isEmpty ? "Unknown" : callerNumber) : callerName
let uuid = UUID(uuidString: callID)
data.uuid = uuid!.uuidString
data.nameCaller = caller
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion()
}
}
}
Note: it is important to call completion() after showing the callkit incoming screen to avoid the app being terminated by the system. See the last line above. if you don't call completion() in pushRegistry(......, completion: @escaping () -> Void), there may be app crash by system when receiving voIP
Dart / Flutter Code Changes
- Listen for Call Events and invoke the
handlePushNotification
method
FlutterCallkitIncoming.onEvent.listen((CallEvent? event) {
switch (event!.event) {
case Event.actionCallIncoming:
// retrieve the push metadata from extras
PushMetaData? pushMetaData = PushMetaData.fromJson(event.body['extra']['metadata']);
_telnyxClient.handlePushNotification(pushMetaData, credentialConfig, tokenConfig);
break;
case Event.actionCallStart:
....
break;
case Event.actionCallAccept:
...
logger.i('Call Accepted Attach Call');
break;
});
- Sending an invitation on iOS when using Callkit. Because of the native changes we made in AppDelegate.swift related to WebRTC audio handling, we now need to start a callkit call whenever we send out an invitation so that we can have an active audio session. This simply means starting a callkit call whenever we send out an invitation. Your call() method could look something like:
void call(String destination) {
_currentCall = telnyxClient.newInvite(
_localName,
_localNumber,
destination,
'State',
customHeaders: {'X-Header-1': 'Value1', 'X-Header-2': 'Value2'},
);
var params = CallKitParams(
id: _currentCall?.callId,
nameCaller: _localName,
appName: 'My Calling App',
handle: destination,
);
FlutterCallkitIncoming.startCall(params);
}
Handling Late Notifications
If notifications arrive very late due to no internet connectivity, It is good to always flag it as a missed call. You can do that using the code snippet below :
const CALL_MISSED_TIMEOUT = 60;
DateTime nowTime = DateTime.now();
Duration? difference = nowTime?.difference(message.sentTime!);
if (difference.inSeconds > CALL_MISSED_TIMEOUT) {
NotificationService.showMissedCallNotification(message);
return;
}
Handling Background Calls
Background calls are handled slightly differently than terminated calls as the application is not disconnected and in a fresh state. The application is in the background and the socket connection is still active - meaning we can receive an invite on the socket but not be notified about it.
We can get around this by listening to application lifecycle events and checking if the application is in the background, and if so disconnecting from the socket manually. This way we can listen to the push notification and reconnect to the socket when the user accepts the call.
You can do this by either manually creating a lifecycle state listener in dart using the WidgetsBindingObserver or using a library like flutter_fgbg to listen to the application lifecycle events.
An implementation could look like this:
@pragma('vm:entry-point')
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!AppInitializer()._isInitialized) {
await AppInitializer()
.initialize(); // handle any initialization here (setting up listeners, etc)
}
runApp(
FGBGNotifier(
onEvent: (FGBGType type) =>
switch (type) {
FGBGType.foreground =>
{
print('App in the foreground (FGBGNotifier)'),
// Check if we are in the foreground as a result of a push notification, if we are do nothing, reconnection will happen there in handlePush. Otherwise connect
if (telnyxClient.callFromPush)
{
telnyxClient.connectWithCredential(getCredentialConfig)
},
},
FGBGType.background =>
{
print('App in the background (FGBGNotifier)'),
telnyxClient.disconnect(),
}
},
child: const MyApp()),
);
}
Note: Notice that in the foreground event we check if we are in the foreground as a result of a push notification, if we are do nothing, reconnection will happen there in handlePush. Otherwise connect. This is because handlePushNotification will be called when the user accepts the call from the push notification and we will reconnect to the socket there.
This also means however that when we are showing a notification, which on iOS can be full screen, we need to make sure that we don't disconnect when the notification is shown. We can do this with FGBG's ignoreWhile method like so:
Future<void> showNotification(IncomingInviteParams message) async {
// Temporarily ignore FGBG events while showing the CallKit notification
FGBGEvents.ignoreWhile(() async {
CallKitParams callKitParams = CallKitParams(
id: message.callID,
nameCaller: message.callerIdName,
appName: 'Telnyx Flutter Demo',
handle: message.callerIdNumber,
type: 0,
textAccept: 'Accept',
textDecline: 'Decline',
missedCallNotification: const NotificationParams(
showNotification: false,
isShowCallback: false,
subtitle: 'Missed call',
),
duration: 30000,
extra: {},
headers: <String, dynamic>{'platform': 'flutter'},
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
});
}
Best Practices for Push Notifications on iOS
- Push Notifications only work in foreground for apps that are run in
debug
mode (You will not receive push notifications when you terminate the app while running in debug mode). Make sure you are inrelease
mode. Preferably test using Testfight or Appstore. To test if push notifications are working, disconnect the telnyx client (while app is in foreground) and make a call to the device. You should receive a push notification.
New Push Notification Features
Simplified Push Decline Method
The Telnyx Flutter Voice SDK now includes a significantly simplified method for declining push notifications, eliminating the complex workflow previously required.
Previous Method (Legacy)
Previously, declining a push notification required:
- Logging back into the socket
- Listening for events
- Waiting for an INVITE message
- Manually sending a bye message to decline the call
- Handling bye responses and cleanup
New Simplified Method
The SDK now handles push call decline automatically when you set the decline flag. The process is streamlined to:
- Set the decline flag when the user declines the push notification:
case Event.actionCallDecline:
TelnyxClient.setPushMetaData(message.data, isAnswer: false, isDecline: true);
break;
- Call handlePushNotification as usual:
Future<void> _handlePushNotification() async {
final data = await TelnyxClient.getPushMetaData();
PushMetaData? pushMetaData = PushMetaData.fromJson(data);
if (pushMetaData != null) {
_telnyxClient.handlePushNotification(pushMetaData, credentialConfig, tokenConfig);
}
}
What Happens Internally
When isDecline: true
is set, the SDK automatically:
- Connects to the Telnyx socket
- Sends a login message with
decline_push: true
parameter - The backend handles the call termination
- Automatically disconnects from the socket
- No need to wait for INVITE messages or handle bye responses
Benefits
- Simplified Implementation: Reduces code complexity significantly
- Automatic Cleanup: SDK handles all socket management and cleanup
- Reliable: Eliminates potential race conditions and edge cases
- Consistent: Works the same way across Android and iOS platforms
10-Second Answer Timeout Mechanism
The SDK now includes an automatic timeout mechanism to handle cases where an INVITE message is missing after accepting a VoIP push notification.
The Problem
Sometimes after accepting a push notification, the INVITE message may not arrive due to:
- Network connectivity issues
- Server-side problems
- Message delivery failures
- Backend processing delays
Previously, this would result in the app waiting indefinitely for an INVITE that might never come.
The Solution
The SDK now implements a 10-second timeout mechanism that:
- Starts automatically when a push notification is accepted (
isAnswer: true
) - Monitors for incoming INVITE messages
- Automatically terminates the call if no INVITE arrives within 10 seconds
- Sends a bye message with
ORIGINATOR_CANCEL
termination reason (SIP code 487)
How It Works
Normal Flow (INVITE Received):
- User accepts push notification
- SDK starts 10-second timer
- INVITE message arrives on socket
- Timer is cancelled
- Call proceeds normally
Timeout Flow (No INVITE Received):
- User accepts push notification
- SDK starts 10-second timer
- No INVITE message arrives within 10 seconds
- Timer expires
- SDK automatically sends bye message with
ORIGINATOR_CANCEL
- Call is terminated gracefully
Implementation Details
The timeout mechanism is completely automatic and requires no additional code. It activates when:
TelnyxClient.setPushMetaData()
is called withisAnswer: true
handlePushNotification()
is called- The SDK connects and waits for an INVITE
Handling Timeout Events
Your existing call event handlers will receive the termination event:
_telnyxClient.onSocketMessageReceived = (TelnyxMessage message) {
switch (message.socketMethod) {
case SocketMethod.bye:
final byeMessage = message.message as ReceivedMessage;
final byeParams = ReceiveByeMessageBody.fromJson(byeMessage.result);
if (byeParams.terminationReason?.reason == CallTerminationReason.ORIGINATOR_CANCEL) {
// Handle timeout case - call was cancelled due to missing INVITE
print('Call terminated due to timeout - no INVITE received');
}
break;
// ... other cases
}
};
Benefits
- Prevents Indefinite Waiting: Ensures calls don't hang indefinitely
- Automatic Recovery: No manual intervention required
- Clear Termination Reason: Provides specific reason code for debugging
- Consistent Behavior: Works across all platforms and scenarios
- User Experience: Prevents users from waiting for calls that will never connect
Configuration
The timeout duration is fixed at 10 seconds and is not currently configurable. This duration was chosen to balance between:
- Allowing sufficient time for normal INVITE delivery
- Preventing excessive wait times for users
- Accommodating network latency variations
Migration Guide
For Existing Decline Implementations
If you have existing push decline logic, you can simplify it:
Before:
case Event.actionCallDecline:
// Complex logic to connect, wait for invite, send bye, etc.
await connectAndDeclineCall(message.data);
break;
After:
case Event.actionCallDecline:
TelnyxClient.setPushMetaData(message.data, isAnswer: false, isDecline: true);
break;
For Timeout Handling
No migration is required for the timeout feature - it works automatically with existing code. However, you may want to add specific handling for ORIGINATOR_CANCEL
termination reasons in your bye message handlers.
Testing the New Features
Testing Push Decline
- Receive a push notification while app is terminated
- Decline the call from the notification
- Verify the call is declined without complex socket operations
- Check logs to confirm
decline_push: true
parameter is sent
Testing Answer Timeout
- Accept a push notification
- Simulate network issues or server problems that prevent INVITE delivery
- Wait 10 seconds
- Verify the call is automatically terminated with
ORIGINATOR_CANCEL
- Check that your app handles the termination gracefully
Troubleshooting
Push Decline Issues
- Ensure you're setting
isDecline: true
insetPushMetaData
- Verify
handlePushNotification
is called after setting the metadata - Check network connectivity for the decline message to be sent
Timeout Issues
- The timeout only applies to accepted push notifications (
isAnswer: true
) - Normal incoming calls (not from push) are not affected by this timeout
- If INVITE arrives after timeout, it will be ignored as the call is already terminated