Warum funktioniert CallKit nicht, wenn die App geschlossen ist? Wie kann ich es mit VoIP-Push-Benachrichtigungen in FlutAndroid

Forum für diejenigen, die für Android programmieren
Guest
 Warum funktioniert CallKit nicht, wenn die App geschlossen ist? Wie kann ich es mit VoIP-Push-Benachrichtigungen in Flut

Post by Guest »

Ich implementiere CallKit in meiner Flutter-App, stoße jedoch auf ein Problem bei der Verarbeitung eingehender Anrufe, wenn die App vollständig geschlossen ist (nicht im Hintergrund ausgeführt wird). Die App funktioniert einwandfrei, wenn sie im Vordergrund oder im Hintergrund läuft – das CallKit wird ausgelöst und der Anrufbildschirm erscheint, wenn ein eingehender Anruf angenommen wird. Wenn die App jedoch geschlossen ist (sie läuft überhaupt nicht im Hintergrund), wird das CallKit nicht ausgelöst, selbst wenn ich einen eingehenden Anruf über eine VoIP-Push-Benachrichtigung erhalte, und die App navigiert nicht zum Anrufbildschirm. Ich habe VoIP-Push-Benachrichtigungen eingerichtet, um die App zu aktivieren, aber CallKit funktioniert nicht wie erwartet, wenn die App vollständig geschlossen ist. Was habe ich versucht? 1. Hintergrundmodi aktivieren: Ich habe Hintergrundabrufe und Push-Benachrichtigungen in Xcode aktiviert, um sicherzustellen, dass die App VoIP-Push-Benachrichtigungen auch dann verarbeiten kann, wenn die App geschlossen ist. 2. Umgang mit VoIP-Push-Benachrichtigungen: Ich habe sichergestellt, dass die App VoIP-Push-Benachrichtigungen empfangen kann und dass der Benachrichtigungshandler ordnungsgemäß konfiguriert ist, um die App aufzuwecken. 3. CallKit mit Push-Benachrichtigungen verwenden: Ich habe CallKit implementiert, um eingehende Anrufe zu verarbeiten, aber es funktioniert nur, wenn die App im Hintergrund oder im Vordergrund läuft.

Erwartete Ergebnisse:
Ich habe erwartet, dass die App aufwacht und den CallKit-Bildschirm anzeigt, wenn eine VoIP-Push-Benachrichtigung empfangen wird, während die App geschlossen ist > und Übergang zur Anrufseite, wo ich kann Nehmen Sie den Anruf an.
Tatsächliche Ergebnisse:
Wenn die App geschlossen ist, CallKit wird nicht ausgelöst und die App navigiert nicht zum Anrufbildschirm, selbst nach Erhalt der VoIP-Push-Benachrichtigung.
Zusätzliche Informationen:
Ich habe versucht, der Dokumentation zu folgen CallKit
und VoIP Push Notifications auf iOS, aber ich kann die App immer noch nicht richtig funktionieren lassen, wenn sie vollständig geschlossen ist. Wenn jemand Erfahrung mit der Handhabung von VoIP-Anrufen und CallKit in Flutter (oder sogar mit nativem iOS-Code) hat, würde ich mich über Hinweise oder Vorschläge dazu freuen So beheben Sie dieses Problem.

Code: Select all

import 'dart:async';
import 'dart:developer' as dev;
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:legalis/AppUtil.dart';
import 'package:legalis/l10n/support_locale.dart';
import 'package:legalis/notify_controller.dart';
import 'package:legalis/provider/auth/auth_provider.dart';
import 'package:legalis/provider/call/call_provider.dart';
import 'package:legalis/provider/category/category_provider.dart';
import 'package:legalis/provider/chat/chat_detail_provider.dart';
import 'package:legalis/provider/communication/communication_provider.dart';
import 'package:legalis/provider/home/lawyer_report_provider.dart';
import 'package:legalis/provider/language_provider.dart';
import 'package:legalis/provider/localization/localization_provider.dart';
import 'package:legalis/provider/message/message_list_provider.dart';
import 'package:legalis/provider/navigation/navigation_provider.dart';
import 'package:legalis/provider/service/user_service_provider.dart';
import 'package:legalis/provider/user/user_provider.dart';
import 'package:legalis/screens/call/call_page.dart';
import 'package:legalis/screens/pages/home/home_page.dart';
import 'package:legalis/static/call_accept_observer.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
import 'firebase_options.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Bu dosya tüm providerları içeriyor.
import 'package:legalis/screens/splash_screen.dart';
import 'package:legalis/screens/auth/login_screen.dart';
import 'package:legalis/screens/pages/message/message_page.dart';
import 'package:legalis/screens/pages/profile/profile_page.dart';
import 'package:legalis/screens/pages/call/call_history.dart';
import 'package:legalis/wrapper/main_wrapper.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

int getUniqueNotificationId() {
var num = Random().nextInt(2000);
AppUtil.setNotification(num);
return num;
}

GlobalKey  navigatorKey = GlobalKey();

void main() async {
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);

// Initialize navigatorKey at the start
navigatorKey = GlobalKey();

// Initialize CallKit event handler
Widget? initialRoute;

// Add CallKit listener initialization here
await _initCallKitListener();

FirebaseMessaging messaging = FirebaseMessaging.instance;

// Set background message handler only once
FirebaseMessaging.onBackgroundMessage(handleNotification);

// Configure foreground message handling
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
dev.log('Foreground message received');
await handleNotification(message);
});

// Configure message handling when app is opened from terminated state
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
dev.log('App opened from terminated state with message');
await handleNotification(message);
});

await Future.delayed(const Duration(seconds: 1));
if (Platform.isIOS) {
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);

if (settings.authorizationStatus == AuthorizationStatus.authorized) {
String? apnsToken = await messaging.getAPNSToken();
Logger().t('APNS Token: $apnsToken');
if (apnsToken != null) {
print('APNS Token: $apnsToken');
}
}
}

// Get FCM token only once
String? fcmToken = await messaging.getToken();
Logger().t('FCM Token: $fcmToken');

// Remove duplicate call
// FirebaseMessaging.onBackgroundMessage(handleNotification);

AwesomeNotifications().initialize(
null,
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: const Color(0xFF9D50DD),
ledColor: Colors.white,
),
NotificationChannel(
channelKey: 'call_channel',
channelName: 'Call notifications',
channelDescription: 'Notification channel for call notifications',
defaultColor: Colors.orange,
ledColor: Colors.white,
importance: NotificationImportance.Max,
channelShowBadge: true,
locked: false,
playSound: true,
defaultRingtoneType: DefaultRingtoneType.Ringtone,
),
NotificationChannel(
channelKey: 'message_channel',
channelName: 'Message notifications',
channelDescription: 'Notification channel for message notifications',
defaultColor: Colors.blue,
ledColor: Colors.white,
importance: NotificationImportance.High,
channelShowBadge: true,
),
],
);
await FirebaseMessaging.instance.setAutoInitEnabled(true);
// Firebase Dynamic Links

runApp(MyApp(page: initialRoute));
}

@pragma('vm:entry-point')
Future  handleNotification(RemoteMessage message) async {
Logger().d('Remote Message  ${message.toString()}');
Logger().d('Remote Message  ${message.data.toString()}');
String? title = message.data['sender_name'] ?? 'Yeni Mesaj';
String? body = message.data['body'];
String channelKey = message.data['channel_key'] ?? 'default_channel';
String? id = message.data['id'] ?? '0';

if (channelKey == 'call_channel') {
// Clear any existing calls first
await FlutterCallkitIncoming.endAllCalls();

final uuid = const Uuid().v4();
final params = CallKitParams(
id: uuid,
nameCaller: title,
appName: 'Legalis',
avatar: message.data['caller_avatar'] ?? '',
handle: body ?? '',
type: 0,
duration: 30000,
textAccept: Platform.isIOS ? 'Accept' : 'Qəbul et',
textDecline: Platform.isIOS ? 'Decline' : 'Rədd et',
extra: {
...message.data,
'timestamp': DateTime.now().toIso8601String(),
'uuid': uuid, // Add unique identifier
},
headers: {},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call"),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: false,
maximumCallGroups: 1, // Limit to 1 call group
maximumCallsPerCallGroup: 1,
audioSessionMode: 'voicechat',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
),
);

try {
await FlutterCallkitIncoming.showCallkitIncoming(params);
} catch (e) {
Logger().e('Error showing CallKit: $e');
}
} else if (channelKey == 'message_channel') {
if (AppUtil.connectionUserId != id) {
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: getUniqueNotificationId(),
channelKey: channelKey,
color: Colors.blue,
title: title,
body: body,
category: NotificationCategory.Message,
backgroundColor: Colors.blue,
payload: {'message-api-id': id, 'username': title}),
actionButtons: [
NotificationActionButton(
key: 'READ',
label: 'Read Message',
color: Colors.green,
),
NotificationActionButton(
key: 'DISMISS',
label: 'Dismiss',
color: Colors.red,
),
],
localizations: {
// Azərbaycanca
'az': NotificationLocalization(buttonLabels: {
'READ': 'Mesajı oxu',
'DISMISS': 'İmtina et',
}),
// EN
'en': NotificationLocalization(
buttonLabels: {
'READ': 'Read Message',
'DISMISS': 'Dismiss',
},
),
// Rus
'ru': NotificationLocalization(
buttonLabels: {
'READ': 'Прочитать сообщение',
'DISMISS': 'Отклонить',
},
),
},
);
}
}

AwesomeNotifications().setListeners(
onActionReceivedMethod: (ReceivedAction receivedAction) async {
AppUtil.init();
AppUtil.setNotification(receivedAction.id);
NotificationController.onActionReceivedMethod(receivedAction);
},
onNotificationCreatedMethod:
(ReceivedNotification receivedNotification) async {
AppUtil.setNotification(receivedNotification.id);
NotificationController.onNotificationCreatedMethod(receivedNotification);
},
onNotificationDisplayedMethod:
(ReceivedNotification receivedNotification) async {
NotificationController.onNotificationDisplayedMethod(
receivedNotification);
},
onDismissActionReceivedMethod:  (ReceivedAction receivedAction) async {
NotificationController.onDismissActionReceivedMethod(receivedAction);
},
);
}

@pragma('vm:entry-point')
Future _initCallKitListener() async {
try {
FlutterCallkitIncoming.onEvent.listen((event) async {
if (event?.event == null || event?.body == null) return;

Logger().i('CallKit Event: ${event?.event}, Body: ${event?.body}');
final data = event?.body;
if (data == null) return;

switch (event?.event) {
case Event.actionCallAccept:
if (AppUtil.activeCallUser) {
Logger().d('Call already active, ignoring accept action');
return;
}
AppUtil.activeCallUser = true;

if (navigatorKey.currentState == null) {
await Future.delayed(const Duration(seconds: 2));
}

// Navigate to CallPage and remove all previous routes
await navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => CallPage(
userName: data['nameCaller'],
callId: data['extra']?['callId'],
recieverId: data['extra']?['userId'] ?? '',
accep: true,
),
),
(route) => false, // Remove all previous routes
);
break;

case Event.actionCallDecline:
await AppUtil.endCallAsync(callId: data['extra']?['callId']);
await FlutterCallkitIncoming.endAllCalls();
break;

case Event.actionCallEnded:
if (AppUtil.activeCallUser) {
await AppUtil.endCallAsync(callId: data['extra']?['callId']);
await FlutterCallkitIncoming.endAllCalls();
AppUtil.activeCallUser = false;
}
break;

case Event.actionCallIncoming:
AppUtil.activeCallUser = false;
break;

default:
break;
}
});
} catch (e, stackTrace) {
Logger().e("Error in call listener", error: e, stackTrace: stackTrace);
}
}

// ignore: must_be_immutable
class MyApp extends StatefulWidget {
final Widget? page;
const MyApp({super.key, this.page});

@override
State createState() => _MyAppState();
}

class _MyAppState extends State {
@override
void initState() {
super.initState();
// Remove _listenerCallKit call since we already initialized it in main()
// _listenerCallKit();

AwesomeNotifications().setListeners(
onActionReceivedMethod: NotificationController.onActionReceivedMethod,
onNotificationCreatedMethod:
NotificationController.onNotificationCreatedMethod,
onNotificationDisplayedMethod:
NotificationController.onNotificationDisplayedMethod,
onDismissActionReceivedMethod:
NotificationController.onDismissActionReceivedMethod);
}

@override
Widget build(BuildContext context) {
// Fbs
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MessageListProvider()),
ChangeNotifierProvider(create: (_) => ChatDetailProvider()),
ChangeNotifierProvider(create: (_) => LawyerReportProvider()),
ChangeNotifierProvider(create: (_) => UserServiceProvider()),
ChangeNotifierProvider(create: (_) => CallProvider()),
ChangeNotifierProvider(create: (_) => CallAcceptProvider()),
ChangeNotifierProvider(create: (_) => NavigationProvider()),
ChangeNotifierProvider(create: (_) => LocalizationProvider()),
ChangeNotifierProvider(create: (_) => CommunicationProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => CategoryProvider()),
ChangeNotifierProvider(create: (_) => LanguageProvider()),
ChangeNotifierProvider(create: (_) =>  UserProvider()),
],
child: Consumer(
builder: (context, provider, child) {
return MaterialApp(
onGenerateRoute: (settings) {
Logger().i('Route: ${settings}');
},
title: 'Legalis',
navigatorKey: navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
locale: provider.locale,
supportedLocales: L10n.support,
theme: ThemeData(
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Color(0xFF000E2B)),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
fontFamily: 'SF-Pro-Display',
primarySwatch: Colors.green,
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF000E2B),
elevation: 0,
iconTheme: IconThemeData(color: Colors.white),
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w700,
),
),
scaffoldBackgroundColor: Colors.white,
),
home: widget.page ?? const SplashPage(),
routes: {
'/login': (context) => const LoginScreen(),
'/home': (context) => const HomePageView(),
'/messagePage': (context) => const MessagePage(),
'/profilePage': (context) => const ProfilePage(),
'/callHistory': (context) => const CallHistory(),
'/mainWrapper': (context) => MainWrapper(),
},
);
},
),
);
}
}

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post