Fatto? Probabilmente avrai notato che nel video ci sono diversi sistemi che parlano tra di loro:
- Il server della tua app (o Firebase Notifications) comunica con Firebase Cloud Messaging
- Firebase Cloud Messaging poi comunica con APNs
- APNs comunica con il dispositivo di destinazione dell'utente
- Sul dispositivo di destinazione dell'utente, iOS comunica con la tua app
Avere questi quattro canali di comunicazione significa che ci sono quattro possibilità che le cose vadano storte. E quando succede, molto spesso si manifestano con questo frustrante tipo di bug "La notifica è stata inviata ma non vedo nulla sul mio dispositivo", il che richiede un po' di lavoro da detective. Ecco qua le operazioni che consiglio di eseguire per iniziare a monitorare questi errori.
1. Disabilita momentaneamente qualsiasi chiamata connectToFCM()
Come abbiamo visto nel video, la tua app può connettersi esplicitamente a Firebase Cloud Messaging chiamando
connectToFCM()
quando è in primo piano, e ciò consente di ricevere messaggi di soli dati che non abbiano il flag
content-available
direttamente tramite FCM.
Anche se questo può essere utile in alcune circostanze, ti consiglio di disabilitarlo mentre esegui il debugging. È semplicemente un fattore in più che ti conviene eliminare. Mi è capitato di vedere alcuni problemi come "La mia app riceve le notifiche in primo piano, ma non in background", che probabilmente si sono verificati perché lo sviluppatore riceve i messaggi solo attraverso il canale FCM e la configurazione di APNs non ha mai funzionato correttamente.
Se ci sono altri problemi a questo punto, e ti ritrovi da "Ehi, ricevevo notifiche in primo piano" a "Non ricevo proprio più notifiche", allora questo è un segnale che la tua app non è mai stata configurata correttamente per ricevere le notifiche da APNs. Potrebbe sembrare che l'app sia peggiorata ma almeno è peggiorata
in modo coerente, urrà! Continua a leggere per il debugging dell'implementazione di APNs!
Per le prossime fasi, faremo un passo indietro per tornare alla catena "Notifiche a FCM, ad APNs, a iOS, alla tua app". Cominciamo assicurandoci che iOS possa effettivamente comunicare con la tua app...
2. Aggiungi un po' di debugging print() vecchio stile
Grazie a un metodo intelligente di swizzling, con Firebase Cloud Messaging non devi implementare né
application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
né
application(_:didFailToRegisterForRemoteNotificationsWithError:)
nell'Application Delegate.
Tuttavia, a scopo di debugging, preferisco aggiungere qualcosa a questi metodi e stampare alcune informazioni di debug per vedere se si verificano errori di cui dovrei essere a conoscenza. Inizia aggiungendo un po' di output di debug al tuo metodo di gestione degli errori. Una cosa del genere:
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Oh no! Failed to register for remote notifications with error \(error)") }
In teoria qualsiasi messaggio di errore stampato qui sarà probabilmente stampato anche dalla libreria client FCM, ma preferisco avere i miei messaggi, perché posso cercare stringhe specifiche (come "Oh no!" dell'esempio riportato sopra) nell'output di Xcode. Questo mi dà anche una pratica riga di codice per aggiungere un punto di interruzione.
Visto che ci siamo, nel tuo metodo
didRegister...
, stampa una versione in forma leggibile del token del tuo dispositivo:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { var readableToken: String = "" for i in 0..<deviceToken.count { readableToken += String(format: "%02.2hhx", deviceToken[i] as CVarArg) } print("Received an APNs device token: \(readableToken)") }
Non devi disabilitare il metodo di swizzling o altro per aggiungere questi metodi di debug. Firebase andrà avanti e li chiamerà non appena ha finito di chiamare il suo set di metodi.
Se a questo punto vedi un messaggio di errore: se ricevi un messaggio di errore o non ti ritorna un token del dispositivo, controlla il messaggio di errore per trovare un buon indizio su cosa sia andato storto. La maggior parte degli errori a questo punto rientra nella categoria "Mi vergogno a confessare perché non funziona", ossia:
- testare sul simulatore iOS e non sul dispositivo
- dimenticare di abilitare le notifiche push nelle impostazioni del progetto Xcode
- non chiamare
application.registerForRemoteNotifications()
quando l'app si avvia.
Certo, si tratta di errori semplici, ma senza il beneficio di stampare messaggi alla console Xcode, è facile che passino inosservati.
3. Conferma di poter inviare notifiche visibili dall'utente
Per tutte le app iOS è necessario ottenere l'autorizzazione esplicita dell'utente per mostrare qualsiasi tipo di avviso di notifica o suono. Se ti trovi in una situazione in cui sembra che la tua app non riceva messaggi di notifica in background, significa che potrebbe non avere l'autorizzazione di iOS a farlo.
Puoi verificare questo comportamento in iOS>= 10 aggiungendo il seguente codice da qualche parte nella tua app.
UNUserNotificationCenter.current().getNotificationSettings { (settings) in print("Alert setting is \(settings.alertSetting == UNNotificationSetting.enabled ? "enabled" : "disabled")") print("Sound setting is \(settings.soundSetting == UNNotificationSetting.enabled ? "enabled" : "disabled")") }
Se a questo punto vedi messaggi "disabilitati": significa che inavvertitamente hai negato l'autorizzazione all'app di inviare notifiche oppure non l'hai mai chiesta.
Se per sbaglio hai fatto clic sul pulsante Don't allow (Non consentire) quando l'app ha chiesto l'autorizzazione a inviare le notifiche, puoi risolvere il problema in questo modo: vai su
Settings (Impostazioni), trova la tua app, seleziona
Notifications (Notifiche), quindi fai clic su
Allow Notifications (Consenti notifiche).
Se non hai mai chiesto il permesso di mostrare le autorizzazioni visibili all'utente, allora significa che è necessario aggiungere codice come questo (per iOS> = 10) all'interno della tua app:
let authOptions : UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { (granted, error) in if (error != nil) { print("I received the following error: \(error)") } else if (granted) { print ("Authorization was granted!") } else { print ("Authorization was not granted. :(") } }
Ma se tutto è a posto, a questo punto, puoi passare al debugging della connessione APNs!
4. Effettua una chiamata direttamente tramite APNs
Ricorda che solo perché stai utilizzando FCM per gestire le notifiche non significa che non puoi anche usare direttamente APNs. Ci sono alcuni modi per provarlo, ad esempio utilizzare uno strumento open-source come
NWPusher per inviare notifiche di prova. Ma personalmente preferisco inviare le richieste APNs direttamente attraverso una chiamata curl.
Fare una richiesta curl APNs è più facile in questi giorni, ora che APNs supporta HTTP/2. Ma dovrai accertarti che la copia di curl sia aggiornata. Per scoprirlo, esegui curl --version. Vedrai probabilmente qualcosa del genere:
curl 7.47.1 (x86_64-apple-darwin15.6.0) libcurl/7.47.1 OpenSSL/1.0.2f zlib/1.2.5 nghttp2/1.8.0 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets
Se vuoi comunicare con APNs, avrai bisogno di una versione di curl superiore a 7.43 e HTTP2 dovrà essere tra le caratteristiche. Se la versione di curl non soddisfa questi requisiti, dovrai aggiornarla.
Il post di Simone Carletti offre ottime indicazioni su come farlo.
Quindi dovrai convertire il file
.p12
scaricato dal Portale degli sviluppatori Apple in un file .pem. Puoi farlo utilizzando questo comando:
openssl pkcs12 -in MyApp_APNS_Certificate.p12 -out myapp-push-cert.pem -nodes -clcerts
Avrai anche bisogno del token dispositivo APNs per il tuo dispositivo di destinazione. Se hai aggiunto il testo di debugging elencato in precedenza nel tuo metodo
application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
, riuscirai ad afferrarlo dalla console Xcode. Dovrebbe presentarsi così
ab8293ad24537c838539ba23457183bfed334193518edf258385266422013ac0d
e ora puoi effettuare una chiamata curl di questo tipo:
> curl --http2 --cert ./myapp-push-cert.pem \ -H "apns-topic: com.example.yourapp.bundleID" \ -d '{"aps":{"alert":"Hello from APNs!","sound":"default"}}' \ https://api.development.push.apple.com/3/device/ab8293ad24537c838539ba23457183bfed334193518edf258385266422013ac0d
Puoi notare tre cose:
- L'argomento
--cert
deve collegarsi al file .pem
creato in precedenza.
- In
apns-topic
, includi l'ID bundle della tua app. Lo so, il concetto di apns-topics è completamente diverso da quello degli argomenti di Firebase Cloud Messaging.
- Assicurati che l'ID dispositivo sia incluso alla fine dell'URL. Non basta copiare e incollare quello che hai, non funzionerà.
Se tutto è andato bene, vedrai una notifica push sul dispositivo e puoi passare alla fase successiva. In caso contrario, ecco cosa devi controllare:
- Hai ricevuto un messaggio di errore da APNs? Significa che qualcosa non è andato come previsto. Ecco alcuni esempi dei messaggi più frequenti:
- "Token del dispositivo errato". Il messaggio che ricevi è qualcosa di simile a questo. Il token del dispositivo è errato. Ricontrolla di averlo copiato correttamente dalla tua app.
- "Token del dispositivo non per l'argomento". Può significare che l'argomento non è correttamente impostato sull'ID bundle della tua app, ma potrebbe anche significare che hai utilizzato il certificato errato (ho ricevuto questo messaggio quando ho usato il file .pem sbagliato).
- La tua app è in background? Ricorda che iOS non mostrerà automaticamente gli avvisi di notifica o i suoni se la tua app è in primo piano.
- In iOS 10 la visualizzazione di questi avvisi è stata resa molto più facile anche quando l'app è in primo piano. Devi solo chiamare
completionHandler([.alert])
alla fine di userNotificationCenter(_:willPresent:withCompletionHandler:)
- Stai inviando richieste APNs valide? Ci sono alcuni tipi di richieste che, pur essendo sintatticamente corrette, possono essere respinte. Al momento di scrivere, potrebbe trattarsi dell'invio di notifiche silenziose che non includono il flag content-available o dell'invio di notifiche silenziose ad alta priorità.
- iOS può anche ritardare le notifiche silenziose se la tua app tralascia di chiamare il suo completionHandler in un ragionevole lasso di tempo dopo averle ricevute o usa troppa potenza per elaborare queste notifiche. Per ulteriori informazioni, consulta la documentazione di Apple.
- Problemi con APNs? Ricontrolla lo stato di APNs e del rispettivo Sandbox su https://developer.apple.com/system-status/
Ma se tutto sembra funzionare correttamente fino a qui, è il momento di passare alla fase successiva...
5. Effettua una chiamata curl direttamente tramite APNs
Dopo aver verificato il corretto funzionamento della chiamata APNs, il passo successivo è confermare che tutto proceda bene dal lato di FCM. Per questa verifica in genere effettuo un'altra chiamata curl e, perché funzioni, sono necessari due elementi: la chiave del server e il token dispositivo FCM del dispositivo di destinazione.
Per ottenere la chiave del server, vai nelle impostazioni di
Cloud Messaging del tuo progetto nella Console Firebase. La chiave del server dovrebbe essere elencata lì sotto forma di una stringa gigante con 175 caratteri.
Ottenere il token dispositivo FCM è leggermente più complesso. Quando la tua app riceve un token APNs, lo invierà ai server FCM in cambio di un token dispositivo FCM. Dopo aver ricevuto questo token FCM, la libreria FCM attiverà una notifica di "Aggiorna istanza ID token".
1 L'ascolto della NSNotification
firInstanceIDTokenRefresh
ti consente di sapere qual è il token dispositivo FCM, ma questa notifica viene attivata solo quando il token del dispositivo cambia. Ciò accade di rado, ad esempio quando passi dalla build di debug a quella di produzione, oppure quando esegui la tua app per la prima volta. Altrimenti questa notifica non viene chiamata.
Tuttavia, è possibile recuperare il token dispositivo FCM memorizzato nella cache semplicemente attraverso la libreria InstanceID, che ti darà qualsiasi token del dispositivo archiviato, se disponibile. Quindi per ottenere il token FCM migliore e più recente, ti consigliamo di scrivere il codice in questo modo:
func application(_ application: UIApplication, didFinishLaunchingWithOptions // ... printFCMToken() // This will be nil the first time, but it will give you a value on most subsequent runs NotificationCenter.default.addObserver(self, selector: #selector(tokenRefreshNotification), name: NSNotification.Name.firInstanceIDTokenRefresh, object: nil) application.registerForRemoteNotifications() //... } func printFCMToken() { if let token = FIRInstanceID.instanceID().token() { print("Your FCM token is \(token)") } else { print("You don't yet have an FCM token.") } } func tokenRefreshNotification(_ notification: NSNotification?) { if let updatedToken = FIRInstanceID.instanceID().token() { printFCMToken() // Do other work here like sending the FCM token to your server } else { print("We don't have an FCM token yet") } }
La prima volta che esegui l'app, verrà visualizzato un messaggio per comunicarti che non hai un token FCM, seguito subito dopo da un messaggio con il tuo token effettivo. Nelle esecuzioni successive dovresti vedere subito il tuo token memorizzato nella cache. Si tratta di una stringa casuale di 153 caratteri che assomiglia molto alla chiave del server, quindi non confonderle.
Ora hai entrambe le informazioni per la chiamata curl. Prova a effettuare una chiamata così:
> curl --header "Content-Type: application/json" \ --header "Authorization: key=AU...the rest of your server key...s38txvmxME-W1N4" \ https://fcm.googleapis.com/fcm/send \ -d '{"notification": {"body": "Hello from curl via FCM!", "sound": "default"}, "priority": "high", "to": "gJHcrfzW2Y:APA91...the rest of your FCM token...-JgS70Jm"}'
Se tutto è andato come doveva, vedrai una notifica sul tuo dispositivo oltre a una risposta dell'esito positivo inviata da FCM.
{"multicast_id":86655058283942579,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1486683492595106961%9e7ad9838bdea651f9"}]}
Non farti illudere da questa risposta. Significa solo che FCM ha ricevuto il tuo messaggio, non significa che l'ha consegnato ad APNs. Devi verificare che la notifica appaia sul tuo dispositivo.
Se non ti sembra di averla ricevuta, ecco cosa bisogna controllare.
- Vedi un messaggio di errore nella tua risposta? Non ignorare questi messaggi. Sono degli ottimi indizi su cosa sta succedendo.
InvalidRegistration
significa che non hai il token dispositivo FCM corretto (ricordati che è chiamato "token di registrazione").
- Un errore 401 con il messaggio "La chiave di autenticazione (Server-) della richiesta conteneva un FCM-Token non valido o in formato non corretto" indica che la chiave del server probabilmente è errata. Accertati di aver copiato tutto correttamente dalla Console Firebase.
- La tua
priority
è impostata su high
? I dispositivi Android e iOS hanno differenti interpretazioni di cosa significhi priorità alta e media.
- Su Android, priorità media significa essenzialmente "Invia il messaggio ma rispetta il dispositivo dell'utente se è in modalità Doze". Questo è il motivo per cui in genere FCM usa la "media" come priorità predefinita se non ne viene specificata un'altra.
- Su iOS, priorità media (o 5) può essere descritta come "Forse te lo invieremo a un certo punto ma in questo nostro strano mondo, chi può dirlo?!? ¯\_(ツ)_/¯".
- Per questo motivo, APNs stabilisce il valore predefinito di priorità a 10 (ossia "alta") quando non è specificato alcun valore di priorità e viene richiesto di utilizzare la priorità media per inviare i messaggi
content-available
di soli dati.
- L'ideale sarebbe inviare la maggior parte dei messaggi visibili dall'utente con priorità media ai dispositivi Android e ad alta priorità a quelli iOS. Se stai usando il pannello Firebase Notifications, puoi farlo facilmente.
- Utilizzi la sintassi APNs invece di quella FCM? Anche se FCM tradurrà correttamente FCM-speak ad APNs, si confonderà se invii la sintassi APNs. Pertanto verifica di aver inviato messaggi correttamente formattati per FCM. In particolare, conferma di aver impostato la "priorità" su "alta" e non su "10".
- Se invii un messaggio content-available, accertati di specificare
"content_available": true
con il carattere di sottolineatura e non "content-available":
2
- A questo punto ti consiglio anche di inviare una notifica tramite il pannello Firebase Notifications. Se puoi effettuare una chiamata tramite Notifications ma non una chiamata curl, potrebbe indicare che il messaggio non è formattato correttamente.
- Hai caricato il certificato di APNs nella console Firebase? Ed è scaduto? Ricorda che FCM ha bisogno di quel certificato per poter comunicare con APNs.
6. Effettua una chiamata tramite il pannello Notifications e/o il tuo server
Se sei arrivato a questo punto, di fatto possiamo dedurre che tu abbia creato una solida catena di comunicazione da FCM ad APNs a iOS alla tua app. Quindi sarei molto sorpreso se il pannello Notifications non funzionasse proprio ora. In caso contrario, la cosa migliore è controllare
status.firebase.google.com per verificare che non ci siano problemi con il servizio Cloud Messaging (questo include anche il pannello Notifications).
Se il problema risiede nel codice del server, beh, allora dipende da te e dal tuo server. Ma ora che hai capito esattamente quali dati è necessario generare per effettuare una corretta chiamata FCM, spero che tu possa affrontare questa parte da solo con fiducia. Se non altro con un parvenza di fiducia... spesso è sufficiente a ingannare la maggior parte delle persone.
Ce l'abbiamo fatta! Beh, abbiamo affrontato parecchi argomenti e spero che ti sia fermato, diciamo, per esempio, al passaggio 2 perché ti sei accorto di aver semplicemente dimenticato di premere un tasto nel progetto Xcode. Se le cose stanno così, probabilmente non leggerai neanche questa parte finale. Ma se sei arrivato fino a qui, probabilmente è perché hai ancora dei problemi con l'implementazione, in tal caso... approfitta dei nostri
canali di supporto o chiedi a
@lmoroney perché a questo punto sono a corto di suggerimenti.
Grazie di aver letto il mio post!
[1] Questa è una NSNotification, non una notifica di APNs. Urrà per il sovraccarico di termini!
[2] Un errore interessante che ricordo era quello di uno sviluppatore che non riusciva a capire perché i suoi messaggi content-available venissero ricevuti solo quando l'app era in primo piano. Si è scoperto che aveva esplicitamente collegato a FCM (come nella fase 1) e utilizzato la chiave (non valida)
"content-available"
nel suo messaggio. Poiché FCM non l'ha tradotto in un messaggio content-available APNs valido, è stato interpretato come un messaggio di soli dati da inviare solo in FCM, ed è il motivo per cui funzionava solo quando l'app era in primo piano.