Overview
KlugPush brings remote push notifications to AIR apps on iOS, Android and macOS with one extension and one ActionScript API. It asks for permission, registers the device, gives you the token for your server and delivers incoming notifications to ActionScript.
On iPhone, iPad and Mac the notifications come directly from Apple's push service. There is no Firebase SDK in your app. On Android KlugPush uses Firebase Cloud Messaging; Google's messaging library is added automatically when the app is packaged, without any extra extensions.
When the user taps a notification, your app learns which one it was, also when the tap started the app. So you can open the right screen directly, for example a live view, a chat or an order.
KlugPush is in daily use in our apps on iOS, Android and macOS, our proof of concept.
Features
- Self-contained: one ANE, no core library or dependency ANEs to add
- iOS and macOS: directly through Apple's push service, no Firebase SDK in the app
- Android: Firebase Cloud Messaging, Google's library is added at packaging
- Tap to open: the payload of the tapped notification, also after a cold start
- Notifications while the app is in the foreground: shown as a banner and delivered to ActionScript
- Android notification channel with your own sound and importance
- Permission prompt, live permission status and a shortcut to the notification settings
- Android 13 and later: the notification permission is asked at runtime
- Callbacks or StatusEvents, whichever fits your code
Supported platforms
On other platforms isSupported returns false, so the same code can stay in a cross-platform app.
Getting started
Add the extension to your application descriptor:
<extensions>
<extensionID>com.klug.push.Push</extensionID>
</extensions>
iOS and macOS: sign the app with the push notifications entitlement (aps-environment) and a provisioning profile that includes the Push Notifications capability.
Android: put the google-services.json of your Firebase project into the root of the app package and add these entries to the manifest additions:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application>
<service android:name="com.klug.push.KlugPushMessagingService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<activity android:name="com.klug.push.KlugPushPermissionActivity" android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
<provider android:name="com.klug.push.KlugPushFileProvider"
android:authorities="air.YOUR.APP.ID.pushnotificationsfileprovider"
android:exported="false" android:grantUriPermissions="true"/>
</application>
The provider lets the notification channel play a sound file from your app package.
Code examples
Four complete flows, taken from the API that runs in our apps on iOS, Android and macOS. Every call returns at once; results arrive through your callbacks and as StatusEvents. Full documentation →
import com.klug.push.Push; var push:Push = Push.getInstance(); if ( push.isSupported ) // iOS, Android, macOS { // Android: notification channel with your sound file from the app root (created once). // iOS: only the last argument counts (show banners while the app is open). push.setup( "alerts", "Alerts", "alert.wav", Push.IMPORTANCE_HIGH, true ); push.requestAuthorisation(); // system prompt, only while not decided yet push.register( onToken, onPushError, onNotification ); } function onToken( token:String ):void { // iOS / macOS: the APNs device token (64 hex). Android: the FCM registration token. if ( token != loadSavedToken() ) // your app functions sendTokenToServer( token ); // e.g. a POST to https://your.server/push } function onPushError( message:String ):void { // typical causes: missing push entitlement (Apple) or google-services.json (Android) trace( "push registration failed: " + message ); }
import flash.events.Event; import flash.events.StatusEvent; import flash.desktop.NativeApplication; // cold start: the tap launched the app openFromPayload( push.consumeSelectedNotification() ); // warm start: the app was in the background NativeApplication.nativeApplication.addEventListener( Event.ACTIVATE, function( e:Event ):void { openFromPayload( push.consumeSelectedNotification() ); }); // iOS: the app was open and the user tapped the banner. // Consume here too, so the next ACTIVATE does not open the same screen again. push.addEventListener( StatusEvent.STATUS, function( e:StatusEvent ):void { if ( e.code == Push.NOTIFICATION_SELECTED ) openFromPayload( push.consumeSelectedNotification() ); }); function openFromPayload( json:String ):void { if ( json == "" ) return; // not opened from a notification var payload:Object = JSON.parse( json ); // your own keys are included if ( payload.hasOwnProperty( "stationId" ) ) openLiveView( payload.stationId ); // your app function }
consumeSelectedNotification() returns each tap only once, so calling it at startup and on every activation never opens the same screen twice.
function onNotification( json:String ):void { // the payload as your server sent it, plus your own keys var payload:Object = JSON.parse( json ); showInAppHint( payload ); // your app function; the system banner shows as well } // later, e.g. after the user signed in to another account function onLogin():void { if ( push.token != null ) // the last token; null until registration finished sendTokenToServer( push.token ); }
import flash.events.Event; import flash.events.StatusEvent; import flash.desktop.NativeApplication; function updateNotificationSwitch():void { var status:String = push.authorisationStatus(); if ( status == Push.AUTHORISED ) showSwitch( "on" ); // your app function else if ( status == Push.DENIED ) showSwitch( "off" ); else if ( status == Push.NOT_DETERMINED ) showSwitch( "not asked" ); } // after a denial the system does not ask again: the way back is the settings page function onSwitchTapped():void { if ( push.authorisationStatus() == Push.DENIED ) push.openSettings(); else push.requestAuthorisation(); } // the answer to the prompt arrives as an event push.addEventListener( StatusEvent.STATUS, function( e:StatusEvent ):void { if ( e.code == Push.AUTHORISATION ) updateNotificationSwitch(); }); // the user may have changed it in the settings: read the live status when the app comes back NativeApplication.nativeApplication.addEventListener( Event.ACTIVATE, function( e:Event ):void { push.refreshStatus( function( status:int ):void { updateNotificationSwitch(); } ); });
API overview
| Push.getInstance() | Returns the single instance. |
| isSupported | True on iOS, Android and macOS, false elsewhere. |
| setup(channelId, channelName, sound, importance, showWhenActive) | Android: creates the notification channel with a sound file from the app root. iOS: showWhenActive shows banners while the app is open. macOS: no effect. |
| requestAuthorisation() | Shows the system prompt while the user has not decided yet. The answer arrives as AUTHORISATION. |
| authorisationStatus() | AUTHORISED, DENIED, NOT_DETERMINED or UNKNOWN. |
| register(onToken, onError, onNotification) | Registers for push. All three callbacks are optional; the token also arrives as TOKEN. Returns false where push is not supported. |
| token | The last token, or null until registration finishes. |
| consumeSelectedNotification() | The JSON payload of the notification the user tapped to open the app, returned once; an empty string otherwise. |
| refreshStatus(onStatus) | Reads the live permission status; onStatus(status:int) receives the raw value. |
| authStatus | The last raw status value: 0 not determined, 1 denied, 2 authorised; -1 until known. |
| openSettings() | Opens the notification settings of your app. |
| unregister() | iOS: stops receiving notifications until register() runs again. |
Events
All events are StatusEvent.STATUS: the code names the kind, the level carries the value.
| Push.TOKEN | level = the device token |
| Push.ERROR | level = the error message from registration |
| Push.NOTIFICATION | level = the payload as JSON, received while the app is running |
| Push.NOTIFICATION_SELECTED | level = the payload as JSON, the user tapped the notification |
| Push.AUTHORISATION | level = AUTHORISED, DENIED or NOT_DETERMINED |
| Push.UNREGISTERED | the device no longer receives notifications |
Buy KlugPush
A yearly licence for one AIR application, with all updates for 12 months. After payment you receive the extension, the ActionScript library and the documentation by e-mail, usually within one business day. Licence and terms
- Licence for one AIR application
- All updates for 12 months
- Answer by e-mail within 7 days
- Licence for one AIR application
- All updates for 12 months
- Answer by e-mail within 48 hours
Requirements
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 13 or later |
| Android | Android 6.0 (API 23) or later |
| macOS | macOS 11 or later |
| Signing | iOS and macOS: push notifications entitlement and a matching provisioning profile |
| Server | iOS and macOS: Apple's push service (token-based .p8 key). Android: Firebase Cloud Messaging HTTP v1 |