Communications · Foundation · Uncategorized

Receiving push notifications from Apple’s Push Notification and Google’s Firebase Cloud Messaging services

In the previous article we demonstrated how to roll your own Google Firebase Cloud Messaging and Apple APNS push notifications from Delphi apps in a unified manner. This article continues the conversation about how to easily receive and consume them in your Delphi mobile app.

Introduction

If you are authoring a Delphi service for the cloud, you may have the need to send and receive push notifications from your service to your mobile client application. You could use an existing BAAS provider to accomplish this, but why pay for this when you can roll your own so easily with Delphi?

Delphi provides a base set of classes in the units FMX.PushNotification.Android and FMX.PushNotification.iOS that do most of the heavy lifting and allow you to easily consume remote push notifications. In this conversation we will be demonstrating a helper class that unifies the approach for both platforms, uses TMessage callbacks for notifications and device token changes and adds some new missing features.

For more information about us, our support and services visit the Grijjy homepage or the Grijjy developers blog.

The example contained here depends upon part of our Grijjy Foundation library.

Getting started on iOS

To receive push notifications on iOS you must:

  1. Already have created an Apple Push Services certificate in the Apple Developer website for use in sending remote push notifications.

  2. Create an App ID on the Apple Developer website for your project.

  3. Enable Push Notifications for your App ID on the Apple Developer website for your project.

  4. A Provisioning Profile must exist on the Apple website that matches the identifier.

Then in your Delphi Project Options you must set your CFBundleIdentifier for iOS build configurations to the matching name of the ID in Apple Developer website. (ex: com.some.app) This is the only thing your app will need to identify itself for Apple Push Notifications.

Getting started on Android

To receive push notifications on Android you must:

  1. Obtain a Firebase Cloud Messaging API Key by setting up cloud messaging on Google’s developer site. This is also called the Server API Key under Firebase Cloud Messaging.

  2. Modify your AndroidManifest.xml to register an Intent and add the following receiver:

  <receiver
  android:name="com.embarcadero.gcm.notifications.GCMNotification" android:exported="true"
  android:permission="com.google.android.c2dm.permission.SEND" >
  <intent-filter>
    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    <category android:name="%package%" />
  </intent-filter>
  </receiver>
  <service android:name="com.embarcadero.gcm.notifications.GCMIntentService" />
  <%receivers%>

For Android, we only need the Firebase Cloud Messaging API Key for our Delphi app.

TgoRemotePushReceiver class

The TgoRemotePushReceiver class unifies the receiving of remote push notifications from both APNS and Firebase Cloud Messaging into a common class model.

  TgoRemotePushReceiver = class(TObject)
  public
    constructor Create(const AGCMAppId: UnicodeString);
    destructor Destroy; override;
  public
    property DeviceToken: String read FDeviceToken;
    property Number: Integer read GetNumber write SetNumber;
  end;

When you receive a remote push notification message from Android or iOS, the following TMessage is fired:

  TgoRemoteNotificationMessage = class(TMessage)
  public
    constructor Create(const ADataKey, AJson: String; const AActivated: Boolean);
    property DataKey: String read FDataKey;
    property Json: String read FJson;

    { Whether message is activated by the user (tapping in it) }
    property Activated: Boolean read FActivated;
  end;

Since we are receiving a JSON payload for both Android and iOS we can examine the content here. This is useful is we want to include our own custom content in the payload that is app specific. See our article on sending remote push notifications for a discussion about customizing the payload.

If your device token changes on Android or iOS, the following TMessage is fired:

  { Device token change message }
  TgoDeviceTokenChangeMessage = class(TMessage)
  private
    FDeviceToken: String;
  public
    constructor Create(const ADeviceToken: String);
    property DeviceToken: String read FDeviceToken;
  end;

The above message is fired initially when you device token is assigned after app startup. Due to the variances in the way device tokens are handled on iOS and Android this event could be immediate or delayed by the operating system.

As a developer you should also be prepared that the device token could change if your application is reinstalled, the operating system is updated as well as dynamically while your app is active.

Apple APNS quirks

On iOS Remote Push notification events are received only when your application is in the foreground or background. If your application is “force quit” also known as “terminated” then you cannot receive remote push notification events into your app. Instead when the push notification arrives on screen and is pressed your app is launched but without receiving a notification event.

Note: You only receive a single remote notification event at a time, meaning that if there are several events queued, the only one you receive is the one that the user clicked on in the iOS user interface.

iOS 8.x introduces a new approach called the PushKit which has the ability to silently launch your app into the background state. We will explore this in a future article.

Hello World for push notifications

To get started using the helper class, you will need your Android API Key for Firebase Cloud Messaging.

RemotePushReceiver := TgrRemotePushReceiver.Create(MyAndroidApiKey);

To subscribe to remote push notifications:

TMessageManager.DefaultManager.SubscribeToMessage(TgoRemoteNotificationMessage, RemoteNotificationHandler);
procedure TMyClass.RemoteNotificationHandler(const Sender: TObject;
  const Msg: TMessage);
var
  RemoteNotificationMessage: TgrRemoteNotificationMessage;
  Doc: TgoBsonDocument;
begin
  Assert(Assigned(Msg));
  Assert(Msg is TgoRemoteNotificationMessage);
  RemoteNotificationMessage := Msg as TgoRemoteNotificationMessage;
  { The user tapped this push notification, which resulted in the bringing the app to the
    foreground? }
  if (RemoteNotificationMessage.Activated) then
    Doc := TgrBsonDocument.Parse(RemoteNotificationMessage.Json);
end;

You will receive an Activated set to True if the notification is the likely result of a end-user actually pressing the notification.

To subscribe to device token changes:

TMessageManager.DefaultManager.SubscribeToMessage(TgoDeviceTokenChangeMessage, DeviceTokenChangeListener);
procedure TMyClass.DeviceTokenChangeListener(const Sender: TObject;
  const M: TMessage);
var
  DeviceTokenChangeMessage: TgoDeviceTokenChangeMessage;
begin
  DeviceTokenChangeMessage := M as TgoDeviceTokenChangeMessage;
  Writeln('Device Token is ' + DeviceTokenChangeMessage.DeviceToken);
end;

Make sure you unsubscribe all the various TMessage listeners when you are completed.

Conclusion

Receiving remote push notifications in Delphi apps for Android and iOS is relatively straightforward with this helper class. In the end, all you need to know is the device token in order to send a message to a device.

License

TgoRemotePushReceiver is licensed under the Simplified BSD License.

14 thoughts on “Receiving push notifications from Apple’s Push Notification and Google’s Firebase Cloud Messaging services

  1. For IOS: I have created all certificates and followed your instructions on building the Server. Now I try to follow the instructions above to build the client (there are some minor errors TgrXxx instead of TgoXxx) but I cannnot get the Device Token, in order to test the Server part.

    I have enabled UI_Backgroundmodes: remote_notifications and I get a question to allow them when the application starts.

    In FormCreate I create the
    FRemotePushReceiver := TgoRemotePushReceiver.Create(MyAndroidApiKey);
    and setup the listeners
    TMessageManager.DefaultManager.SubscribeToMessage(TgoDeviceTokenChangeMessage, DeviceTokenChangeListener);
    TMessageManager.DefaultManager.SubscribeToMessage(TgoRemoteNotificationMessage, RemoteNotificationHandler);

    But I cannot get the DeviceToken???

    Like

    1. Problem solved by regenerating the Provisioning profile after adding Push Notification to the AppId.

      Now I have the Token, but the Http2 request receives StatusCode = 0 and nothing is of course delivered on my phone.

      The APNS-cert.pem and APNS-key.pem tests fine on Mac, so they should be ok.
      The APNS Topic is correct and the Device token is also correct (double checked).

      I am confused now???

      Like

      1. Sorry again, maybe you can update your texts to make other find similar problems quicker than me (A pitfall section :)).
        I used the development certificates, so I should use the development server in the Grijjy.RemotePush.Sender.pas

        “`Delphi
        AResponse := FHttp2.Post(‘https://api.development.push.apple.com/3/device/’ + ADeviceToken);
        “`

        Lets hope it is easier to have Android on the go as well.

        Like

      2. When you create a certificate, you create a single one that works on both the sandbox (development) and production as mentioned in the article “On iOS you must create a production certificate that is universal by selecting the option “Apple Push Notification service SSL (Sandbox & Production)” under Certificates in the developer portal.”

        Are you still receiving a status code 0 when you post? If you set a breakpoint in TgoHTTP2Client.nghttp2_on_header_callback() do you receive any headers?

        Like

  2. I set a breakpoint in TgoHTTP2Client.nghttp2_on_header_callback() and using the production URL it will NOT stop there (not coming here) and Statuscode is still 0 (it take like 8 seconds before failing).

    Using the development URL everything works fine.

    Like

    1. Okay, so you have everything working in the sandbox, just not production. I suspect that the certificate is not valid for production. If you try the test with your current certificate against the production server, does it work?

      openssl s_client -connect gateway.push.apple.com:2195 -cert APNS-cert.pem -key APNS-key.pem

      Like

  3. The openssl command on the Mac work okey.

    what is the differance beween the URLs api.push… and gateway.push… ?

    Tomorrow I will test using the production certificates (I genererted one for development and one for production).

    Like

    1. Not sure if the newer http/2 protocol works on the other address. The one we are recommending works for production for http/2. I would make sure you retrace all your steps for creating and provisioning your app id and certificate.

      Like

      1. Initially I used the Delphi 32-bit Ios client. I have now switched to 64-bit and it still works to api.development.push, but still get statuscode 0 from api.push.

        When shifting Configuration from Development to Ad Hoc, I get Status Code 400 “Bad request” from api.development.push and still 0 from api.push (and no headers).

        https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1

        Like

    2. Also, did you ever create a universal certificate as the article discusses. You kept discussing using 2 certificates instead, which this article never discussed. We are also commenting in the wrong article since this is a discussion on sending, not receiving. I would be testing 64-bit, adhoc against the production push server.

      Like

Leave a comment