Closed Musfick closed 2 years ago
Hm on iOS to get CallKit integration you need to use PushKit, and as I know FCM can't sent such type of push messages 🤔.
I have done it by only using FCM and some ios configuration. FCM is working fine in my case for receive calls and everything. Also i am using agora for video and audio call.
Yes sure FCM will work. But as I understand in such case you will see regular push notification message when app in background/terminated. Or you somehow call CallKit from regular push notification handler (but it will executed as I understand only on tap)?
Good afternoon @Musfick can you please walk me through how you made it work on ios, have literally tried everything and am out of options, its working perfectly well for my android
am using FCM and this package for this. +2349034286339 (WhatsApp and Telegram) please lets get connected
please am humble to learn from you @Musfick
@Musfick Hi Musfick, I wanted to ask something regarding navigation on ACCEPT event. I am stuck at this. I am using Firebase Push Notifications. Any help is appreciated.
Thank u
@Musfick Hi Musfick, I wanted to ask something regarding navigation on ACCEPT event. I am stuck at this. I am using Firebase Push Notifications. Any help is appreciated.
Thank u
whats your challenge?
@zionnite Thanks a lot for getting back to me.
This is my background handler.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
bool videoCallEnabled = false;
bool audioCallEnabled = false;
if (message != null) {
debugPrint("Handling background is called");
print("Handling a background message and background handler: ${message.messageId}");
try {
videoCallEnabled = message.data.containsKey('videoCall');
audioCallEnabled = message.data.containsKey('voiceCall');
if (videoCallEnabled || audioCallEnabled) {
log("Video call is configured and is started");
showCallkitIncoming(Uuid().v4(), message: message);
//w8 for streaming
debugPrint("Should listen to events");
_NikkahMatchState.listenerEvent(message);
} else {
log("No Video or audio call was initialized");
}
} catch (e) {
debugPrint("Error occured:" + e.toString());
}
}
}
And this is my Listener event ACCEPT case:
case CallEvent.ACTION_CALL_ACCEPT:
// TODO: accepted an incoming call
// TODO: show screen calling in Flutter
log("Call Accepted onMessageOpened state");
var ccurrentChannel = chatRoomId;
currentChannel = chatRoomId;
log("currentChannel in accepted is: $ccurrentChannel");
log("currentChannel in accepted is: $currentChannel");
debugPrint("Details of call" + chatRoomId + callsDocId);
CallDocModel passableAbleCdm = CallDocModel();
await FirebaseFirestore.instance
.collection("ChatRoom")
.doc(chatRoomId)
.collection("calls")
.doc(callsDocId)
.update({'receiverCallResponse': 'Accepted', 'callResponseDateTime': FieldValue.serverTimestamp()});
if (videoCallEnabled) {
log("in video call enabled in accept call of listener event");
videoCallIsActive = "Yes";
/*NavigationService().navigationKey.currentState.push(
MaterialPageRoute(builder: (_) => VideoCallAgoraUIKit(
anotherUserName: callerName,
anotherUserImage: imageUrl,
channelName: chatRoomId,
token: "",
anotherUserId: "",
docId: callsDocId,
),));*/
FirebaseFirestore.instance
.collection("ChatRoom")
.doc(currentChannel)
.collection("calls")
.doc(callsDocId)
.get()
.then((value) async {
passableAbleCdm = CallDocModel.fromJson(value.data());
backgroundPassableAbleCdm = passableAbleCdm;
});
videoCallIsActive = "Yes";
log("${DateTime.now()} : before updating user value");
await ffstore.collection("Users").doc(auth.currentUser.uid).update({"isOnCall": true});
log("${DateTime.now()} after updating user value");
// await NavigationService.instance.pushNamed(
// AppRoute.makingVideoCall,
// );
// await initialization.then((value) async {
// // Get.put(AppController());
// await di.init();
// Future.delayed(Duration(seconds: 20), () {
// authController.navigateToSuitableScreen();
// });
// // FirebaseDynamicLinkService.initDynamicLink();
// });
// NavigationService().navigationKey.currentState.push( MaterialPageRoute(
// builder: (context) => VideoCallAgoraUIKit(
// anotherUserName: requesterName,
// anotherUserImage: requesterImageUrl,
// channelName: chatRoomId,
// token: "",
// anotherUserId: "",
// docId: callsDocId,
// callDoc: passableAbleCdm,
// ),));
currentChannel = chatRoomId;
log("this is where we are navigating wth currentChannel: $currentChannel and chatRoomId: $chatRoomId");
// Get.to(
// () => VideoCallAgoraUIKit(
// anotherUserName: requesterName,
// anotherUserImage: requesterImageUrl,
// channelName: chatRoomId,
// token: "",
// anotherUserId: "",
// docId: callsDocId,
// callDoc: passableAbleCdm,
// ),
// );
} else {
log("in voice call enabled in accept call of listener event");
var voiceCallTokenn = await GetToken().getTokenMethod(backgroundChatRoomId, auth.currentUser.uid);
log("token before if in splashscreen is: $voiceCallToken");
if (voiceCallTokenn != null) {
FirebaseFirestore.instance
.collection("ChatRoom")
.doc(currentChannel)
.collection("calls")
.doc(callsDocId)
.get()
.then((value) async {
log('after fetching the doc passableAbleCdm in splashscreen');
CallDocModel passableAbleCdm = CallDocModel.fromJson(value.data());
backgroundPassableAbleCdm = passableAbleCdm;
Get.to(
() => VoiceCall(
toCallName: requesterName,
toCallImageUrl: requesterImageUrl,
channelName: chatRoomId,
token: voiceCallTokenn,
docId: callsDocId,
callDoc: passableAbleCdm,
),
);
});
} else {
log("in token being null in voice call enabled in accept call of listener event");
}
}
break;
You may notice the comments that show that I have tried Get as I am using GetX and also tried passing a navigator key which in this background case returns currentState as null.
You know what mate,
I will take a look at it tomorrow noon it's already late I will need to open my system to take a look at it
no issues and thanks a bunch. I appreciate that.
You know what mate,
I will take a look at it tomorrow noon it's already late I will need to open my system to take a look at it
@zionnite Still waiting brother. I would really appreciate a response. thank u. We can also connect via Whatsapp at +923064278613.
I replied one of ur post, check that out
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await GetStorage.init();
await FirebaseService().init();
await NotificationService().init();
final service = MeetingService();
await service.initCallKitCallBack(isTerminated: true);
debugPrint("Handling a background message: ${message.messageId}");
final meetingState = MeetingState.fromJson(message.data);
switch (meetingState.type) {
case FCM_TYPE_NOTIFICATION:
NotificationService()
.showNotification(message.data["title"], message.data["message"]);
break;
case RECEIVE_VIDEO_MEETING_INVITATION:
service.startIncomingCall(meetingState);
break;
case CANCELED_VIDEO_MEETING_INVITATION:
service.endCurrentCall();
break;
}
}
initCallKitCallBack({bool isTerminated = false}) async {
try {
FlutterCallkitIncoming.onEvent.listen((event) async {
switch (event?.name) {
case CallEvent.ACTION_CALL_ACCEPT:
debugPrint("ACTION_CALL_ACCEPT");
if(isTerminated)return;
acceptCall(event?.body["extra"]);
break;
case CallEvent.ACTION_CALL_DECLINE:
debugPrint("ACTION_CALL_DECLINE");
var meetingState = MeetingState.fromJson(event?.body["extra"]);
if (!isCallCanceled) {
sendPush(DECLINE_VIDEO_MEETING_INVITATION, meetingState);
}
break;
}
});
} on Exception {}
}
acceptCall(extra){
endCurrentCall();
var meetingState = MeetingState.fromJson(extra);
sendPush(ACCEPT_VIDEO_MEETING_INVITATION, meetingState);
Get.to(
() => MeetingPage(),
arguments: {
"agora_channel": ________,
});
}
When app is in terminated state check if user is in current call. Better understand check this package example
@override
void onInit() {
checkCurrentCall();
super.onInit();
}
void checkCurrentCall() async {
final list = await FlutterCallkitIncoming.activeCalls();
if(list.length != 0){
final extra = box.read("call_extra");
MeetingService().acceptCall(extra);
}
}
@Musfick where did you put this acceptCall method because I have this method in the ACTION_CALL_ACCEPT event and I am getting the
[navigatorState is null when using pushNamed Navigation onGenerateRoutes of GetMaterialPage](https://stackoverflow.com/questions/72845544/navigatorstate-is-null-when-using-pushnamed-navigation-ongenerateroutes-of-getma)
for the navigator key in the background and terminated state. (but the navigator key is working correctly in the foreground ) and for the getx, I am getting something that asks me to put GetMaterialApp in place of the MaterialApp, which is already there but probably haven't been initialized yet.
@Musfick Can you please share any github repository where we can look deep into it... We need you!
@Musfick where did you put this acceptCall method because I have this method in the ACTION_CALL_ACCEPT event and I am getting the
[navigatorState is null when using pushNamed Navigation onGenerateRoutes of GetMaterialPage](https://stackoverflow.com/questions/72845544/navigatorstate-is-null-when-using-pushnamed-navigation-ongenerateroutes-of-getma)
for the navigator key in the background and terminated state. (but the navigator key is working correctly in the foreground ) and for the getx, I am getting something that asks me to put GetMaterialApp in place of the MaterialApp, which is already there but probably haven't been initialized yet.
when the app is in terminated state don't navigate to screen from callback. When user accept the call from terminated state it basically open the app but the it keep the call active. After open the app check if any active call is running..if active then navigate to Screen. For better understand please take a look this example https://github.com/hiennguyen92/flutter_callkit_incoming/blob/master/example/lib/main.dart
Important
@override
void initState() {
super.initState();
_uuid = Uuid();
initFirebase();
WidgetsBinding.instance?.addObserver(this);
//Check call when open app from terminated
checkAndNavigationCallingPage();
}
getCurrentCall() async {
//check current call from pushkit if possible
var calls = await FlutterCallkitIncoming.activeCalls();
if (calls is List) {
if (calls.isNotEmpty) {
print('DATA: $calls');
this._currentUuid = calls[0]['id'];
return calls[0];
} else {
this._currentUuid = "";
return null;
}
}
}
checkAndNavigationCallingPage() async {
var currentCall = await getCurrentCall();
if (currentCall != null) {
NavigationService.instance
.pushNamedIfNotCurrent(AppRoute.callingPage, args: currentCall);
}
}
@Musfick Can you please share any github repository where we can look deep into it... We need you!
https://github.com/hiennguyen92/flutter_callkit_incoming/tree/master/example/lib
I am having trouble in getting any call when app is terminated state. The calls are coming when my app is in background or lock state. What are the right procedure to get any call when the app is terminated? I am using firebase cloud functions. Any kind of help is much appreciated.
I am having trouble in getting any call when app is terminated state. The calls are coming when my app is in background or lock state. What are the right procedure to get any call when the app is terminated? I am using firebase cloud functions. Any kind of help is much appreciated.
@bihim in Android or iOS?
I am having trouble in getting any call when app is terminated state. The calls are coming when my app is in background or lock state. What are the right procedure to get any call when the app is terminated? I am using firebase cloud functions. Any kind of help is much appreciated.
@bihim in Android or iOS?
Oh sorry to mention. In iOS.
I'm having the same issue. it is because of the iOS settings. I have read somewhere that if we can somehow successfully implement pushKit for iOS, we might be able to get the device to wake up on call and show callKit notifications with the help of that. And I also tried that but I couldn't get it to work. The notifications are received for about 10-15 minutes after terminating the app even without the pushKit configuration (which I did and am not sure whether it was correct or not because it didn't work). And after those 10-15 minutes, I have to re-open the app and get the 10-15 minute active cycle of call notifications.
Though I should mention that the text message notifications are working even in terminated state.
I am having trouble in getting any call when app is terminated state. The calls are coming when my app is in background or lock state. What are the right procedure to get any call when the app is terminated? I am using firebase cloud functions. Any kind of help is much appreciated.
@bihim in Android or iOS?
Oh sorry to mention. In iOS.
@Musfick @ycherniavskyi @zionnite Can any of you please help in regards to getting the call notifications on iOS even in terminated state? Thanks
I am having trouble in getting any call when app is terminated state. The calls are coming when my app is in background or lock state. What are the right procedure to get any call when the app is terminated? I am using firebase cloud functions. Any kind of help is much appreciated.
@bihim in Android or iOS?
Oh sorry to mention. In iOS.
@Musfick @ycherniavskyi @zionnite Can any of you please help in regards to getting the call notifications on iOS even in terminated state? Thanks
I have implemented callkit with only FCM. I have not use pushkit.
Note: This function execute only in for foreground mode
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
Note: This function execute when app is in terminated state and background state
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
@Musfick does this function keep executing even after a lot of time have passed after termination?
@Musfick does this function keep executing even after a lot of time have passed after termination?
no
@Musfick does this function keep executing even after a lot of time have passed after termination?
no
@Musfick and you are ok with that? I mean I am making a project for a client and cannot find a solution to this problem. There must be a way to wake the iOS app from terminated state anytime, so that showCallKitIncoming can be called. Do you have any suggestions? @hiennguyen92 @zionnite can u please help with this? Thanks
@Musfick does this function keep executing even after a lot of time have passed after termination?
no
@Musfick and you are ok with that? I mean I am making a project for a client and cannot find a solution to this problem. There must be a way to wake the iOS app from terminated state anytime, so that showCallKitIncoming can be called. Do you have any suggestions? @hiennguyen92 @zionnite can u please help with this? Thanks
When one use call another use, my app get instant call. Whatever the app in background, foreground, terminated state. Like when you post a notification using fcm, phone receive the notification instantly no matter the app is in which state. I have only used fcm to implement the feature. I dont need to use pushKit for this.
@Musfick May I see the notification payload? I mean the whole sed method? because I am sending a silent notification and it stops getting through after some time. But the other notifications are being received.
@Musfick May I see the notification payload? I mean the whole sed method? because I am sending a silent notification and it stops getting through after some time. But the other notifications are being received.
Sending Call Invitation Payload
final params = {
"content_available": true,
"data": {
"meeting_token": data["agora_data"]["token"],
"message": type == "paid"
? "Meeting : ${bookedAppointment.startTime} - ${bookedAppointment.endTime}"
: "Free Meeting Invitation",
"meeting_id": bookedAppointment.bookingSlotId.toString(),
"type": SEND_VIDEO_MEETING_INVITATION,
"meeting_type": type,
"from_token": myToke,
"meeting_channel": data["agora_data"]["channel_name"],
"remaining_time": data["details"]
["remaining_time_in_real_millisecond"],
"call_Type": medium,
"profile_image": profile.value?.profileImage,
"duration": bookedAppointment.duration,
"duration_in": bookedAppointment.duration,
"designation":
profile.value?.counsellorProfessionalDetails?.profession,
"name": profile.value?.name,
"sender_id": profile.value?.userId
},
"to": tokenRes["data"]["token"]
};
@Musfick this params part is inside the apns section? Because here's mine:
await admin.messaging().send({
token: token_o,
notification: {},
data: {
imageUrl: requesterImageUrl,
chatRoomId: chatRoomId,
screenName: 'voiceScreen',
voiceCall: 'voiceCall',
callerName: requesterName,
callsDocId: callsDocId,
senderId: requesterId,
callInitTime:callInitTime
},
android: {
notification: {
click_action: "android.intent.action.MAIN"
},
},
apns: {
headers: {
"apns-push-type": "alert",
"apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
},
payload: {
aps: {
alert: {},
badge: 0,
contentAvailable: true,
},
notification: {
//ti `You received a call from ${requesterName}`,
//body: "",
//imageUrl: requesterImageUrl,
},
priority: "high",
},
}
}).then(value => {
functions.logger.log("Notification for AudioCall is sent to the Receiver");
}).catch((e) => {
functions.logger.log(e.toString());
});
@Musfick May I see the notification payload? I mean the whole sed method? because I am sending a silent notification and it stops getting through after some time. But the other notifications are being received.
Sending Call Invitation Payload
final params = { "content_available": true, "data": { "meeting_token": data["agora_data"]["token"], "message": type == "paid" ? "Meeting : ${bookedAppointment.startTime} - ${bookedAppointment.endTime}" : "Free Meeting Invitation", "meeting_id": bookedAppointment.bookingSlotId.toString(), "type": SEND_VIDEO_MEETING_INVITATION, "meeting_type": type, "from_token": myToke, "meeting_channel": data["agora_data"]["channel_name"], "remaining_time": data["details"] ["remaining_time_in_real_millisecond"], "call_Type": medium, "profile_image": profile.value?.profileImage, "duration": bookedAppointment.duration, "duration_in": bookedAppointment.duration, "designation": profile.value?.counsellorProfessionalDetails?.profession, "name": profile.value?.name, "sender_id": profile.value?.userId }, "to": tokenRes["data"]["token"] };
I am hitting this https://fcm.googleapis.com/fcm/send url with this payload.
@Musfick this params part is inside the apns section? Because here's mine:
await admin.messaging().send({ token: token_o, notification: {}, data: { imageUrl: requesterImageUrl, chatRoomId: chatRoomId, screenName: 'voiceScreen', voiceCall: 'voiceCall', callerName: requesterName, callsDocId: callsDocId, senderId: requesterId, callInitTime:callInitTime }, android: { notification: { click_action: "android.intent.action.MAIN" }, }, apns: { headers: { "apns-push-type": "alert", "apns-priority": "5", // Must be `5` when `contentAvailable` is set to true. }, payload: { aps: { alert: {}, badge: 0, contentAvailable: true, }, notification: { //ti `You received a call from ${requesterName}`, //body: "", //imageUrl: requesterImageUrl, }, priority: "high", }, } }).then(value => { functions.logger.log("Notification for AudioCall is sent to the Receiver"); }).catch((e) => { functions.logger.log(e.toString()); });
it's in Node JS. I am using loud Functions for this.
@Musfick this params part is inside the apns section? Because here's mine:
await admin.messaging().send({ token: token_o, notification: {}, data: { imageUrl: requesterImageUrl, chatRoomId: chatRoomId, screenName: 'voiceScreen', voiceCall: 'voiceCall', callerName: requesterName, callsDocId: callsDocId, senderId: requesterId, callInitTime:callInitTime }, android: { notification: { click_action: "android.intent.action.MAIN" }, }, apns: { headers: { "apns-push-type": "alert", "apns-priority": "5", // Must be `5` when `contentAvailable` is set to true. }, payload: { aps: { alert: {}, badge: 0, contentAvailable: true, }, notification: { //ti `You received a call from ${requesterName}`, //body: "", //imageUrl: requesterImageUrl, }, priority: "high", }, } }).then(value => { functions.logger.log("Notification for AudioCall is sent to the Receiver"); }).catch((e) => { functions.logger.log(e.toString()); });
no, it is the whole payload. I didn't use apns params
contentAvailable: true, /// is important.You are using this inside the apns, use this inside root. like
{
"content_available": true,
"data": {},
"to": "eyLsdfd_____"
}
contentAvailable: true, /// is important.You are using this inside the apns, use this inside root. like
{ "content_available": true, "data": {}, "to": "eyLsdfd_____" }
@Musfick using
contentAvailable: true,
in root throws following error in the Firebase Functions log:
Error: Invalid JSON payload received. Unknown name "contentAvailable" at 'message': Cannot find field.
@waqadArshad did it work for you?
@waqadArshad did it work for you?
@zionnite what? contentAvailable: true? No it didn't. Threw an error as shown in the last message.
and sorry for the delayed response. I'm just so worried about this issue. have been trying to debug this and it isn't working out.
@waqadArshad did it work for you?
@zionnite what? contentAvailable: true? No it didn't. Threw an error as shown in the last message.
and sorry for the delayed response. I'm just so worried about this issue. have been trying to debug this and it isn't working out.
content_available not contentAvailable
@zionnite well, it depends upon how you are using it. As far as I know, if you are using it in an HTTP request, it is content_available but if you are using it in a cloud function written in Node JS, you would use contentAvailable.
no its content_available in every where
I have done it by only using FCM and some ios configuration. FCM is working fine in my case for receive calls and everything. Also i am using agora for video and audio call.
@Musfick : what iOS configuration changes that you have done to make it work?
I successfully implemented calling feature on ios using firebase FCM. It works in every state, Background state, Foreground state, Terminated state.
Thank You again ❤️