Delphi 11 – Firebase Cloud Messaging ( FCM ) – iOS app Push Notification
Obiettivi:
- integrare le push notification Firebase (FCM) in una app realizzata con Delphi per iOS senza l’ausilio di librerie di terze parti.
- Avere lo stesso base code nella applicazione e nei server per la gestione delle push in Delphi uguale a quello utilizzato per Android.
Ambiente di test utilizzato:
- VM Delphi 11 Alexandria Ent. Edition su Windows 10 pro 64GB Ram
- Xcode 13.2.1
- Mac OS Menterey 12.0.1
- iOS sdk iPhoneOS 14.5
- Device test
- iPad 10.2 myl92ty/a os. ver 14.3
- iPhone 12 mini
Prerequisiti:
- Applicazione iOS operativa sui tuoi dispositivi in development
- Account Firebase e progetto creato
Step necessari per associare le notifiche
- Configurare la distribuzione della app sul portale developer.apple.com e su https://appstoreconnect.apple.com/apps/<appId>
- Aggiungere il codice di gestione sull’applicazione Delphi e importare l’sdk firebase per iOS
- Configurare il progetto su firebase https://console.firebase.google.com/project/
- Scaricare il file “GoogleService-Info.plist” da firebase, copiarlo nella directory del progetto e inserirlo nell’elenco di distribuzione (deploy)
- Invio di un messaggio di push di prova da firebase console o dal tuo server
Step 1 : Configurare la distribuzione della app sul portale developer.apple.com e su https://appstoreconnect.apple.com/apps/<appId>
- (apple portal) Configurare la distribuzione della app sul portale apple developer per ricevere le push
- creare un certificato per le push notifications, ricliccando su “configure” della voce “Push Notifications”
- abilitare nella sezione “Identifier” “Push Notifications” e associargli un certificato per le push “Production SSL Certificate” ( mac keystore–> Accesso Portachiavi–> Assistente certificato –> Richiedi un certificato da un autorità di certificazione )
- nella sezione “keys” sempre del tuo pannello developers Creare una chiave per le push, questa chiave la si può scaricare una volta sola
- (se non è già stato fatto) creare una app su “app Store Connect” ( il codice “sku” è un tuo codice interno (scrivici quello che vuoi basta che te lo ricordi) )
- pubblicare la app su App Store Connect anche senza il supporto alle push utilizzando il programmino “Transporter”
- creare un certificato per le push notifications, ricliccando su “configure” della voce “Push Notifications”
Step 2 : Aggiungere il codice di gestione sull’applicazione Delphi e importare l’sdk firebase per iOS
- (delphi) Scaricare da getit il package SDK FCM e decomprimerlo nella directory proposta oppure scegliere una path personalizzata
- (delphi) Creare una “Variabile di ambiente” (environment variable) in cui definire la path base di Friebase SDK for iOS ( nel mio caso C:\Users\<myusername>\Documents\Embarcadero\Studio\22.0\CatalogRepository\FirebaseSDKforiOS-6.28\Firebase ) e che ho chiamato “Firebase_6_28“
- (delphi) Modificare la search path nelle opzioni di progetto ( project –> options –> Delphi compiler –> search Path :
-
1$(Firebase_6_28)\FirebaseAnalytics\nanopb.xcframework\ios-armv7_arm64\nanopb.framework;$(Firebase_6_28)\FirebaseAnalytics\GoogleUtilities.xcframework\ios-armv7_arm64\GoogleUtilities.framework;$(Firebase_6_28)\FirebaseAnalytics\GoogleDataTransport.xcframework\ios-armv7_arm64\GoogleDataTransport.framework;$(Firebase_6_28)\FirebaseAnalytics\GoogleAppMeasurement.framework;$(Firebase_6_28)\FirebaseAnalytics\FirebaseInstallations.xcframework\ios-armv7_arm64\FirebaseInstallations.framework;$(Firebase_6_28)\FirebaseAnalytics\FirebaseCoreDiagnostics.xcframework\ios-armv7_arm64\FirebaseCoreDiagnostics.framework;$(Firebase_6_28)\FirebaseAnalytics\FirebaseCore.xcframework\ios-armv7_arm64\FirebaseCore.framework;$(Firebase_6_28)\FirebaseAnalytics\FirebaseAnalytics.framework;$(Firebase_6_28)\FirebaseMessaging\FirebaseMessaging.xcframework\ios-armv7_arm64\FirebaseMessaging.framework;$(Firebase_6_28)\FirebaseMessaging\FirebaseInstanceID.xcframework\ios-armv7_arm64\FirebaseInstanceID.framework;$(Firebase_6_28)\FirebaseAnalytics\PromisesObjC.framework\ios-armv7_arm64\PromisesObjC.framework;$(Firebase_6_28)\FirebaseMLModelInterpreter\Protobuf.xcframework\ios-armv7_arm64\Protobuf.framework
- Copiare la cartella PromisesObjC.xcframework in PromisesObjC.framework
-
- (delphi) direttiva “-ObjC” al linker LD (project –> options –> Delphi Compiler –> Linking ) per permettere di includere i metodi dell’sdk
- (delphi) copiare il file “iOSapi.FirebaseCommon.pas” di Delphi nella cartella del tuo progetto ( C:\Program Files (x86)\Embarcadero\Studio\22.0\source\rtl\ios\iOSapi.FirebaseCommon.pas –> e:\progetti\myApp ) editare il file e scriveteci dentro:
-
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778unit iOSapi.FirebaseCommon;{*******************************************************}{ }{ CodeGear Delphi Runtime Library }{ }{ Copyright(c) 2010-2021 Embarcadero Technologies, Inc. }{ All rights reserved }{ }{*******************************************************}interfaceusesMacapi.ObjectiveC,iOSapi.CocoaTypes, iOSapi.Foundation;constFIRInstanceIDErrorUnknown = 0;FIRInstanceIDErrorAuthentication = 1;FIRInstanceIDErrorNoAccess = 2;FIRInstanceIDErrorTimeout = 3;FIRInstanceIDErrorNetwork = 4;FIRInstanceIDErrorOperationInProgress = 5;FIRInstanceIDErrorInvalidRequest = 7;FIRInstanceIDAPNSTokenTypeUnknown = 0;FIRInstanceIDAPNSTokenTypeSandbox = 1;FIRInstanceIDAPNSTokenTypeProd = 2;typeFIRInstanceIDError = NSUInteger;FIRAppClass = interface(NSObjectClass)['{B8962096-555F-498E-B102-8EC66E871EF2}']{class} procedure configure; cdecl;end;FIRApp = interface(NSObject)['{FFF4B247-25C6-47B8-BBC5-893D2170EFA5}']end;TFIRApp = class(TOCGenericImport<FIRAppClass, FIRApp>) end;FIRInstanceIDClass = interface(NSObjectClass)['{4A9F1C85-AEDE-4284-A7DC-0EF9111504B1}']{class} function instanceID: pointer; cdecl;end;FIRInstanceID = interface(NSObject)['{2967A1F9-98F5-40E6-8BDA-A25D3C699ED3}']function token: NSString; cdecl;end;TFIRInstanceID = class(TOCGenericImport<FIRInstanceIDClass, FIRInstanceID>) end;implementationusesSystem.Sqlite, System.ZLib,iOSapi.StoreKit;constlibSystemConfiguration = '/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration';procedure ClangRTLoader; cdecl; external '/usr/lib/clang/lib/darwin/libclang_rt.ios.a';procedure FirebaseAnalyticsLoader; cdecl; external 'FirebaseAnalytics';procedure FirebaseCoreLoader; cdecl; external 'FirebaseCore';procedure FirebaseCoreDiagnosticsLoader; cdecl; external 'FirebaseCoreDiagnostics';procedure FirebaseInstallationsLoader; cdecl; external 'FirebaseInstallations';procedure FoundationLoader; cdecl; external {$IFDEF IOS32}libFoundation{$ELSE}framework 'Foundation'{$ENDIF};procedure GoogleAppMeasurementLoader; cdecl; external 'GoogleAppMeasurement';procedure GoogleDataTransportLoader; cdecl; external 'GoogleDataTransport';procedure GoogleUtilitiesLoader; cdecl; external 'GoogleUtilities';procedure nanopbLoader; cdecl; external 'nanopb';procedure PromisesObjCLoader; cdecl; external 'PromisesObjC';//procedure PromisesObjCLoader; cdecl; external 'PromisesObjC';procedure SystemConfigurationLoader; cdecl; external {$IFDEF IOS32}libSystemConfiguration{$ELSE}framework 'SystemConfiguration'{$ENDIF};procedure StoreKitLoader; cdecl; external {$IFDEF IOS32}libStoreKit{$ELSE}framework 'StoreKit'{$ENDIF};end.
-
- (delphi) copiare il file “iOSapi.FirebaseMessaging.pas” di Delphi nella cartella del tuo progetto ( C:\Program Files (x86)\Embarcadero\Studio\22.0\source\rtl\ios\iOSapi.FirebaseMessaging.pas–> e:\progetti\myApp )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
unit iOSapi.FirebaseMessaging; {*******************************************************} { } { CodeGear Delphi Runtime Library } { } { Copyright(c) 2010-2021 Embarcadero Technologies, Inc. } { All rights reserved } { } {*******************************************************} interface uses Macapi.ObjectiveC, iOSapi.CocoaTypes, iOSapi.Foundation; const FIRMessagingErrorUnknown = 0; FIRMessagingErrorAuthentication = 1; FIRMessagingErrorNoAccess = 2; FIRMessagingErrorTimeout = 3; FIRMessagingErrorNetwork = 4; FIRMessagingErrorOperationInProgress = 5; FIRMessagingErrorInvalidRequest = 7; FIRMessagingMessageStatusUnknown = 0; FIRMessagingMessageStatusNew = 1; FIRMessasingAPNSTokenTypeUnknown = 0; FIRMessasingAPNSTokenTypeSandbox = 1; FIRMessasingAPNSTokenTypeProd = 2; type FIRInstanceIDAPNSTokenType = NSInteger; FIRMessagingAPNSTokenType = NSInteger; FIRMessagingError = NSUInteger; FIRMessagingMessageStatus = NSInteger; TFIRMessagingConnectCompletion = procedure(error: NSError) of object; FIRMessagingMessageInfoClass = interface(NSObjectClass) ['{FDAC534F-3D79-4FF6-824E-50DC7423662A}'] end; FIRMessagingMessageInfo = interface(NSObject) ['{4D70F5C5-3635-405F-895C-F41C8D1FD76B}'] function status: FIRMessagingMessageStatus; cdecl; end; TFIRMessagingMessageInfo = class(TOCGenericImport<FIRMessagingMessageInfoClass, FIRMessagingMessageInfo>) end; FIRMessagingRemoteMessageClass = interface(NSObjectClass) ['{EF45D074-C7A5-4DB2-BCD1-53B8650419F4}'] end; FIRMessagingRemoteMessage = interface(NSObject) ['{6E2F8E14-FD8D-4B5D-8026-A607BE0B8F9C}'] function appData: NSDictionary; cdecl; end; TFIRMessagingRemoteMessage = class(TOCGenericImport<FIRMessagingRemoteMessageClass, FIRMessagingRemoteMessage>) end; FIRMessaging = interface; FIRMessagingDelegate = interface(IObjectiveC) ['{264C1F0E-3EA9-42AC-9802-EF1BC9A7E321}'] procedure applicationReceivedRemoteMessage(remoteMessage: FIRMessagingRemoteMessage); cdecl; [MethodName('messaging:didReceiveMessage:')] procedure didReceiveMessage(messaging: FIRMessaging; remoteMessage: FIRMessagingRemoteMessage); cdecl; [MethodName('messaging:didRefreshRegistrationToken:')] procedure didRefreshRegistrationToken(messaging: FIRMessaging; fcmToken: NSString); cdecl; [MethodName('messaging:didReceiveRegistrationToken:')] procedure didReceiveRegistrationToken(messaging: FIRMessaging; fcmToken: NSString); cdecl; end; FIRMessagingClass = interface(NSObjectClass) ['{62AF9A4C-681E-4BCD-9063-6209CAE08296}'] {class} function messaging: pointer; cdecl; end; FIRMessaging = interface(NSObject) ['{A721C3D4-82EB-4A7B-A5E5-42EF9E8F618E}'] function APNSToken: NSData; cdecl; procedure connectWithCompletion(handler: TFIRMessagingConnectCompletion); cdecl; function delegate: Pointer; cdecl; procedure disconnect; cdecl; procedure sendMessage(msg: NSDictionary; receiver: NSString; messageID: NSString; ttl: Int64); cdecl; procedure setAPNSToken(apnsToken: NSData; tokenType: FIRMessagingAPNSTokenType); cdecl; procedure setDelegate(delegate: Pointer); cdecl; function shouldEstablishDirectChannel: Boolean; cdecl; procedure setShouldEstablishDirectChannel(value: Boolean); cdecl; procedure subscribeToTopic(topic: NSString); cdecl; procedure unsubscribeFromTopic(topic: NSString); cdecl; end; TFIRMessaging = class(TOCGenericImport<FIRMessagingClass, FIRMessaging>) end; function kFIRInstanceIDTokenRefreshNotification: NSString; cdecl; implementation uses iOSapi.FirebaseCommon, Macapi.Helpers; function kFIRInstanceIDTokenRefreshNotification: NSString; begin Result := StrToNSStr('com.firebase.iid.notif.refresh-token'); end; procedure FirebaseInstanceIDLoader; cdecl; external 'FirebaseInstanceID'; procedure FirebaseMessagingLoader; cdecl; external 'FirebaseMessaging'; procedure ProtobufLoader; cdecl; external 'Protobuf'; end. |
- Eliminare dai files la scritta “{$IFNDEF IOS32}framework{$ENDIF}” (direttiva al compilatore) dai due file appena menzionati
- Includere le librerie di Embarcadero per la gestione delle push via FCM nel form principale del progetto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
interface uses xxxx ,System.PushNotification {$IFDEF ANDROID},FMX.PushNotification.Android {$ENDIF} {$IFDEF IOS} , FMX.PushNotification.iOS , FMX.PushNotification.FCM.iOS {$ENDIF} .. .. .. uses System.NetEncoding, {$IFDEF IOS} ,iOSapi.Foundation ,iOSapi.CoreTelephony {$ENDIF} {$IFDEF ANDROID} ,Androidapi.Helpers, Androidapi.JNIBridge, Androidapi.Jni, Androidapi.JNI.JavaTypes, FMX.Platform.Android, Androidapi.JNI.Os, FMX.Helpers.Android {$ENDIF} ; |
- Definiamo nel form i metodi di gestione della ricezione notifiche e del cambio di connessione, inoltre utilizziamo l’evento onclick “btnInitializePushClick” di un bottone “btnInitializePush” per eseguire l’inizializzazione della gestione delle notifiche. L’interfaccia viene gestita in questo demo da un Tmemo “memoLog”
1234567891011121314151617181920212223242526TfmxMain = class(TForm)....private{ Private declarations }o : TMyBridgeObject;// PUSH NOTIFICATION IDFDeviceId: string;FDeviceToken: string;function HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;procedure btnInitializePushClick(Sender: TObject);{$IF (defined(ANDROID)) OR (defined(IOS))}procedure OnServiceConnectionChange(Sender: TObject; PushChanges: TPushService.TChanges);procedure OnReceiveNotificationEvent(Sender: TObject; const ServiceNotification: TPushServiceNotification);{$ENDIF}public{ Public declarations }....end; - Implementiamo il metodo che gestisce la ricezione delle notifiche
1234567891011121314151617181920212223242526272829303132333435{$IF defined(ANDROID) OR defined(IOS)}procedure TfmxMain.OnReceiveNotificationEvent(Sender: TObject;const ServiceNotification: TPushServiceNotification);varMessageText: string;beginMemoLog.Lines.Add('-----------------------------------------');MemoLog.Lines.Add('DataKey = ' + ServiceNotification.DataKey);MemoLog.Lines.Add('Json = ' + ServiceNotification.Json.ToString);MemoLog.Lines.Add('DataObject = ' + ServiceNotification.DataObject.ToString);MemoLog.Lines.Add('---------------------------------------');end;procedure TfmxMain.OnServiceConnectionChange(Sender: TObject;PushChanges: TPushService.TChanges);varPushService: TPushService;beginPushService := TPushServiceManager.Instance.GetServiceByName(TPushService.TServiceNames.FCM);if TPushService.TChange.DeviceToken in PushChanges thenbeginFDeviceToken := PushService.DeviceTokenValue[TPushService.TDeviceTokenNames.DeviceToken];MemoLog.Lines.Add('Firebase Token: ' + FDeviceToken);// Log.d('Firebase device token: token=' + FDeviceToken);end;if (TPushService.TChange.Status in PushChanges) and(PushService.Status = TPushService.TStatus.StartupError) thenMemoLog.Lines.Add('Error: ' + PushService.StartupError);end;{$ENDIF} - Inizilizzazione delle push tramite evento “click” del bottone:
-
12345678910111213141516171819202122232425262728293031323334353637383940procedure TfmxMain.btnIniutializePushClick(Sender: TObject);{$IF (defined(ANDROID) OR defined(IOS))}varPushService: TPushService;ServiceConnection: TPushServiceConnection;Notifications: TArray<TPushServiceNotification>;beginPushService :=TPushServiceManager.Instance.GetServiceByName(TPushService.TServiceNames.FCM);ServiceConnection := TPushServiceConnection.Create(PushService);ServiceConnection.Active := True;ServiceConnection.OnChange := OnServiceConnectionChange;ServiceConnection.OnReceiveNotification := OnReceiveNotificationEvent;FDeviceId :=PushService.DeviceIDValue[TPushService.TDeviceIDNames.DeviceId];MemoLog.Lines.Add('DeviceID: ' + FDeviceId);MemoLog.Lines.Add('Ready to receive!');// Checks notification on startup, if application was launched fromcold start// by tapping on Notification in Notification CenterNotifications := PushService.StartupNotifications;if Length(Notifications) > 0 thenbeginMemoLog.Lines.Add('-----------------------------------------');MemoLog.Lines.Add('DataKey = ' + Notifications[0].DataKey);MemoLog.Lines.Add('Json = ' + Notifications[0].Json.ToString);MemoLog.Lines.Add('DataObject = ' +Notifications[0].DataObject.ToString);MemoLog.Lines.Add('-----------------------------------------');end;end;{$ELSE}beginend;{$ENDIF}
Step 3: Configurare il progetto su firebase:
- Aggiungi la app al progetto cliccando su “Aggiungi App” e scarica il file GoggleService-Info.plist nella directory del tuo progetto Delphi.
- Nella sezione impostazioni devi caricare il certificato APN che hai precedentemente generato sul portale di apple nella sezione “Keys” e caricarlo in:
A questo punto, compilate l’applicazione e fatela girare sul vostro dispositivo.
All’interno di memoLog troverete un JSON contente il deviceID, dovete copiarlo per fare un test di invio del primo messaggio da firebase.
Step 5) Invio di un messaggio di push di prova da firebase console o dal tuo server
Copiare la chiave server ed eseguire una chiamata rest come segue:
Il JSON deve contenere anche l’oggetto “notification” se volete che la notifica arrivi anche quando la vostra app non è attiva:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "to": "d-fmNb_zQ6u39hIOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "priority" : "high", "message_id":"2205", "time_to_live": 200, "notification":{ "title":"Portugal vs. Denmark", "body":"great match!" }, "data":{ "body":"Ciao", "title":"Cisco" } } |
that’s all