Friday, 27 January 2017

Firebase Cloud Messaging with Delphi 10.1 Berlin update 2.

A comprehensive step by step guide, covering everything you need to know to receive push notifications to your Android device using Firebase Cloud Messaging and the latest Delphi 10.1 Berlin update 2.

Push notifications let your application notify a user of new messages or events even when the user is not actively using the application (downstream messaging) (Parse.com). On Android devices, when a device receives a push notification, the application's icon and a message appear in the status bar. When the user taps the notification, they are sent to the application. Notifications can be broadcast to all users, such as for a marketing campaign, or sent to just a subset of users, to give personalised information. To provide this functionality I will rely on Firebase Cloud Messaging which is the new version of GCM (Google cloud messaging) and Delphi to develop the Android application.

1. Create your Firebase project


Create your Firebase project by visiting the console if you still don't have one and in this project create an Android App.


I already have one project so I will use this one for my demo. Once in the project, go to Overview -> Add another app -> Android:


And give it a sensible name. In my case I called the package com.embarcadero.FirebaseCloudMessaging. This package name is important as it will be referenced later on. Once you click Add App, you will receive a google-services.json file which contain information that we will use later.

The package name is defined in your Delphi project:


So make sure that everything matches with the name you give to your Firebase application as the manifest file will contain this information.

2 Request your FCM Token


Now that we have our project configured, we need to request Firebase a unique token for our Android device. You can see the description here as to how to get the FCM token via Android Studio, but I will show the necessary steps to get the same value from our Delphi application.

Basically we are trying to get the same value from FirebaseInstanceId.getInstance().getToken(). We will achieve the same behaviour by using TPushServiceManager which is the unit responsible for handling push notifications.

The following code snippet tries to request the FCM token via TPushServiceManager:

Now, to allow this code to work correctly, we will have to configure few things.

a) Enter the Sender Id.

In the source code snippet above, I'm mentioning the SENDER ID. This sender id, can be found under Firebase -> Project Settings -> Cloud Messaging:


This is the value you have to put here:

PushService.AppProps[TPushService.TAppPropNames.GCMAppID] := 'SENDER ID';

Knowing that the GCMAppId is actually the Sender Id has been a quite a struggle for some users and you can see my answer on Stack overflow.

b) Configure the project to receive push notifications.

In the Delphi IDE, go to your project options -> Entitlement List and set the property Receive push notifications to true.


c) Configure the AndroidManifest.template.xml file.

Before we try to run the code above, we'll have to configure the manifest file to grant our device permissions to connect to Firebase. If you don't configure the permissions, you might run into an exception like the one below:


Error message: EJNIException with message 'java.lang.SecurityException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.REGISTER pkg=com.google.android.gms (has extras) } without permission com.google.android.c2dm.permission.RECEIVE'.

See the code snipped below for reference:

The full source code of the solution can be found here for reference where you can find the manifest files.

Once everything is configured, we can now test if we can receive the FCM token. Here is a screenshot of my project so you can see that there are two buttons, one to receive the token and the other one to store this token somewhere so the system that sends the notification knows the receiver.


Let's see the project in action here:


As you can see in the image above, I get the DeviceID and the FCM Token. The one we are interested in is the FCM Token. This token is quite large so it does not appear completely on the screen.

Now we need to configure what to do when we receive a notification and how this notification is built.

3 Receive your first FCM Push notification


The following code snipped will configure the OnReceiveNotification event and will display a notification using the TNotificationCenter class.

Notice that the ServiceNotification variable contains a DataKey member which contains a JSON envelope. This object will contain all the information of our push notification. Here you can see how this notification looks like:


Notice that the message is part of the gcm.notification.body property and this one is the one that we will use for our DataObject.GetValue method.

Let's see the application in action:



Here you can see side by side my Android device and Firebase Notification testing system. Once the application is ready to receive notifications, you just need to send the notification using the configured app or the token if you want to target a single device.

Next step is to store these tokens on the cloud and use your own system to deliver those messages.

Please, do not hesitate to contact me if you have any further questions.

Related links:


Jordi
Embarcadero MVP.

36 comments:

  1. Hello,
    Great article! .. I am using a third party library to manage the push notification.
    This is a good way to implement own system push ...
    Have you a example in order to use a push Apple server ?

    thanks
    regards
    Antonello

    ReplyDelete
    Replies
    1. Hi Antonello,

      Unfortunately I don't have any example using iOS. Probably it's similar but you'll have to check what are the parameters required in TPushServiceManager.

      Regards,
      Jordi

      Delete
    2. Any chance of ever making an iOS guide?

      This Android write up worked very well for me it seems. I have to write the Android Service, but other than that it was great.

      For iOS I am just hitting road blocks that I can't find answers to.

      I can't even get the Device Token ....

      It would be great if you have time to do an iOS flavor of this write up for us that are struggling.

      Hold-your-hand instructions and with pictures, on these kind of topics is great.

      Thanks for good write up!

      Delete
    3. Hi Steven,

      I don't think I will do one for iOS as it requires additional effort and time that I don't have. I know it's different for iOS and you have to change the code in the GetServiceByName to use this instead:

      PushService := TPushServiceManager.Instance.GetServiceByName(TPushService.TServiceNames.APS);

      This will make it work for iOS. Also bear in mind that the process is a bit different and you need to send a different request first to get the token. Also the device needs to be registered.

      I hope this helped.

      Cheers,
      Jordi

      Delete
  2. Congratulations

    ReplyDelete
  3. Hola Jordi. Te felicito por el artículo. Me ha servido mucho de ayuda. Quisiera saber como se pueden recibir notificaciones, cuando la aplicación está cerrada, es decir, no cargada en la memoria. He probado tanto cuando la aplicación está en el background como en el foreground y funciona. El problema es cuando fuerzas el cierre de la aplicación. En ese momento, ya no se reciben notificaciones. Gracias por adelantado.

    ReplyDelete
  4. Thanx very much

    ReplyDelete
  5. What version of Android do you have on your device? The device id comes up blank on my Android 7.1.1. I note that the code in FMX.jar uses:

    Secure.getString(getContentResolver(), "android_id")

    Which it then "dehashes" to a string. It may be that this value is blank on newer versions of Android (or at least on my Nexus 5X)

    ReplyDelete
    Replies
    1. Hi David,

      I'm using Android 6 Marshmallow and I had similar issues when moving away from previous versions of Android as there is always something different that needs modifying. You'll have to fiddle a bit as to see what's different.

      Cheers,
      Jordi

      Delete
  6. В окне уведомления нет заголовка и текста (только иконка приложения). Отправляю например через Firebase Console

    dmokrushin@fortebank.com

    ReplyDelete
    Replies
    1. HI Dmitriy,

      You'll have to check the notification component as to see what can be done with it. Bear in mind that the notification component calls the underlying Android component so I don't know what you will be allowed to do with it.

      Cheers,
      Jordi

      Delete
  7. Good article, works fne, just when I close the app (yours or mine), there is a segmentation fault : https://puu.sh/tT6Le/9cf4d2f07e.png

    ReplyDelete
    Replies
    1. Hi Helge,

      I didn't notice it. I'll have to test it again as to see where the error happens.

      Regards,
      Jordi

      Delete
  8. Also you wrote about that file google-services.json which "we will use later"... But i can't find it anywhere mentioned again ... Does that mean it isn't used or in a later article that's yet to be published ?

    Greetings

    ReplyDelete
    Replies
    1. Hi Helge,

      Yes I thought about that. The file will be used later on when I start saving tokens.

      Cheers,
      Jordi

      Delete
  9. Hi! Nice and useful article. Actually I have tried to send message FCM using topic. On the FCM user manual, it recommend to use that like "FirebaseMessaging.getInstance().subscribeToTopic("news")". Do you know how to build this issue? It it very difficult to build without not much information with DELPHI than JAVA.

    ReplyDelete
    Replies
    1. Hi,

      For that I guess that you'll have to do some manual filtering through the API. You'll have to look for it on that end.

      Cheers,
      JOrdi

      Delete
  10. Hi! Grate article. I have a question. Is it possible to use topic for messaging to all users?

    ReplyDelete
    Replies
    1. Hi there,

      You have to build your own topic platform. When a user is granted then you store his token somewhere and add a specific topic. Then when you push the notification you only push specific notifications according to the topic.

      Cheers,
      Jordi

      Delete
  11. Dear Jordi,

    When trying to make a request "post" via Delphi Seattle up2 or the RESTDebugger to Firebase with HTTP, returns the error:
    "HTTP / 1.1 401 The request was missing an Authentification Key (FCM Token). Please, refer to section" Authentification "of the FCM documentation, at https // = firebase.google.com/docs"

    Http://www.netnucleo.com.br/imagens/delphi_http/rest_debugger_01.jpg
    Http://www.netnucleo.com.br/imagens/delphi_http/rest_debugger_02.jpg

    Using the Chrome Postman extension, it works.
    http://www.netnucleo.com.br/imagens/delphi_http/postman_01.jpg

    http://www.netnucleo.com.br/imagens/delphi_http/delphi_01.jpg


    See the Delphi code:
    Try
    IdIOHandler: = TIdSSLIOHandlerSocketOpenSSL.Create (nil);
    IdIOHandler.ReadTimeout: = IdTimeoutInfinite;
    IdIOHandler.ConnectTimeout: = IdTimeoutInfinite;
    IdHTTP: = TIdHTTP.Create (nil);
    Try
    IdHttp.Request.Clear;
    IdHttp.Request.CustomHeaders.Clear;
    IdHttp.Request.ContentType: = 'application / json';
    Idhttp.Request.Charset: = 'UTF-8';
    IdHTTP.IOHandler: = IdIOHandler;
    IdHTTP.ReadTimeout: = IdTimeoutInfinite;
    IdHTTP.Request.Connection: = 'Keep-Alive';
    IdIOHandler.SSLOptions.Method: = sslvSSLv23;
    IdHTTP.Request.Method: = 'POST';
    IdHTTP.Request.CustomHeaders.Values ​​['Authorization: key']: = 'AAAAYsnMbsY: APA91bEjYZK-xxxxxxx ......';
    idHttp.Request.CustomHeaders.Values ​​[ 'Content-Type'] = 'application / json';
    JsonString: = '{"to": "APA91bFJSdGW_yrX7p_TNKZ4k0OpdXTQM6xdd7BUsslk6zSvZlBmoAnfvyX-nBm4DYY-xxxxx ......",' + '
    '' Data ": {'+
    '"Nick": "Push Test",' + '
    '"Body": "Push body",' +
    'Room': 'Just one test' '+
    '},}';
    JsonToSend: = TStringStream.Create (jsonString);
    Try
    Response: IdHTTP.Post = ( 'https://fcm.googleapis.com/fcm/send' JsonToSend);
    Response: = response.Replace (Char (# 10), '');
    Except
    On E: EIdHTTPProtocolException
    Memo1.Lines.Insert (0, e.ErrorMessage);
    End;
    Memo1.Lines.Insert (0, response);
    Finally
    IdHTTP.Free;
    End;
    Finally
    IdIOHandler.Free;
    End;

    ReplyDelete
    Replies
    1. Hi there,

      I think your request is malformed. Are you using your server key as authentication key? And your json string should contain the "data" node. Check out the documentation here:
      https://firebase.google.com/docs/cloud-messaging/server

      Cheers,
      Jordi

      Delete
    2. Hi Jordi,

      I am using the key as image:
      http://www.netnucleo.com.br/imagens/delphi_http/key_server_01.jpg

      My JSON String Contains o node "data":
      jsonString := '{"registration_ids" :["APA91bFJSdGW_yrX7p_TNKZ4k0OpdXTQM6xdd7BUsslk6zSvZlBmoAnfvyX-nBm4DYY-.....xxxxx"]}' +
      '{"data" : { ' +
      '"Nick" : "Teste de Push", ' +
      '"body" : "Corpo do push", ' +
      '"Room" : "Apenas um teste" ' +
      '},}';

      Delete
  12. Sorted out. See this link: http://stackoverflow.com/questions/42392627/delphi-google-firebase-http

    Even so, thank you very much for your support.

    ReplyDelete
  13. Great Jordi !!

    But When my app is closed, the notification received does not show the message, only an alert with the icon and package name appears on my device notification (if the app is closed). Can you help me ?

    Jordi Grifoll.

    ReplyDelete
    Replies
    1. Hi Jordi,

      You might have to build a background service for that so it's alive and can accept requests. Or keep the application alive on background also but the first approach is better.

      http://docwiki.embarcadero.com/RADStudio/Seattle/en/Creating_Android_Services

      Cheers,
      Jordi

      Delete
  14. Great Jordi !!

    But When my app is closed, the notification received does not show the message, only an alert with the icon and package name appears on my device notification (if the app is closed). Can you help me ?

    Jordi Grifoll.

    ReplyDelete
  15. Hi Jordi, Thank you for the article and sorry for my English.
    Well, i'm trying to POST to this url "https://iid.googleapis.com/iid/info/APA91bFICaJ9rbQccb....B1G0SMK7GA8bjeG1vP391e5hp_XNRqRHWWO8SSL93h-E1Fv_CJeaQki3RZ....cm0A25EuVvax9J3k5nQCQl6ltkw" with the FCM Token, but the response i get is "{"error":"InvalidTokenVersion"}".

    Firebase's documentation references another FCM token format.
    Do you know why this is happening?

    ReplyDelete
  16. Hi Jordi, Thank you for the article and sorry for my English.
    Well, i'm trying to POST to this url "https://iid.googleapis.com/iid/info/APA91bFICaJ9rbQccb....B1G0SMK7GA8bjeG1vP391e5hp_XNRqRHWWO8SSL93h-E1Fv_CJeaQki3RZ....cm0A25EuVvax9J3k5nQCQl6ltkw" with the FCM Token, but the response i get is "{"error":"InvalidTokenVersion"}".

    Firebase's documentation references another FCM token format.
    Do you know why this is happening?

    ReplyDelete
  17. Hi Jordi, Thank you for the article and sorry for my English.
    Well, i'm trying to POST to this url "https://iid.googleapis.com/iid/info/APA91bFICaJ9rbQccb....B1G0SMK7GA8bjeG1vP391e5hp_XNRqRHWWO8SSL93h-E1Fv_CJeaQki3RZ....cm0A25EuVvax9J3k5nQCQl6ltkw" with the FCM Token, but the response i get is "{"error":"InvalidTokenVersion"}".

    Firebase's documentation references another FCM token format.
    Do you know why this is happening?

    ReplyDelete
    Replies
    1. Hi Alberto,
      Are you using the parameters mentioned in the help about authentication and details? https://developers.google.com/instance-id/reference/server

      Just make sure that your post includes those headers.

      Cheers,
      Jordi

      Delete

  18. If the application is closed, the notification arrives normally, but the message appears without text, with only the name of the application.
    Would you help me?

    ReplyDelete
    Replies
    1. Hi Lunar Tecnologi,

      you'll have to research a bit about the notification component. I experienced this issue too but I didn't delve into it any further. You might want to consider using a service instead that can be up all the time and receiving those requests.

      Cheers,
      Jordi

      Delete
  19. Hi Jordi,
    Thanks for providing such useful code and article.

    I am using 10.2 Tokyo and have a problem registering - either your code untouched or my own app.
    When setting ServiceConnection.Active := True;
    I get the following exception.
    "Exception class EJNIException with message 'java.io.IOException: MAIN_THREAD'."
    I can trace it down to the line
    "LToken := LGCM.Register(LSenderIds);" in TGCMPushService.Register
    I believe that is a call into the Java com/google/android/gms/gcm/GoogleCloudMessaging.register (in Android.JNI.PlayServices).
    Any thoughts on where to go from here?
    Denver Jeans

    ReplyDelete
    Replies
    1. Hi Denver,

      I hope your back-end is configured correctly? And that you are using an android device?

      Cheers,
      Jordi

      Delete