{"id":"5ece3b2a-cf85-48d6-8855-d3a2a2354193","shortId":"vzKstu","kind":"skill","title":"cometchat-flutter-v5-push","tagline":"Use when implementing push notifications with CometChat Flutter UIKit v5. Covers FCM (Android), APNs (iOS), VoIP calls, token lifecycle, local notifications, and tap-to-navigate.","description":"# CometChat Flutter UIKit v5 — Push Notifications\n\nPush notification setup for Android (FCM) and iOS (APNs + VoIP).\n\n## Dependencies\n\n```yaml\ndependencies:\n  firebase_core: ^3.9.0\n  firebase_messaging: ^15.1.6\n  flutter_local_notifications: ^18.0.0\n  flutter_callkit_incoming: # for VoIP call notifications\n  app_badge_plus: ^1.2.6  # badge count\n```\n\n## Architecture Overview\n\n```\nnotifications/\n├── models/\n│   ├── payload.dart              # PayloadData model for parsing FCM data\n│   ├── call_action.dart          # CallAction enum (initiated, cancelled, unanswered)\n│   ├── call_type.dart            # CallType enum (audio, video)\n│   └── notification_message_type.dart  # Message type constants\n├── services/\n│   ├── android_notification_service/\n│   │   ├── firebase_services.dart       # FCM init, listeners, token management\n│   │   ├── local_notification_handler.dart  # Local notification display + tap handling\n│   │   ├── voip_notification_handler.dart   # VoIP call display, accept, decline\n│   │   └── notification_launch_handler.dart # Terminated state launch handling\n│   ├── iOS_notification_service/\n│   │   └── apns_services.dart           # APNs connector, VoIP token, CallKit\n│   └── cometchat_service/\n│       └── cometchat_services.dart      # PNRegistry (token registration/unregistration)\n```\n\n## Token Registration — `CometChatNotifications.registerPushToken`\n\nThe kit's only public push surface is `CometChatNotifications.registerPushToken(platform, {providerId, fcmToken, deviceToken, voipToken, onSuccess, onError})` (and `unregisterPushToken({onSuccess, onError})`). The sample app wraps this in an extension named `PNRegistry on CometChatService` (see `sample_app_push_notifications/lib/notifications/services/cometchat_service/cometchat_services.dart`) that picks the right provider ID + platform constant for FCM-Android / FCM-iOS / APNs / APNs-VoIP. **Copy that helper into your project, or call `CometChatNotifications.registerPushToken` directly** — `PNRegistry` is a sample-app extension, not importable from any cometchat package.\n\n```dart\n// Direct kit API:\nimport 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';\n\nawait CometChatNotifications.registerPushToken(\n  PushPlatforms.FCM_FLUTTER_ANDROID,    // platform — first positional arg\n  providerId: fcmProviderId,            // dashboard FCM provider ID\n  fcmToken: token,                      // use fcmToken / deviceToken / voipToken depending on platform\n  onSuccess: (response) => debugPrint('registered: $response'),\n  onError: (e) => debugPrint('register failed: $e'),\n);\n\n// On logout:\nawait CometChatNotifications.unregisterPushToken(\n  onSuccess: (_) {},\n  onError: (e) => debugPrint('unregister failed: $e'),\n);\n```\n\n```dart\n// Or use the sample-app PNRegistry helper after copying it into your project:\nPNRegistry.registerPNService(token, true, false);   // (token, isFcm, isVoip)\nPNRegistry.unregisterPNService();\n```\n\nPlatform mapping:\n- FCM Android → `PushPlatforms.FCM_FLUTTER_ANDROID`\n- FCM iOS → `PushPlatforms.FCM_FLUTTER_IOS`\n- APNs Device → `PushPlatforms.APNS_FLUTTER_DEVICE`\n- APNs VoIP → `PushPlatforms.APNS_FLUTTER_VOIP`\n\nProvider IDs come from `AppCredentials.fcmProviderId` / `AppCredentials.apnProviderId` (your own constants — these are dashboard-configured values, not kit exports).\n\nThe remaining examples below assume you've copied `PNRegistry` from the sample app. If you call `CometChatNotifications.registerPushToken` directly, swap the call sites accordingly.\n\n## Android — FCM Setup\n\n### 1. Background handler (must be top-level function)\n\n```dart\n@pragma('vm:entry-point')\nFuture<void> firebaseMessagingBackgroundHandler(RemoteMessage rMessage) async {\n  LocalNotificationService.showNotification(rMessage.data, rMessage, \"\", false);\n  await VoipNotificationHandler.displayIncomingCall(rMessage);\n}\n```\n\n### 2. Initialize in dashboard/home screen\n\n```dart\nclass FirebaseService {\n  Future<void> init(BuildContext context) async {\n    _firebaseMessaging = FirebaseMessaging.instance;\n    await requestPermissions();\n    await initListeners(context);\n\n    String? token = await _firebaseMessaging.getToken();\n    if (token != null) {\n      PNRegistry.registerPNService(token, true, false);\n    }\n  }\n}\n```\n\n### 3. Listener setup\n\n```dart\n// Background messages\nFirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);\n\n// Token refresh\n_firebaseMessaging.onTokenRefresh.listen((token) {\n  PNRegistry.registerPNService(token, true, false);\n});\n\n// Foreground messages\nFirebaseMessaging.onMessage.listen((message) {\n  LocalNotificationService.showNotification(message.data, message, conversationId, isAgentic);\n});\n\n// Tap from background\nFirebaseMessaging.onMessageOpenedApp.listen((message) {\n  openNotification(context, message, conversationId);\n});\n\n// Tap from terminated state\nFirebaseMessaging.instance.getInitialMessage().then((message) {\n  if (message != null) openNotification(context, message, conversationId);\n});\n```\n\n## iOS — APNs Setup\n\n```dart\nfinal _connector = ApnsPushConnector();\n_connector.shouldPresent = (x) => Future.value(false);\n\n_connector.configure(\n  onLaunch: (message) async { openNotification(message, context, \"\"); },\n  onResume: (message) async { openNotification(message, context, conversationId); },\n  onMessage: (message) async { _showNotification(message.data, message, conversationId, isAgentic); },\n);\n\n_connector.requestNotificationPermissions();\n\n// APNs device token\n_connector.token.addListener(() {\n  PNRegistry.registerPNService(_connector.token.value!, false, false);\n});\n\n// VoIP token\nFlutterCallkitIncoming.getDevicePushTokenVoIP().then((voipToken) {\n  PNRegistry.registerPNService(voipToken, false, true);\n});\n```\n\n## VoIP Call Notifications\n\n### Display incoming call (both platforms)\n\n```dart\nstatic Future<void> displayIncomingCall(RemoteMessage rMessage) async {\n  PayloadData callPayload = PayloadData.fromJson(rMessage.data);\n  if (callPayload.type == 'call' && callPayload.callAction == CallAction.initiated) {\n    CallKitParams params = CallKitParams(\n      id: callPayload.sessionId,\n      nameCaller: callPayload.senderName,\n      type: (callPayload.callType == CallType.audio) ? 0 : 1,\n      duration: 45000,\n    );\n    await FlutterCallkitIncoming.showCallkitIncoming(params);\n  }\n}\n```\n\n### Accept/Decline via CallKit events\n\n```dart\nFlutterCallkitIncoming.onEvent.listen((CallEvent? callEvent) {\n  switch (callEvent?.event) {\n    case Event.actionCallAccept:\n      VoipNotificationHandler.acceptVoipCall(callEvent, context);\n      break;\n    case Event.actionCallDecline:\n      VoipNotificationHandler.declineVoipCall(callEvent);\n      break;\n    case Event.actionCallTimeout:\n    case Event.actionCallEnded:\n      VoipNotificationHandler.endCall(sessionId: callEvent?.body['id']);\n      break;\n  }\n});\n```\n\n## Local Notification Display\n\nUses `flutter_local_notifications` with inbox-style grouping per conversation:\n\n```dart\n// Skip if user is viewing the same conversation\nif (conversationId == notifConversationId) return;\n\n// Skip call-type notifications (handled by CallKit)\nif (data[\"type\"] == \"call\") return;\n\n// Show with stable ID per conversation (replaces previous)\nfinal notificationId = conversationId.hashCode;\nawait flutterLocalNotificationsPlugin.show(notificationId, title, body, details, payload: jsonPayload);\n```\n\n## Tap-to-Navigate\n\n```dart\nstatic void handleNotificationTap(NotificationResponse? response) async {\n  if (response?.payload != null) {\n    final body = jsonDecode(response!.payload!);\n    NotificationDataModel model = NotificationDataModel.fromJson(body);\n\n    User? user; Group? group;\n    if (model.receiverType == \"user\") {\n      user = await CometChat.getUser(model.sender);\n    } else {\n      group = await CometChat.getGroup(model.receiver);\n    }\n\n    if (model.type == \"chat\" && (user != null || group != null)) {\n      Navigator.of(CallNavigationContext.navigatorKey.currentContext!).push(\n        MaterialPageRoute(builder: (_) => MessagesSample(user: user, group: group)),\n      );\n    }\n  }\n}\n```\n\n## Terminated State Handling\n\n```dart\n// In main()\nfinal launchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();\nif (launchDetails?.didNotificationLaunchApp == true) {\n  NotificationLaunchHandler.pendingNotificationResponse = launchDetails!.notificationResponse;\n}\n\n// In dashboard initState()\nFuture.delayed(Duration(milliseconds: 300), () {\n  final response = NotificationLaunchHandler.pendingNotificationResponse;\n  if (response != null) {\n    NotificationLaunchHandler.pendingNotificationResponse = null;\n    LocalNotificationService.handleNotificationTap(response, isTerminatedState: true);\n  }\n});\n```\n\n## Logout — Unregister Token\n\n```dart\nPNRegistry.unregisterPNService();\n// Then: CometChatUIKit.logout(...)\n```\n\n## Checklist — Push Notifications\n\n- [ ] Firebase initialized before CometChat init\n- [ ] FCM token registered via `PNRegistry.registerPNService(token, true, false)`\n- [ ] APNs device + VoIP tokens registered on iOS\n- [ ] Background handler is top-level `@pragma('vm:entry-point')` function\n- [ ] Token refresh listener re-registers token\n- [ ] Local notifications skip current active conversation\n- [ ] Call notifications handled via `FlutterCallkitIncoming`, not local notifications\n- [ ] Tap-to-navigate uses `CallNavigationContext.navigatorKey.currentContext`\n- [ ] Tokens unregistered on logout via `PNRegistry.unregisterPNService()`\n- [ ] Terminated state launch handled via `NotificationLaunchHandler`","tags":["cometchat","flutter","push","skills","agent-skills","ai-agent","chat","claude-code","cursor","messaging","nextjs","react"],"capabilities":["skill","source-cometchat","skill-cometchat-flutter-v5-push","topic-agent-skills","topic-ai-agent","topic-chat","topic-claude-code","topic-cometchat","topic-cursor","topic-messaging","topic-nextjs","topic-react","topic-react-native","topic-ui-kit"],"categories":["cometchat-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cometchat/cometchat-skills/cometchat-flutter-v5-push","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cometchat/cometchat-skills","source_repo":"https://github.com/cometchat/cometchat-skills","install_from":"skills.sh"}},"qualityScore":"0.463","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (9,528 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:04:50.387Z","embedding":null,"createdAt":"2026-05-07T13:05:09.942Z","updatedAt":"2026-05-18T19:04:50.387Z","lastSeenAt":"2026-05-18T19:04:50.387Z","tsv":"'0':559 '1':368,560 '1.2.6':71 '15.1.6':56 '18.0.0':60 '2':395 '3':426 '3.9.0':53 '300':737 '45000':562 'accept':120 'accept/decline':566 'accord':364 'activ':803 'android':18,42,101,193,237,305,308,365 'api':227 'apn':19,46,131,197,199,314,319,475,508,773 'apns-voip':198 'apns_services.dart':130 'apnspushconnector':480 'app':68,167,179,216,285,354 'appcredentials.apnproviderid':329 'appcredentials.fcmproviderid':328 'architectur':74 'arg':241 'assum':346 'async':387,407,488,494,501,539,667 'audio':94 'await':233,270,392,410,412,417,563,649,689,694,722 'background':369,430,453,780 'badg':69,72 'bodi':595,653,673,680 'break':582,587,597 'buildcontext':405 'builder':708 'call':22,66,118,208,357,362,526,530,546,627,636,805 'call-typ':626 'call_action.dart':85 'call_type.dart':91 'callact':86 'callaction.initiated':548 'callev':572,573,575,580,586,594 'callkit':62,135,568,632 'callkitparam':549,551 'callnavigationcontext.navigatorkey.currentcontext':705,818 'callpayload':541 'callpayload.callaction':547 'callpayload.calltype':557 'callpayload.sendername':555 'callpayload.sessionid':553 'callpayload.type':545 'calltyp':92 'calltype.audio':558 'cancel':89 'case':577,583,588,590 'chat':231,699 'checklist':757 'class':401 'come':326 'cometchat':2,12,32,136,222,230,763 'cometchat-flutter-v5-push':1 'cometchat.getgroup':695 'cometchat.getuser':690 'cometchat_services.dart':138 'cometchatnotifications.registerpushtoken':144,153,209,234,358 'cometchatnotifications.unregisterpushtoken':271 'cometchatservic':176 'cometchatuikit.logout':756 'configur':337 'connector':132,479 'connector.configure':485 'connector.requestnotificationpermissions':507 'connector.shouldpresent':481 'connector.token.addlistener':511 'connector.token.value':513 'constant':99,189,332 'context':406,414,457,471,491,497,581 'convers':611,620,643,804 'conversationid':449,459,473,498,505,622 'conversationid.hashcode':648 'copi':201,289,349 'core':52 'count':73 'cover':16 'current':802 'dart':224,279,377,400,429,477,533,570,612,661,717,753 'dashboard':244,336,732 'dashboard-configur':335 'dashboard/home':398 'data':84,634 'debugprint':259,264,275 'declin':121 'depend':48,50,254 'detail':654 'devic':315,318,509,774 'devicetoken':157,252 'didnotificationlaunchapp':726 'direct':210,225,359 'display':113,119,528,600 'displayincomingcal':536 'durat':561,735 'e':263,267,274,278 'els':692 'entri':381,789 'entry-point':380,788 'enum':87,93 'event':569,576 'event.actioncallaccept':578 'event.actioncalldecline':584 'event.actioncallended':591 'event.actioncalltimeout':589 'exampl':344 'export':341 'extens':172,217 'fail':266,277 'fals':297,391,425,441,484,514,515,523,772 'fcm':17,43,83,105,192,195,245,304,309,366,765 'fcm-android':191 'fcm-io':194 'fcmproviderid':243 'fcmtoken':156,248,251 'final':478,646,672,720,738 'firebas':51,54,760 'firebase_services.dart':104 'firebasemessag':408 'firebasemessaging.gettoken':418 'firebasemessaging.instance':409 'firebasemessaging.instance.getinitialmessage':464 'firebasemessaging.onbackgroundmessage':432 'firebasemessaging.onmessage.listen':444 'firebasemessaging.onmessageopenedapp.listen':454 'firebasemessaging.ontokenrefresh.listen':436 'firebasemessagingbackgroundhandl':384,433 'firebaseservic':402 'first':239 'flutter':3,13,33,57,61,236,307,312,317,322,602 'fluttercallkitincom':809 'fluttercallkitincoming.getdevicepushtokenvoip':518 'fluttercallkitincoming.onevent.listen':571 'fluttercallkitincoming.showcallkitincoming':564 'flutterlocalnotificationsplugin.getnotificationapplaunchdetails':723 'flutterlocalnotificationsplugin.show':650 'foreground':442 'function':376,791 'futur':383,403,535 'future.delayed':734 'future.value':483 'group':609,683,684,693,702,712,713 'handl':115,126,630,716,807,828 'handlenotificationtap':664 'handler':370,781 'helper':203,287 'id':187,247,325,552,596,641 'implement':8 'import':219,228 'inbox':607 'inbox-styl':606 'incom':63,529 'init':106,404,764 'initi':88,396,761 'initlisten':413 'initst':733 'io':20,45,127,196,310,313,474,779 'isagent':450,506 'isfcm':299 'isterminatedst':748 'isvoip':300 'jsondecod':674 'jsonpayload':656 'kit':146,226,340 'launch':125,827 'launchdetail':721,725,729 'level':375,785 'lifecycl':24 'listen':107,427,794 'local':25,58,111,598,603,799,811 'local_notification_handler.dart':110 'localnotificationservice.handlenotificationtap':746 'localnotificationservice.shownotification':388,446 'logout':269,750,822 'main':719 'manag':109 'map':303 'materialpagerout':707 'messag':55,97,431,443,445,448,455,458,466,468,472,487,490,493,496,500,504 'message.data':447,503 'messagessampl':709 'millisecond':736 'model':77,80,678 'model.receiver':696 'model.receivertype':686 'model.sender':691 'model.type':698 'must':371 'name':173 'namecal':554 'navig':31,660,816 'navigator.of':704 'notif':10,26,37,39,59,67,76,102,112,128,527,599,604,629,759,800,806,812 'notifconversationid':623 'notification_launch_handler.dart':122 'notification_message_type.dart':96 'notificationdatamodel':677 'notificationdatamodel.fromjson':679 'notificationid':647,651 'notificationlaunchhandl':830 'notificationlaunchhandler.pendingnotificationresponse':728,740,744 'notificationrespons':665,730 'notifications/lib/notifications/services/cometchat_service/cometchat_services.dart':181 'null':421,469,671,701,703,743,745 'onerror':160,164,262,273 'onlaunch':486 'onmessag':499 'onresum':492 'onsuccess':159,163,257,272 'opennotif':456,470,489,495 'overview':75 'packag':223,229 'param':550,565 'pars':82 'payload':655,670,676 'payload.dart':78 'payloaddata':79,540 'payloaddata.fromjson':542 'per':610,642 'pick':183 'platform':154,188,238,256,302,532 'plus':70 'pnregistri':139,174,211,286,350 'pnregistry.registerpnservice':294,422,438,512,521,769 'pnregistry.unregisterpnservice':301,754,824 'point':382,790 'posit':240 'pragma':378,786 'previous':645 'project':206,293 'provid':186,246,324 'providerid':155,242 'public':149 'push':5,9,36,38,150,180,706,758 'pushplatforms.apns':316,321 'pushplatforms.fcm':235,306,311 're':796 're-regist':795 'refresh':435,793 'regist':260,265,767,777,797 'registr':143 'registration/unregistration':141 'remain':343 'remotemessag':385,537 'replac':644 'requestpermiss':411 'respons':258,261,666,669,675,739,742,747 'return':624,637 'right':185 'rmessag':386,390,394,538 'rmessage.data':389,543 'sampl':166,178,215,284,353 'sample-app':214,283 'screen':399 'see':177 'servic':100,103,129,137 'sessionid':593 'setup':40,367,428,476 'show':638 'shownotif':502 'site':363 'skill' 'skill-cometchat-flutter-v5-push' 'skip':613,625,801 'source-cometchat' 'stabl':640 'state':124,463,715,826 'static':534,662 'string':415 'style':608 'surfac':151 'swap':360 'switch':574 'tap':29,114,451,460,658,814 'tap-to-navig':28,657,813 'termin':123,462,714,825 'titl':652 'token':23,108,134,140,142,249,295,298,416,420,423,434,437,439,510,517,752,766,770,776,792,798,819 'top':374,784 'top-level':373,783 'topic-agent-skills' 'topic-ai-agent' 'topic-chat' 'topic-claude-code' 'topic-cometchat' 'topic-cursor' 'topic-messaging' 'topic-nextjs' 'topic-react' 'topic-react-native' 'topic-ui-kit' 'true':296,424,440,524,727,749,771 'type':98,556,628,635 'uikit':14,34 'uikit/cometchat_chat_uikit.dart':232 'unansw':90 'unregist':276,751,820 'unregisterpushtoken':162 'use':6,250,281,601,817 'user':615,681,682,687,688,700,710,711 'v5':4,15,35 'valu':338 've':348 'via':567,768,808,823,829 'video':95 'view':617 'vm':379,787 'void':663 'voip':21,47,65,117,133,200,320,323,516,525,775 'voip_notification_handler.dart':116 'voipnotificationhandler.acceptvoipcall':579 'voipnotificationhandler.declinevoipcall':585 'voipnotificationhandler.displayincomingcall':393 'voipnotificationhandler.endcall':592 'voiptoken':158,253,520,522 'wrap':168 'x':482 'yaml':49","prices":[{"id":"7b3d6b6f-bf96-463a-b149-20a40562d3d2","listingId":"5ece3b2a-cf85-48d6-8855-d3a2a2354193","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cometchat","category":"cometchat-skills","install_from":"skills.sh"},"createdAt":"2026-05-07T13:05:09.942Z"}],"sources":[{"listingId":"5ece3b2a-cf85-48d6-8855-d3a2a2354193","source":"github","sourceId":"cometchat/cometchat-skills/cometchat-flutter-v5-push","sourceUrl":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-flutter-v5-push","isPrimary":false,"firstSeenAt":"2026-05-07T13:05:09.942Z","lastSeenAt":"2026-05-18T19:04:50.387Z"}],"details":{"listingId":"5ece3b2a-cf85-48d6-8855-d3a2a2354193","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cometchat","slug":"cometchat-flutter-v5-push","github":{"repo":"cometchat/cometchat-skills","stars":27,"topics":["agent-skills","ai-agent","chat","claude-code","cometchat","cursor","messaging","nextjs","react","react-native","ui-kit"],"license":null,"html_url":"https://github.com/cometchat/cometchat-skills","pushed_at":"2026-05-18T05:04:24Z","description":"Add CometChat chat to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.","skill_md_sha":"a080be0d0b57d0ffef0580b9fb3b92066885a69e","skill_md_path":"skills/cometchat-flutter-v5-push/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-flutter-v5-push"},"layout":"multi","source":"github","category":"cometchat-skills","frontmatter":{"name":"cometchat-flutter-v5-push","license":"MIT","description":"Use when implementing push notifications with CometChat Flutter UIKit v5. Covers FCM (Android), APNs (iOS), VoIP calls, token lifecycle, local notifications, and tap-to-navigate.","compatibility":"cometchat_chat_uikit ^5.2.14; cometchat_calls_uikit ^5.0.15; firebase_messaging; flutter_local_notifications; flutter_callkit_incoming"},"skills_sh_url":"https://skills.sh/cometchat/cometchat-skills/cometchat-flutter-v5-push"},"updatedAt":"2026-05-18T19:04:50.387Z"}}