I am utilizing ConnectyCube Flutter to implement video name performance in my Flutter app.
The decision notifications work wonderful on Android, however on iOS, I do not obtain any notification when a name is initiated from one other gadget.
What I Have Completed So Far:
- Configured Push Notifications:
- Arrange Firebase Cloud Messaging (FCM) for push notifications.
- Uploaded the APNs certificates to ConnectyCube.
- Checked iOS Permissions: Enabled Push Notifications functionality in
Xcode. Ensured NSUserNotificationAlert is added within the Data.plist.
Examined with Background & Terminated State: On Android, the
notification is acquired correctly. On iOS, no notification seems in
each foreground and background states. Debugging Logs: No errors
associated to push notifications seem within the logs. Verified that FCM
token is appropriately generated on iOS.
under is my AndroidManifest file
xmlns:instruments="http://schemas.android.com/instruments"
bundle="com.sambuq.care_first">
**name supervisor class **
import 'bundle:care_first/bloc/auth_bloc/bloc/auth_bloc.dart';
import 'bundle:care_first/routing/route_names.dart';
import 'bundle:care_first/screens/settings/my_appointments/doctor_appointment/fashions/my_appointment.dart';
import 'bundle:care_first/shared/helper/helper_methods.dart';
import 'bundle:flutter/basis.dart';
import 'bundle:flutter/materials.dart';
import 'bundle:flutter_bloc/flutter_bloc.dart';
import 'bundle:go_router/go_router.dart';
import 'bundle:universal_io/io.dart';
import 'bundle:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
import 'bundle:connectycube_sdk/connectycube_sdk.dart';
import 'call_kit_manager.dart';
import '../utils/consts.dart';
class CallManager {
static String TAG = "CallManager";
static CallManager get occasion => _getInstance();
static CallManager? _instance;
static CallManager _getInstance() {
return _instance ??= CallManager._internal();
}
manufacturing unit CallManager() => _getInstance();
CallManager._internal();
P2PClient? _callClient;
P2PSession? _currentCall;
BuildContext? context;
MediaStream? localMediaStream;
Map remoteStreams = {};
GlobalKey? navigatorKey;
String? selfCubeId;
late String resourceId;
SystemMessagesManager? systemMessagesManager =
CubeChatConnection.occasion.systemMessagesManager;
CubeMessage systemMessage = CubeMessage();
closing participant = AudioPlayer();
Perform(bool, String)? onMicMuted;
init(
BuildContext context,
GlobalKey navigatorKey,
String selfCubeId,
String resourceId,
) {
this.context = context;
this.navigatorKey = navigatorKey;
this.selfCubeId = selfCubeId;
this.resourceId = resourceId;
_initCustomMediaConfigs();
if (CubeChatConnection.occasion.isAuthenticated()) {
_initCalls(context);
} else {
_initChatConnectionStateListener(context);
}
_initCallKit();
}
destroy() {
_callClient?.destroy();
_callClient = null;
}
void _initCustomMediaConfigs() {
RTCMediaConfig mediaConfig = RTCMediaConfig.occasion;
mediaConfig.minHeight = 340;
mediaConfig.minWidth = 480;
mediaConfig.minFrameRate = 25;
RTCConfig.occasion.statsReportsInterval = 200;
}
void _initCalls(BuildContext context) {
if (_callClient == null) {
_callClient = P2PClient.occasion;
_callClient!.init();
}
// _callClient is P2PClient
_callClient!.onReceiveNewSession = (callSession) async {
print("${'*' * 10} RECEIVED NEW CALL SESSION ${'*' * 10}");
// if (_currentCall != null &&
// _currentCall!.sessionId != callSession.sessionId &&
// _currentCall!.opponentsIds.first.toString() != selfCubeId) {
// callSession.reject();
// return;
// }
if (navigatorKey!.currentContext!.learn().person.persona ==
"affected person") {
await participant
.setReleaseMode(ReleaseMode.loop)
.then(
(worth) => participant.setSource(AssetSource('audio/ringtone.mp3')))
.then((worth) => participant.resume());
}
_currentCall = callSession;
var callState = await _getCallState(_currentCall!.sessionId);
await Future.delayed(Length(seconds: 1));
if (callState == CallState.REJECTED) {
reject(_currentCall!.sessionId, false);
} else if (callState == CallState.ACCEPTED) {
acceptCall(_currentCall!.sessionId,
_currentCall?.cubeSdp.userInfo ?? {}, false);
} else if (callState == CallState.UNKNOWN ||
callState == CallState.PENDING) {
if (callState == CallState.UNKNOWN &&
(Platform.isIOS || Platform.isAndroid)) {
await ConnectycubeFlutterCallKit.setCallState(
sessionId: _currentCall!.sessionId, callState: CallState.PENDING);
}
if (_currentCall!.cubeSdp.callerId.toString() != selfCubeId) {
navigatorKey!.currentContext!.learn().p2pSession =
callSession;
// await Future.delayed(Length(seconds: 1));
// await ConnectycubeFlutterCallKit.showCallNotification(CallEvent(
// sessionId: _currentCall!.sessionId,
// callType: CallType.VIDEO_CALL,
// callerId: _currentCall!.cubeSdp.callerId,
// callerName: "YASH",
// opponentsIds: _currentCall!.opponentsIds,
// userInfo: _currentCall?.cubeSdp.userInfo ?? {},
// ));
print("NOTIFICATION NOT CALLED");
_showIncomingCallScreen(_currentCall!, navigatorKey!.currentContext!);
}
}
_currentCall?.onLocalStreamReceived = (localStream) {
localMediaStream = localStream;
};
_currentCall?.onRemoteStreamReceived = (session, userId, stream) {
remoteStreams[userId] = stream;
};
_currentCall?.onRemoteStreamRemoved = (session, userId, stream) {
remoteStreams.take away(userId);
};
_currentCall?.onReceiveHungUpFromUser =
(session, userId, [userInfo]) async {
print("CALL_MANAGER: onReceiveHungUpFromUser");
/* systemMessage.recipientId = int.tryParse(selfCubeId!);
systemMessage.properties["resourceId"] = resourceId;
systemMessage.properties["action"] = "CALL_HUNGUP";
print("-------------BROADCASTING CALL HUNGUP EVENT-----------");
systemMessagesManager?.sendSystemMessage(systemMessage); */
broadcastSystemMessage("CALL_HUNGUP");
await participant.launch();
if (GoRouter.of(navigatorKey!.currentContext!)!.location ==
RoutesName.incomingVideoCall) {
GoRouter.of(navigatorKey!.currentContext!).pop();
}
};
};
_callClient!.onSessionClosed = (callSession) async {
if (_currentCall != null &&
_currentCall!.sessionId == callSession.sessionId) {
_currentCall = null;
localMediaStream?.getTracks().forEach((monitor) async {
await monitor.cease();
});
await localMediaStream?.dispose();
localMediaStream = null;
remoteStreams.forEach((key, worth) async {
await worth.dispose();
});
remoteStreams.clear();
CallKitManager.occasion.processCallFinished(callSession.sessionId);
if (GoRouter.of(navigatorKey!.currentContext!)!.location ==
RoutesName.incomingVideoCall) {
GoRouter.of(navigatorKey!.currentContext!).pop();
}
}
};
}
void startNewCall(BuildContext context, int callType, int patientCubeId,
MyAppointment appointment, String? businessId, String? moduleId) async {
this.context = context;
Set opponents = {patientCubeId};
if (opponents.isEmpty) return;
if (!kIsWeb) {
if (Platform.isIOS) {
Helper.setAppleAudioIOMode(AppleAudioIOMode.localAndRemote,
preferSpeakerOutput: true);
}
}
P2PSession callSession =
_callClient!.createCallSession(callType, opponents);
_currentCall = callSession;
// storing it in authbloc
context.learn().p2pSession = callSession;
context.learn().resourceId = resourceId;
context.learn().selfCubeId = selfCubeId;
String callerName;
if (appointment.doctorMap?["firstname"] != null) {
callerName =
"${appointment.doctorMap?["firstname"]} ${appointment.doctorMap?["lastname"]}";
} else {
callerName = "";
}
_sendStartCallSignalForOffliners(_currentCall!, callerName);
GoRouter.of(context)
.pushNamed(RoutesName.videoCallDoctorRoute, pathParameters: {
'businessId': encrypt(textual content: businessId ?? '', context: context),
}, queryParameters: {
'moduleId': moduleId,
}, additional: {
// 'callSession': callSession,
'appointment': appointment,
});
}
void _showIncomingCallScreen(P2PSession callSession, BuildContext context) {
if (navigatorKey!.currentContext != null) {
GoRouter.of(navigatorKey!.currentContext!).pushNamed(
RoutesName.incomingVideoCall,
additional: Map.from(
{"resourceId": resourceId, "selfCubeId": selfCubeId}));
}
}
void acceptCall(
String sessionId,
Map userInfo,
bool fromCallkit,
) async {
await participant.launch();
log('acceptCall, from callKit: $fromCallkit', TAG);
//ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: true);
if (_currentCall != null) {
if (context != null) {
// if (AppLifecycleState.resumed !=
// WidgetsBinding.occasion.lifecycleState) {
await _currentCall?.acceptCall();
// }
MyAppointment appointment = MyAppointment(
id: userInfo['appointment_id'], physician: userInfo['doctor_id']);
log(userInfo.toString(), "Person Data from AcceptCall");
if (!fromCallkit) {
await ConnectycubeFlutterCallKit.reportCallAccepted(
sessionId: sessionId);
}
broadcastSystemMessage("CALL_ACCEPTED");
navigatorKey!.currentContext!.learn().p2pSession =
_currentCall;
GoRouter.of(navigatorKey!.currentContext!).pushReplacementNamed(
RoutesName.videoCallPatientRoute,
additional: Map.from({
// 'currentCall': _currentCall!,
'appointment': appointment,
}),
);
/* systemMessage.recipientId = int.tryParse(selfCubeId!);
systemMessage.properties["resourceId"] = resourceId;
systemMessage.properties["action"] = "CALL_ACCEPTED";
print("-------------BROADCASTING ACCEPT CALL EVENT-----------");
systemMessagesManager?.sendSystemMessage(systemMessage); */
}
if (!kIsWeb) {
if (Platform.isIOS) {
await Helper.setAppleAudioIOMode(AppleAudioIOMode.localAndRemote,
preferSpeakerOutput: true);
}
}
}
}
void reject(String sessionId, bool fromCallkit) {
if (_currentCall != null) {
participant.launch();
broadcastSystemMessage("CALL_REJECTED");
if (fromCallkit) {
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: false);
} else {
CallKitManager.occasion.processCallFinished(_currentCall!.sessionId);
}
_currentCall!.reject();
_sendEndCallSignalForOffliners(_currentCall, null);
}
}
void hungUp() {
if (_currentCall != null) {
participant.launch();
CallKitManager.occasion.processCallFinished(_currentCall!.sessionId);
_currentCall!.hungUp();
_sendEndCallSignalForOffliners(_currentCall, null);
}
}
CreateEventParams _getCallEventParameters(
P2PSession currentCall, String? callerName) {
/* String? callerName = customers
.the place((cubeUser) => cubeUser.id == currentCall.callerId)
.first
.fullName; */
CreateEventParams params = CreateEventParams();
params.parameters = {
'message':
"Incoming ${currentCall.callType == CallType.VIDEO_CALL ? "Video" : "Audio"} name",
PARAM_CALL_TYPE: currentCall.callType,
PARAM_SESSION_ID: currentCall.sessionId,
PARAM_CALLER_ID: currentCall.callerId,
PARAM_CALL_OPPONENTS: currentCall.opponentsIds.be part of(','),
PARAM_CALLER_NAME: callerName ?? "Physician",
};
params.notificationType = NotificationType.PUSH;
params.atmosphere = CubeEnvironment.DEVELOPMENT;
//kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
params.usersIds = currentCall.opponentsIds.toList();
return params;
}
Future _sendStartCallSignalForOffliners(
P2PSession currentCall, String callerName) async {
CreateEventParams params = _getCallEventParameters(currentCall, callerName);
params.parameters[PARAM_SIGNAL_TYPE] = SIGNAL_TYPE_START_CALL;
params.parameters[PARAM_IOS_VOIP] = 1;
params.parameters[PARAM_EXPIRATION] = 0;
params.parameters['ios_push_type'] = 'background';
await createEvent(params.getEventForRequest()).then((cubeEvent) {
log("Occasion for offliners created: $cubeEvent");
}).catchError((error) {
log("ERROR happens throughout create occasion");
});
}
void _sendEndCallSignalForOffliners(
P2PSession? currentCall, String? callerName) {
if (currentCall == null) return;
CubeUser? currentUser = CubeChatConnection.occasion.currentUser;
if (currentUser == null || currentUser.id != currentCall.callerId) return;
CreateEventParams params = _getCallEventParameters(currentCall, callerName);
params.parameters[PARAM_SIGNAL_TYPE] = SIGNAL_TYPE_END_CALL;
createEvent(params.getEventForRequest()).then((cubeEvent) {
log("Occasion for offliners created");
}).catchError((error) {
log("ERROR happens throughout create occasion");
});
}
void _initCallKit() {
CallKitManager.occasion.init(
onCallAccepted: (uuid) {
acceptCall(uuid, _currentCall?.cubeSdp.userInfo ?? {}, true);
},
onCallEnded: (uuid) {
reject(uuid, true);
},
onMuteCall: (mute, uuid) {
onMicMuted?.name(mute, uuid);
},
);
}
void _initChatConnectionStateListener(BuildContext context) {
CubeChatConnection.occasion.connectionStateStream.pay attention((state) {
if (CubeChatConnectionState.Prepared == state) {
_initCalls(context);
}
});
}
Future _getCallState(String sessionId) async {
if (Platform.isAndroid || Platform.isIOS) {
var callState =
await ConnectycubeFlutterCallKit.getCallState(sessionId: sessionId);
log("CONECTICUBE CALL STATE: $callState");
return callState;
} else {
return Future.worth(CallState.UNKNOWN);
}
}
void muteCall(String sessionId, bool mute) {
CallKitManager.occasion.muteCall(sessionId, mute);
}
Future _onBackPressed(BuildContext context) {
return Future.worth(false);
}
void broadcastSystemMessage(motion) {
systemMessage.recipientId = int.tryParse(selfCubeId!);
systemMessage.properties["resourceId"] = resourceId;
systemMessage.properties["action"] = motion;
print("BROADCASTING: $motion EVENT");
systemMessagesManager?.sendSystemMessage(systemMessage);
}
}
FCM SETUP CLASS
import 'bundle:connectycube_sdk/connectycube_sdk.dart';
import 'bundle:device_info_plus/device_info_plus.dart';
import 'bundle:firebase_messaging/firebase_messaging.dart';
import 'bundle:flutter/basis.dart';
import 'bundle:package_info_plus/package_info_plus.dart';
import 'bundle:universal_io/io.dart';
import 'bundle:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
class FcmSetup {
static FcmSetup? _instance;
FcmSetup._internal();
manufacturing unit FcmSetup() {
return _instance ??= FcmSetup._internal();
}
FirebaseMessaging? firebaseMessaging;
Future init({
required Perform(RemoteMessage remoteMessage) onMessage,
}) async {
firebaseMessaging = FirebaseMessaging.occasion;
log("INT STARTED");
await firebaseMessaging!.requestPermission(
alert: false,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
String? token;
if (Platform.isAndroid || kIsWeb) {
token = await firebaseMessaging!.getToken();
} else if (Platform.isIOS || Platform.isMacOS) {
token = await firebaseMessaging!.getAPNSToken();
log("APNS TOKEN $token");
}
if (token != null) {
subscribe(token);
}
if (Platform.isIOS || Platform.isMacOS) {
String? voipToken = await ConnectycubeFlutterCallKit.getToken();
log("VOIP TOKEN $voipToken");
if (voipToken != null) {
subscribeVoIP(voipToken);
}
}
firebaseMessaging!.onTokenRefresh.pay attention((newToken) async {
subscribe(newToken);
});
// FirebaseMessaging.onMessage.pay attention(onMessage);
// FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
}
subscribe(String token) async {
log('[subscribe] token: $token');
bool isProduction = bool.fromEnvironment('dart.vm.product');
CreateSubscriptionParameters parameters = CreateSubscriptionParameters();
parameters.atmosphere =
isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
if (Platform.isAndroid) {
parameters.channel = NotificationsChannels.GCM;
parameters.platform = CubePlatform.ANDROID;
parameters.bundleIdentifier = "com.360carefirst.app";
} else if (Platform.isIOS) {
parameters.channel = NotificationsChannels.APNS;
parameters.platform = CubePlatform.IOS;
parameters.bundleIdentifier = Platform.isIOS
? "com.360carefirst.app"
: "com.connectycube.flutter.chatSample.macOS";
}
String deviceId = await getDeviceId();
parameters.udid = deviceId;
parameters.pushToken = token;
createSubscription(parameters.getRequestParameters())
.then((cubeSubscription) {
getSubscriptions().then((subscriptions) {
log("Subscriptions: ${subscriptions.toString()}");
});
log("SUBSCRIPTION CREATED");
}).catchError((error) {
log("SUBSCRIPTION ERROR ${error}");
});
}
Future getDeviceId() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.id; // Distinctive ID for Android
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
return iosInfo.identifierForVendor!;
}
return '';
}
void sendPushNotification(Record ids, String message, String title) {
bool isProduction = bool.fromEnvironment('dart.vm.product');
CreateEventParams params = CreateEventParams();
params.parameters = {
'message': message, // Required
'title': title, // Required
'ios_voip': 1, // Required for iOS VoIP push
'push_badge': 1, // Updates app badge depend
'push_sound': 'default', // Performs default notification sound
'custom_data': {
'param1': 'value1', // Customized parameters (elective)
'param2': 'value2',
},
'aps': {
'alert': {'title': title, 'physique': message},
'sound': 'default',
'content-available': 1 // Required for silent VoIP pushes
}
};
params.notificationType = NotificationType.PUSH;
params.atmosphere =
isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
params.usersIds = ids;
createEvent(params.getEventForRequest()).then((cubeEvent) {
log("SENT TO IDS ${ids.toString()}");
}).catchError((error) {
log("ERROR WHILE SEDING TO IDS");
});
}
Future unsubscribe() async {
strive {
Record subscriptionsList = await getSubscriptions();
log("SUBSCRIBED USER LIST ${subscriptionsList.size}");
for (CubeSubscription subscription in subscriptionsList) {
log("SUBSCRIPTION ID ${subscription.id}");
await deleteSubscription(subscription.id!);
}
log("UNSUBSCRIBED SUC");
} on Exception catch (e) {
log("UNSUBSCRIBED ERROR ${e}");
}
}
subscribeVoIP(String token) async {
log('[subscribeVoIP] token: $token');
CreateSubscriptionParameters parameters = CreateSubscriptionParameters();
parameters.pushToken = token;
if (Platform.isIOS) {
parameters.channel = NotificationsChannels.APNS_VOIP;
parameters.platform = CubePlatform.IOS;
}
String deviceId = await getDeviceId();
parameters.udid = deviceId;
parameters.atmosphere =
kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
var packageInfo = await PackageInfo.fromPlatform();
parameters.bundleIdentifier = packageInfo.packageName;
createSubscription(parameters.getRequestParameters())
.then((cubeSubscriptions) {
log('[subscribeVoIP] subscription SUCCESS');
}).catchError((error) {
log('[subscribeVoIP] subscription ERROR: $error');
});
}
}