flutter – “flutter_callkit_incoming” does not work in Kill state on iOS however notifications seem

flutter – “flutter_callkit_incoming” does not work in Kill state on iOS however notifications seem


I’m utilizing the flutter_callkit_incoming package deal in my Flutter app to deal with incoming name notifications. The problem is that whereas notifications seem when the app is within the foreground or background, it doesn’t work as anticipated when the app is in a Kill state. Incoming name notifications present up, however tapping on them doesn’t set off the app to open or deal with the decision.

I’ve adopted the package deal documentation and configured the mandatory capabilities (Push Notifications, Background Modes, and VoIP). This difficulty appears to happen solely on iOS. On Android, all the things works tremendous.

What might be inflicting this downside, and the way can I make sure the app handles incoming calls appropriately even within the Kill state?


void most important() async {
  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 Firebase.initializeApp(
    choices: DefaultFirebaseOptions.currentPlatform,
  );

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  FirebaseMessaging.onMessage.pay attention((RemoteMessage message) async {
    Logger().d('Distant Message  ${message.notification}');
    Logger().d('Distant Message  ${message.knowledge.toString()}');
    String? title = message.knowledge['sender_name'] ?? 'Yeni Mesaj';
    String? physique = message.knowledge['body'];
    String channelKey = message.knowledge['channel_key'] ?? 'default_channel';
    String? id = message.knowledge['id'] ?? '0';
    print(
        'Message title: ${message.notification?.title}, physique: ${message.notification?.physique}, knowledge: ${message.knowledge}');
    if (channelKey == 'message_channel') {
      await AwesomeNotifications().createNotification(
        content material: NotificationContent(
            id: getUniqueNotificationId(),
            channelKey: channelKey,
            coloration: Colours.blue,
            title: title,
            physique: physique,
            class: NotificationCategory.Message,
            backgroundColor: Colours.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': 'Learn Message',
              'DISMISS': 'Dismiss',
            },
          ),
          // Rus
          'ru': NotificationLocalization(
            buttonLabels: {
              'READ': 'Прочитать сообщение',
              'DISMISS': 'Отклонить',
            },
          ),
        },
      );
    } else if (channelKey == 'call_channel') {
      print('--------------------1');
      showCallkitIncoming(const Uuid().v4(), message);
    }
  });
  
  runApp(const MyApp());
}

@pragma('vm:entry-point')
Future<void> listenerEvent() async {
  attempt {
    FlutterCallkitIncoming.onEvent.pay attention((occasion) async {
      print('Occasion: $occasion');
      swap (occasion!.occasion) {
        case Occasion.actionCallIncoming:
          // TODO: acquired an incoming name
          break;
        case Occasion.actionCallStart:
          // TODO: began an outgoing name
          // TODO: present display calling in Flutter
          break;
        case Occasion.actionCallAccept:
          print('uuid ${occasion.physique['uuid']}');
          NavigationService.occasion
              .pushNamedIfNotCurrent(AppRoute.callingPage, args: occasion);
          break;
        case Occasion.actionCallDecline:
          // TODO: declined an incoming name
          // print(
          //'actionCallDecline actionCallDecline actionCallDecline actionCallDecline actionCallDecline ');
          break;
        case Occasion.actionCallEnded:
          // TODO: ended an incoming/outgoing name
          break;
        case Occasion.actionCallTimeout:
          // TODO: missed an incoming name
          break;
        case Occasion.actionCallCallback:
          // TODO: solely Android - click on motion `Name again` from missed name notification
          break;
        case Occasion.actionCallToggleHold:
          // TODO: solely iOS
          break;
        case Occasion.actionCallToggleMute:
          // TODO: solely iOS
          break;
        case Occasion.actionCallToggleDmtf:
          // TODO: solely iOS
          break;
        case Occasion.actionCallToggleGroup:
          // TODO: solely iOS
          break;
        case Occasion.actionCallToggleAudioSession:
          NavigationService.occasion
              .pushNamedIfNotCurrent(AppRoute.callingPage, args: occasion);
          break;
        case Occasion.actionDidUpdateDevicePushTokenVoip:
          // TODO: solely iOS
          break;
        case Occasion.actionCallCustom:
          break;
      }
    });
  } on Exception catch (e) {}
}

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  var token = await FlutterCallkitIncoming.getDevicePushTokenVoIP();
  Logger().d('Distant Message  ${message.notification}');
  Logger().d('Distant Message  ${message.knowledge.toString()}');
  String? title = message.knowledge['sender_name'] ?? 'Yeni Mesaj';
  String? physique = message.knowledge['body'];
  String channelKey = message.knowledge['channel_key'] ?? 'default_channel';
  String? id = message.knowledge['id'] ?? '0';
  print(
      'Message title: ${message.notification?.title}, physique: ${message.notification?.physique}, knowledge: ${message.knowledge}');
  if (channelKey == 'message_channel') {
    await AwesomeNotifications().createNotification(
      content material: NotificationContent(
          id: getUniqueNotificationId(),
          channelKey: channelKey,
          coloration: Colours.blue,
          title: title,
          physique: physique,
          class: NotificationCategory.Message,
          backgroundColor: Colours.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': 'Learn Message',
            'DISMISS': 'Dismiss',
          },
        ),
        // Rus
        'ru': NotificationLocalization(
          buttonLabels: {
            'READ': 'Прочитать сообщение',
            'DISMISS': 'Отклонить',
          },
        ),
      },
    );
  } else if (channelKey == 'call_channel') {
    print('--------------------2');
    showCallkitIncoming(const Uuid().v4(), message);
  }
}

Future<void> showCallkitIncoming(String uuid, RemoteMessage message) async {
  attempt {
    print('uuid $uuid');
    ultimate params = CallKitParams(
      id: uuid,
      nameCaller: message.knowledge['sender_name'],
      appName: 'Legalis',
      avatar: 'https://i.pravatar.cc/100',
      deal with: '0123456789',
      kind: 0,
      period: 30000,
      textAccept: 'Settle for',
      textDecline: 'Decline',
      missedCallNotification: const NotificationParams(
        showNotification: true,
        isShowCallback: false,
        subtitle: 'Missed name',
      ),
      further: message.knowledge,
      android: const AndroidParams(
        isCustomNotification: true,
        isShowLogo: false,
        ringtonePath: 'system_ringtone_default',
        backgroundColor: '#000E2B',
        isShowFullLockedScreen: true,
        isCustomSmallExNotification: true,
        actionColor: '#4CAF50',
        textColor: '#ffffff',
      ),
      ios: const IOSParams(
        iconName: 'CallKitLogo',
        handleType: '',
        supportsVideo: false,
        maximumCallGroups: 2,
        maximumCallsPerCallGroup: 1,
        audioSessionMode: 'default',
        audioSessionActive: true,
        audioSessionPreferredSampleRate: 44100.0,
        audioSessionPreferredIOBufferDuration: 0.005,
        supportsDTMF: false,
        supportsHolding: true,
        supportsGrouping: false,
        supportsUngrouping: false,
        configureAudioSession: false,
        ringtonePath: 'system_ringtone_default',
      ),
    );
    await FlutterCallkitIncoming.showCallkitIncoming(params);
  } catch (e) {
    print('Error: $e');
  }
}

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

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

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  late ultimate Uuid _uuid;
  String? _currentUuid;

  late ultimate FirebaseMessaging _firebaseMessaging;

  @override
  void initState() {
    tremendous.initState();
    // Take away _listenerCallKit name since we already initialized it in most important()
    listenerEvent();
    _uuid = const Uuid();
    initFirebase();
    WidgetsBinding.occasion.addObserver(this);
    //Verify name when open app from terminated
    checkAndNavigationCallingPage();
    AwesomeNotifications().setListeners(
        onActionReceivedMethod: NotificationController.onActionReceivedMethod,
        onNotificationCreatedMethod:
            NotificationController.onNotificationCreatedMethod,
        onNotificationDisplayedMethod:
            NotificationController.onNotificationDisplayedMethod,
        onDismissActionReceivedMethod:
            NotificationController.onDismissActionReceivedMethod);
  }

  getCurrentCall() async {
    //examine present name from pushkit if potential
    var calls = await FlutterCallkitIncoming.activeCalls();
    if (calls is Checklist) {
      if (calls.isNotEmpty) {
        if (calls[0]['id'] == CallUtil.currentKitCallId) {
          return null;
        }
        print('DATA: $calls');
        _currentUuid = calls[0]['id'];
        CallUtil.currentKitCallId = _currentUuid;
        return calls[0];
      } else {
        _currentUuid = "";
        return null;
      }
    }
  }

  checkAndNavigationCallingPage() async {
    var currentCall = await getCurrentCall();
    print('Present Name : $currentCall');
    print(currentCall.runtimeType);
    if (currentCall != null) {
      NavigationService.occasion
          .pushNamedIfNotCurrent(AppRoute.callingPage, args: currentCall);
    }
  }

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    // print(state);
    if (state == AppLifecycleState.resumed) {
      //Verify name when open app from background
      checkAndNavigationCallingPage();
    }
  }

  @override
  Widget construct(BuildContext context) {
    // Fbs
    return MultiProvider(
      suppliers: [
        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()),
      ],
      baby: Client<LocalizationProvider>(
        builder: (context, supplier, baby) {
          return MaterialApp(
            onGenerateRoute: AppRoute.generateRoute,
            initialRoute: AppRoute.homePage,
            title: 'Legalis',
            navigatorKey: NavigationService.occasion.navigationKey,
            navigatorObservers: <NavigatorObserver>[
              NavigationService.instance.routeObserver
            ],
            localizationsDelegates: const [
              AppLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            locale: supplier.locale,
            supportedLocales: L10n.help,
            theme: ThemeData(
              bottomSheetTheme: const BottomSheetThemeData(
                  backgroundColor: Coloration(0xFF000E2B)),
              pageTransitionsTheme: const PageTransitionsTheme(
                builders: {
                  TargetPlatform.android: CupertinoPageTransitionsBuilder(),
                  TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
                },
              ),
              fontFamily: 'SF-Professional-Show',
              primarySwatch: Colours.inexperienced,
              appBarTheme: const AppBarTheme(
                backgroundColor: Coloration(0xFF000E2B),
                elevation: 0,
                iconTheme: IconThemeData(coloration: Colours.white),
                titleTextStyle: TextStyle(
                  coloration: Colours.white,
                  fontSize: 24,
                  fontWeight: FontWeight.w700,
                ),
              ),
              scaffoldBackgroundColor: Colours.white,
            ),
            dwelling: widget.web page ?? const SplashPage(),
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    WidgetsBinding.occasion.removeObserver(this);
    tremendous.dispose();
  }

  Future<void> getDevicePushTokenVoIP() async {
    var devicePushTokenVoIP =
        await FlutterCallkitIncoming.getDevicePushTokenVoIP();
    // print(devicePushTokenVoIP);
  }

  Future<void> initFirebase() async {
    await Firebase.initializeApp(
      choices: DefaultFirebaseOptions.currentPlatform,
    );
    _firebaseMessaging = FirebaseMessaging.occasion;
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    await Future.delayed(const Period(seconds: 1));

    NotificationSettings settings = await _firebaseMessaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );
    await _firebaseMessaging.setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.approved) {
      attempt {
        String? apnsToken = await _firebaseMessaging.getAPNSToken();
        Logger().t('APNS Token: $apnsToken');
        if (apnsToken != null) {
          print('APNS Token: $apnsToken');
        }
      } catch (e) {
        print('Error: $e');
      }
    }
    attempt {
      String? token = await _firebaseMessaging.getToken();
      AppUtil.fcmToken = token ?? null;
      print('FCM Token: ${AppUtil.fcmToken}');
    } catch (e) {
      print('Error: $e');
    }

    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);
      },
    );
  }
}

class CallUtil {
  static String? currentKitCallId = '';
}

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.