From 4987750ddd5d3cb7ba70fdf4cda144ab3c2532b3 Mon Sep 17 00:00:00 2001 From: Alessio Zeni Date: Wed, 12 Oct 2022 11:01:05 +0200 Subject: [PATCH 1/5] apns only --- example/pubspec.lock | 33 ++++++++----------- flutter_apns/pubspec.lock | 31 +++++++---------- .../ios/Classes/FlutterApnsPlugin.swift | 9 ++++- flutter_apns_only/lib/flutter_apns_only.dart | 27 ++++++++++----- flutter_apns_only/pubspec.lock | 31 +++++++---------- 5 files changed, 63 insertions(+), 68 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 50e6fbb..0419a9f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -14,7 +14,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -28,21 +28,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -70,7 +63,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -138,7 +131,7 @@ packages: path: "../flutter_apns" relative: true source: path - version: "1.5.4" + version: "1.6.0" flutter_apns_only: dependency: transitive description: @@ -190,28 +183,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_provider: dependency: "direct main" description: @@ -300,7 +293,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -321,21 +314,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" timezone: dependency: transitive description: diff --git a/flutter_apns/pubspec.lock b/flutter_apns/pubspec.lock index feda0ef..b7c3f30 100644 --- a/flutter_apns/pubspec.lock +++ b/flutter_apns/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,21 +21,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -49,7 +42,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" firebase_core: dependency: "direct main" description: @@ -127,28 +120,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" plugin_platform_interface: dependency: transitive description: @@ -167,7 +160,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -188,21 +181,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" vector_math: dependency: transitive description: diff --git a/flutter_apns_only/ios/Classes/FlutterApnsPlugin.swift b/flutter_apns_only/ios/Classes/FlutterApnsPlugin.swift index 6b4c401..58acccf 100644 --- a/flutter_apns_only/ios/Classes/FlutterApnsPlugin.swift +++ b/flutter_apns_only/ios/Classes/FlutterApnsPlugin.swift @@ -129,6 +129,9 @@ func getFlutterError(_ error: Error) -> FlutterError { var provisionalRequested = false if #available(iOS 12.0, *) { + if readBool("criticalAlert") { + options.append(.criticalAlert) + } if readBool("provisional") { options.append(.provisional) provisionalRequested = true @@ -145,12 +148,16 @@ func getFlutterError(_ error: Error) -> FlutterError { } center.getNotificationSettings { (settings) in - let map = [ + var map = [ "sound": settings.soundSetting == .enabled, "badge": settings.badgeSetting == .enabled, "alert": settings.alertSetting == .enabled, "provisional": granted && provisionalRequested ] + + if #available(iOS 12.0, *) { + map["criticalAlert"] = settings.criticalAlertSetting == .enabled + } self.channel.invokeMethod("onIosSettingsRegistered", arguments: map) } diff --git a/flutter_apns_only/lib/flutter_apns_only.dart b/flutter_apns_only/lib/flutter_apns_only.dart index 55fa133..f84b343 100644 --- a/flutter_apns_only/lib/flutter_apns_only.dart +++ b/flutter_apns_only/lib/flutter_apns_only.dart @@ -33,14 +33,16 @@ class ApnsPushConnectorOnly { ApnsMessageHandler? _onResume; Future requestNotificationPermissions( - [IosNotificationSettings iosSettings = const IosNotificationSettings()]) async { + [IosNotificationSettings iosSettings = + const IosNotificationSettings()]) async { final bool? result = await _channel.invokeMethod( 'requestNotificationPermissions', iosSettings.toMap()); return result ?? false; } Future getAuthorizationStatus() async { - return _authorizationStatusForString(await _channel.invokeMethod('getAuthorizationStatus', [])); + return _authorizationStatusForString( + await _channel.invokeMethod('getAuthorizationStatus', [])); } final StreamController _iosSettingsStreamController = @@ -142,23 +144,30 @@ class ApnsPushConnectorOnly { } class IosNotificationSettings { - const IosNotificationSettings({ - this.sound = true, - this.alert = true, - this.badge = true, - }); + const IosNotificationSettings( + {this.sound = true, + this.alert = true, + this.badge = true, + this.criticalAlert = false}); IosNotificationSettings._fromMap(Map settings) : sound = settings['sound'], alert = settings['alert'], - badge = settings['badge']; + badge = settings['badge'], + criticalAlert = settings['criticalAlert']; final bool? sound; final bool? alert; final bool? badge; + final bool? criticalAlert; Map toMap() { - return {'sound': sound, 'alert': alert, 'badge': badge}; + return { + 'sound': sound, + 'alert': alert, + 'badge': badge, + 'criticalAlert': criticalAlert + }; } @override diff --git a/flutter_apns_only/pubspec.lock b/flutter_apns_only/pubspec.lock index ef5e1b7..7af4cde 100644 --- a/flutter_apns_only/pubspec.lock +++ b/flutter_apns_only/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,21 +21,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -49,7 +42,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -66,28 +59,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -99,7 +92,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -120,21 +113,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" vector_math: dependency: transitive description: From 1b570374692530f3050b864d6e14f2b5478f56c4 Mon Sep 17 00:00:00 2001 From: Alessio Zeni Date: Wed, 12 Oct 2022 11:01:39 +0200 Subject: [PATCH 2/5] version --- flutter_apns_only/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_apns_only/pubspec.yaml b/flutter_apns_only/pubspec.yaml index b3e5eb9..a813dd8 100644 --- a/flutter_apns_only/pubspec.yaml +++ b/flutter_apns_only/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_apns_only description: APNS push notification plugin. Works only on iOS. See flutter_apns for apns & firebase combo -version: 1.6.0 +version: 1.6.1 homepage: https://github.com/mwaylabs/flutter-apns environment: From 01c17f3996fbcfad93b5c828d886c2d9f4921638 Mon Sep 17 00:00:00 2001 From: Alessio Zeni Date: Wed, 12 Oct 2022 11:07:47 +0200 Subject: [PATCH 3/5] package --- flutter_apns/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_apns/pubspec.yaml b/flutter_apns/pubspec.yaml index 6ccee89..8ccdf9d 100644 --- a/flutter_apns/pubspec.yaml +++ b/flutter_apns/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: sdk: flutter firebase_core: ^1.20.0 firebase_messaging: ^12.0.1 - flutter_apns_only: ^1.5.2 + flutter_apns_only: ^1.6.1 dev_dependencies: flutter_test: From bee78a8f326f8e9dde20e1a7188336fb00f0d078 Mon Sep 17 00:00:00 2001 From: Alessio Zeni Date: Wed, 12 Oct 2022 11:20:26 +0200 Subject: [PATCH 4/5] readme --- flutter_apns/README.md | 367 ++++++++++++++++++++++++++++++++--------- 1 file changed, 293 insertions(+), 74 deletions(-) diff --git a/flutter_apns/README.md b/flutter_apns/README.md index 4d700f0..3694dc9 100644 --- a/flutter_apns/README.md +++ b/flutter_apns/README.md @@ -1,209 +1,428 @@ + # apns + + Plugin to implement APNS push notifications on iOS and Firebase on Android. + + ## Why this plugin was made? + + Currently, the only available push notification plugin is `firebase_messaging`. This means that, even on iOS, you will need to setup firebase and communicate with Google to send push notification. This plugin solves the problem by providing native APNS implementation while leaving configured Firebase for Android. + + ## Usage + 1. Configure firebase on Android according to instructions: https://pub.dartlang.org/packages/firebase_messaging. + 2. On iOS, make sure you have correctly configured your app to support push notifications, and that you have generated certificate/token for sending pushes. For more infos see section [How to run example app on iOS](#how-to-run-example-app-on-ios) + + 3. Add the following lines to the `didFinishLaunchingWithOptions` method in the AppDelegate.m/AppDelegate.swift file of your iOS project + + Objective-C: + ```objc + if (@available(iOS 10.0, *)) { - [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; + +[UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; + } + ``` + + Swift: + ```swift -if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + +if #available(iOS 10.0, *) { + +UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + ``` + + 4. Add `flutter_apns` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). + 5. Using `createPushConnector()` method, configure push service according to your needs. `PushConnector` closely resembles `FirebaseMessaging`, so Firebase samples may be useful during implementation. You should create the connector as soon as possible to get the onLaunch callback working on closed app launch. + ```dart -import 'package:flutter_apns/apns.dart'; + +import 'package:flutter_apns/apns.dart'; + + final connector = createPushConnector(); + connector.configure( - onLaunch: _onLaunch, - onResume: _onResume, - onMessage: _onMessage, + +onLaunch: _onLaunch, + +onResume: _onResume, + +onMessage: _onMessage, + ); + connector.requestNotificationPermissions() + ``` + 6. Build on device and test your solution using Firebase Console (Android) and CURL (iOS, see [How to run example app on iOS](#how-to-run-example-app-on-ios)). + + ## Additional APNS features: + ### Displaying notification while in foreground + + ```dart + final connector = createPushConnector(); + if (connector is ApnsPushConnector) { - connector.shouldPresent = (x) => Future.value(true); + +connector.shouldPresent = (x) => Future.value(true); + } + ``` + + ### Handling predefined actions + + Firstly, configure supported actions: + ```dart + final connector = createPushConnector(); + if (connector is ApnsPushConnector) { - connector.setNotificationCategories([ - UNNotificationCategory( - identifier: 'MEETING_INVITATION', - actions: [ - UNNotificationAction( - identifier: 'ACCEPT_ACTION', - title: 'Accept', - options: [], - ), - UNNotificationAction( - identifier: 'DECLINE_ACTION', - title: 'Decline', - options: [], - ), - ], - intentIdentifiers: [], - options: [], - ), - ]); + +connector.setNotificationCategories([ + +UNNotificationCategory( + +identifier: 'MEETING_INVITATION', + +actions: [ + +UNNotificationAction( + +identifier: 'ACCEPT_ACTION', + +title: 'Accept', + +options: [], + +), + +UNNotificationAction( + +identifier: 'DECLINE_ACTION', + +title: 'Decline', + +options: [], + +), + +], + +intentIdentifiers: [], + +options: [], + +), + +]); + } + ``` + + Then, handle possible actions in your push handler: + ```dart + Future onPush(String name, RemoteMessage payload) { - final action = UNNotificationAction.getIdentifier(payload.data); - if (action == 'MEETING_INVITATION') { - // do something - } +final action = UNNotificationAction.getIdentifier(payload.data); + + + +if (action == 'MEETING_INVITATION') { + +// do something + +} + + + +return Future.value(true); - return Future.value(true); } + ``` + + Note: if user clickes your notification while app is in the background, push will be delivered through onResume without actually waking up the app. Make sure your handling of given action is quick and error free, as execution time in for apps running in the background is very limited. + + Check the example project for fully working code. + + ## Enabling FirebaseCore + If you want to use firebase, but not firebase messaging, add this configuration entry in your Info.plist (to avoid MissingPluginException): + + ``` + flutter_apns.disable_firebase_core + + ``` + + ## flutter_apns_only - APNS without firebase + If only care about apns - use flutter_apns_only plugin. It does not depend on firebase. To ensure no swizzling (which is needed by original plugin to disable firebase) takes place, add this configuration entry in your Info.plist: + + ```plist + flutter_apns.disable_swizzling + + ``` + +## Critical Alert +For use iOS Critical alert request entitlement to Apple throught this form: +[https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/](https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/) + +After that assign CriticalAlert capability to your app identifier, update provisioning profiles and add this entry to app `.entitlements` file in your code: +``` + com.apple.developer.usernotifications.critical-alerts + +``` + + ## Troubleshooting + + 1. Ensure that you are testing on actual device. NOTE: this may not be needed from 11.4: https://ohmyswift.com/blog/2020/02/13/simulating-remote-push-notifications-in-a-simulator/ + 2. If onToken method is not being called, add error logging to your AppDelegate, see code below. + 3. Open Console app for macOS, connect your device, and run your app. Search for "PUSH registration failed" string in logs. The error message will tell you what was wrong. + + *swift* + ```swift -import UIKit -import Flutter + +import UIKit + +import Flutter + + @UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - NSLog("PUSH registration failed: \(error)") - } + +@objc class AppDelegate: FlutterAppDelegate { + +override func application( + +_ application: UIApplication, + +didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + +) -> Bool { + +GeneratedPluginRegistrant.register(with: self) + +return super.application(application, didFinishLaunchingWithOptions: launchOptions) + +} + + + +func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + +NSLog("PUSH registration failed: \(error)") + +} + } + + ``` + + *objc* + ```objc -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" -@implementation AppDelegate +#include "AppDelegate.h" + +#include "GeneratedPluginRegistrant.h" + + + +@implementation AppDelegate + + - (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. - return [super application:application didFinishLaunchingWithOptions:launchOptions]; + +didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + +[GeneratedPluginRegistrant registerWithRegistry:self]; + +// Override point for customization after application launch. + +return [super application:application didFinishLaunchingWithOptions:launchOptions]; + } + + -(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - NSLog(@"%@", error); + +NSLog(@"%@", error); + } + + @end + ``` + ## How to run example app on iOS + Setting up push notifications on iOS can be tricky since there is no way to permit Apple Push Notification Service (APNS) which requires a complicated certificate setup. The following guide describes a step by step approach to send push notifications from your Mac to an iPhone utilizing the example app of this package. This guide only describes debug environment setup. + + 1. Open example ios folder with Xcode + 2. Select Runner -> Signing & Capabilities + 3. Select your development team and add a globally unique bundle identifier. The one on the picture is already occupied: - ![](example/assets/Xcode_setup.png?raw=true) + +![](example/assets/Xcode_setup.png?raw=true) + 4. Go to https://developer.apple.com/account/resources/identifiers/list/bundleId and press on the plus button + 5. Select "App IDs" and press continue - ![](example/assets/register_app.png?raw=true) + +![](example/assets/register_app.png?raw=true) + 6. Select type "App" + 7. Select "App ID Prefix" which should be same as "Team ID" + 8. Enter description and bundle ID. The latter one needs to be the same as the bundle ID specified in 3. + 9. Select push notification capability - ![](example/assets/select_capability.png?raw=true) + +![](example/assets/select_capability.png?raw=true) + 11. Press on "Continue" and then on "Register" + 12. Go to https://developer.apple.com/account/resources/certificates and add a new certificate by pressing on the plus-button. + 13. Select 'Apple Push Notification service SSL (Sandbox & Production)' - ![](example/assets/push_notification_setup.png?raw=true) + +![](example/assets/push_notification_setup.png?raw=true) + 14. Select the app ID that you hav defined in point 4.-10. + 15. Select a Certificate Signing Request (CSR) file. See https://help.apple.com/developer-account/#/devbfa00fef7 on how to create this certificate + 16. When having finished, download the newly created Apple Push Services certificate + 17. Add certificate to your local keychain by opening the newly downloaded file + 18. Press on "login" on the upper left corner of your keychain window and select the tab "My Certificates" - ![](example/assets/keychain.png?raw=true) + +![](example/assets/keychain.png?raw=true) + 19. Right click on the Apple-Push-Services-certificate and export it as .p12-file + 20. Convert p12-file to pem-file by following command. Please consider that "flutterApns" needs to be replaced by your respective certificate name.
- [More info](https://stackoverflow.com/questions/1762555/creating-pem-file-for-apns) - ``` - openssl pkcs12 -in flutterApns.p12 -out flutterApns.pem -nodes -clcerts - ``` + +[More info](https://stackoverflow.com/questions/1762555/creating-pem-file-for-apns) + +``` + +openssl pkcs12 -in flutterApns.p12 -out flutterApns.pem -nodes -clcerts + +``` + 21. Start example app on physical iPhone device from Xcode or your favorite IDE. + 22. Device token gets automatically printed when application was able to retrieve push token from APNS. This happens after accepting notification permission prompt. + 23. Send the following CURL from you development Mac. You can execute CURLs by copy-pasting them into Terminal and hit enter.
- [More info](https://gist.github.com/greencoder/16d1f8d7b0fed5b49cf64312ce2b72cc) - ```curl - curl -v \ - -d '{"aps":{"alert":"","badge":2}}' \ - -H "apns-topic: " \ - -H "apns-priority: 10" \ - --http2 \ - --cert .pem \ - https://api.development.push.apple.com/3/device/ - ``` + +[More info](https://gist.github.com/greencoder/16d1f8d7b0fed5b49cf64312ce2b72cc) + +```curl + +curl -v \ + +-d '{"aps":{"alert":"","badge":2}}' \ + +-H "apns-topic: " \ + +-H "apns-priority: 10" \ + +--http2 \ + +--cert .pem \ + +https://api.development.push.apple.com/3/device/ + +``` + 24. A push notification does appear if the example app is in background. -When not utilizing the example app, you need to additionally [setup push notification capability inside Xcode](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns) and add the code mentioned in [usage](#usage). + + +When not utilizing the example app, you need to additionally [setup push notification capability inside Xcode](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns) and add the code mentioned in [usage](#usage). \ No newline at end of file From cbd37d5e98d35e42c6e892b35d82b644eae9292d Mon Sep 17 00:00:00 2001 From: Alessio Zeni Date: Wed, 12 Oct 2022 11:21:59 +0200 Subject: [PATCH 5/5] fix readme --- flutter_apns/README.md | 357 +++++++++-------------------------------- 1 file changed, 74 insertions(+), 283 deletions(-) diff --git a/flutter_apns/README.md b/flutter_apns/README.md index 3694dc9..b2b8d57 100644 --- a/flutter_apns/README.md +++ b/flutter_apns/README.md @@ -1,231 +1,117 @@ - # apns - - Plugin to implement APNS push notifications on iOS and Firebase on Android. - - ## Why this plugin was made? - - Currently, the only available push notification plugin is `firebase_messaging`. This means that, even on iOS, you will need to setup firebase and communicate with Google to send push notification. This plugin solves the problem by providing native APNS implementation while leaving configured Firebase for Android. - - ## Usage - 1. Configure firebase on Android according to instructions: https://pub.dartlang.org/packages/firebase_messaging. - 2. On iOS, make sure you have correctly configured your app to support push notifications, and that you have generated certificate/token for sending pushes. For more infos see section [How to run example app on iOS](#how-to-run-example-app-on-ios) - - 3. Add the following lines to the `didFinishLaunchingWithOptions` method in the AppDelegate.m/AppDelegate.swift file of your iOS project - - Objective-C: - ```objc - if (@available(iOS 10.0, *)) { - -[UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; - + [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; } - ``` - - Swift: - ```swift - -if #available(iOS 10.0, *) { - -UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate - +if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } - ``` - - 4. Add `flutter_apns` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). - 5. Using `createPushConnector()` method, configure push service according to your needs. `PushConnector` closely resembles `FirebaseMessaging`, so Firebase samples may be useful during implementation. You should create the connector as soon as possible to get the onLaunch callback working on closed app launch. - ```dart - -import 'package:flutter_apns/apns.dart'; - - +import 'package:flutter_apns/apns.dart'; final connector = createPushConnector(); - connector.configure( - -onLaunch: _onLaunch, - -onResume: _onResume, - -onMessage: _onMessage, - + onLaunch: _onLaunch, + onResume: _onResume, + onMessage: _onMessage, ); - connector.requestNotificationPermissions() - ``` - 6. Build on device and test your solution using Firebase Console (Android) and CURL (iOS, see [How to run example app on iOS](#how-to-run-example-app-on-ios)). - - ## Additional APNS features: - ### Displaying notification while in foreground - - ```dart - final connector = createPushConnector(); - if (connector is ApnsPushConnector) { - -connector.shouldPresent = (x) => Future.value(true); - + connector.shouldPresent = (x) => Future.value(true); } - ``` - - ### Handling predefined actions - - Firstly, configure supported actions: - ```dart - final connector = createPushConnector(); - if (connector is ApnsPushConnector) { - -connector.setNotificationCategories([ - -UNNotificationCategory( - -identifier: 'MEETING_INVITATION', - -actions: [ - -UNNotificationAction( - -identifier: 'ACCEPT_ACTION', - -title: 'Accept', - -options: [], - -), - -UNNotificationAction( - -identifier: 'DECLINE_ACTION', - -title: 'Decline', - -options: [], - -), - -], - -intentIdentifiers: [], - -options: [], - -), - -]); - + connector.setNotificationCategories([ + UNNotificationCategory( + identifier: 'MEETING_INVITATION', + actions: [ + UNNotificationAction( + identifier: 'ACCEPT_ACTION', + title: 'Accept', + options: [], + ), + UNNotificationAction( + identifier: 'DECLINE_ACTION', + title: 'Decline', + options: [], + ), + ], + intentIdentifiers: [], + options: [], + ), + ]); } - ``` - - Then, handle possible actions in your push handler: - ```dart - Future onPush(String name, RemoteMessage payload) { + final action = UNNotificationAction.getIdentifier(payload.data); -final action = UNNotificationAction.getIdentifier(payload.data); - - - -if (action == 'MEETING_INVITATION') { - -// do something + if (action == 'MEETING_INVITATION') { + // do something + } + return Future.value(true); } - - - -return Future.value(true); - -} - ``` - - Note: if user clickes your notification while app is in the background, push will be delivered through onResume without actually waking up the app. Make sure your handling of given action is quick and error free, as execution time in for apps running in the background is very limited. - - Check the example project for fully working code. - - ## Enabling FirebaseCore - If you want to use firebase, but not firebase messaging, add this configuration entry in your Info.plist (to avoid MissingPluginException): - - ``` - flutter_apns.disable_firebase_core - - ``` - - ## flutter_apns_only - APNS without firebase - If only care about apns - use flutter_apns_only plugin. It does not depend on firebase. To ensure no swizzling (which is needed by original plugin to disable firebase) takes place, add this configuration entry in your Info.plist: - - ```plist - flutter_apns.disable_swizzling - - ``` - ## Critical Alert For use iOS Critical alert request entitlement to Apple throught this form: [https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/](https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/) @@ -236,193 +122,98 @@ After that assign CriticalAlert capability to your app identifier, update provis ``` - ## Troubleshooting - - 1. Ensure that you are testing on actual device. NOTE: this may not be needed from 11.4: https://ohmyswift.com/blog/2020/02/13/simulating-remote-push-notifications-in-a-simulator/ - 2. If onToken method is not being called, add error logging to your AppDelegate, see code below. - 3. Open Console app for macOS, connect your device, and run your app. Search for "PUSH registration failed" string in logs. The error message will tell you what was wrong. - - *swift* - ```swift - -import UIKit - -import Flutter - - +import UIKit +import Flutter @UIApplicationMain - -@objc class AppDelegate: FlutterAppDelegate { - -override func application( - -_ application: UIApplication, - -didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - -) -> Bool { - -GeneratedPluginRegistrant.register(with: self) - -return super.application(application, didFinishLaunchingWithOptions: launchOptions) - -} - - - -func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - -NSLog("PUSH registration failed: \(error)") - +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NSLog("PUSH registration failed: \(error)") + } } -} - - - ``` - - *objc* - ```objc +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" -#include "AppDelegate.h" - -#include "GeneratedPluginRegistrant.h" - - - -@implementation AppDelegate - - +@implementation AppDelegate - (BOOL)application:(UIApplication *)application - -didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - -[GeneratedPluginRegistrant registerWithRegistry:self]; - -// Override point for customization after application launch. - -return [super application:application didFinishLaunchingWithOptions:launchOptions]; - + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; } - - -(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - -NSLog(@"%@", error); - + NSLog(@"%@", error); } - - @end - ``` - ## How to run example app on iOS - Setting up push notifications on iOS can be tricky since there is no way to permit Apple Push Notification Service (APNS) which requires a complicated certificate setup. The following guide describes a step by step approach to send push notifications from your Mac to an iPhone utilizing the example app of this package. This guide only describes debug environment setup. - - 1. Open example ios folder with Xcode - 2. Select Runner -> Signing & Capabilities - 3. Select your development team and add a globally unique bundle identifier. The one on the picture is already occupied: - -![](example/assets/Xcode_setup.png?raw=true) - + ![](example/assets/Xcode_setup.png?raw=true) 4. Go to https://developer.apple.com/account/resources/identifiers/list/bundleId and press on the plus button - 5. Select "App IDs" and press continue - -![](example/assets/register_app.png?raw=true) - + ![](example/assets/register_app.png?raw=true) 6. Select type "App" - 7. Select "App ID Prefix" which should be same as "Team ID" - 8. Enter description and bundle ID. The latter one needs to be the same as the bundle ID specified in 3. - 9. Select push notification capability - -![](example/assets/select_capability.png?raw=true) - + ![](example/assets/select_capability.png?raw=true) 11. Press on "Continue" and then on "Register" - 12. Go to https://developer.apple.com/account/resources/certificates and add a new certificate by pressing on the plus-button. - 13. Select 'Apple Push Notification service SSL (Sandbox & Production)' - -![](example/assets/push_notification_setup.png?raw=true) - + ![](example/assets/push_notification_setup.png?raw=true) 14. Select the app ID that you hav defined in point 4.-10. - 15. Select a Certificate Signing Request (CSR) file. See https://help.apple.com/developer-account/#/devbfa00fef7 on how to create this certificate - 16. When having finished, download the newly created Apple Push Services certificate - 17. Add certificate to your local keychain by opening the newly downloaded file - 18. Press on "login" on the upper left corner of your keychain window and select the tab "My Certificates" - -![](example/assets/keychain.png?raw=true) - + ![](example/assets/keychain.png?raw=true) 19. Right click on the Apple-Push-Services-certificate and export it as .p12-file - 20. Convert p12-file to pem-file by following command. Please consider that "flutterApns" needs to be replaced by your respective certificate name.
- -[More info](https://stackoverflow.com/questions/1762555/creating-pem-file-for-apns) - -``` - -openssl pkcs12 -in flutterApns.p12 -out flutterApns.pem -nodes -clcerts - -``` - + [More info](https://stackoverflow.com/questions/1762555/creating-pem-file-for-apns) + ``` + openssl pkcs12 -in flutterApns.p12 -out flutterApns.pem -nodes -clcerts + ``` 21. Start example app on physical iPhone device from Xcode or your favorite IDE. - 22. Device token gets automatically printed when application was able to retrieve push token from APNS. This happens after accepting notification permission prompt. - 23. Send the following CURL from you development Mac. You can execute CURLs by copy-pasting them into Terminal and hit enter.
- -[More info](https://gist.github.com/greencoder/16d1f8d7b0fed5b49cf64312ce2b72cc) - -```curl - -curl -v \ - --d '{"aps":{"alert":"","badge":2}}' \ - --H "apns-topic: " \ - --H "apns-priority: 10" \ - ---http2 \ - ---cert .pem \ - -https://api.development.push.apple.com/3/device/ - -``` - + [More info](https://gist.github.com/greencoder/16d1f8d7b0fed5b49cf64312ce2b72cc) + ```curl + curl -v \ + -d '{"aps":{"alert":"","badge":2}}' \ + -H "apns-topic: " \ + -H "apns-priority: 10" \ + --http2 \ + --cert .pem \ + https://api.development.push.apple.com/3/device/ + ``` 24. A push notification does appear if the example app is in background. - - -When not utilizing the example app, you need to additionally [setup push notification capability inside Xcode](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns) and add the code mentioned in [usage](#usage). \ No newline at end of file +When not utilizing the example app, you need to additionally [setup push notification capability inside Xcode](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns) and add the code mentioned in [usage](#usage).