Dependendo do estado do dispositivo, as mensagens recebidas são tratadas de forma diferente. Para entender esses cenários e como integrar o FCM em sua própria aplicação, primeiro é importante estabelecer os vários estados em que um dispositivo pode estar:
Estado | Descrição |
---|---|
Primeiro plano | Quando o aplicativo está aberto, visualizado e em uso. |
Fundo | Quando o aplicativo está aberto, mas em segundo plano (minimizado). Isso normalmente ocorre quando o usuário pressionou o botão "home" no dispositivo, mudou para outro aplicativo usando o alternador de aplicativos ou abriu o aplicativo em uma guia diferente (web). |
Terminado | Quando o dispositivo está bloqueado ou o aplicativo não está em execução. |
Existem alguns pré-requisitos que devem ser atendidos antes que o aplicativo possa receber cargas de mensagens via FCM:
- A aplicação deve ter sido aberta pelo menos uma vez (para permitir o registo na FCM).
- No iOS, se o usuário deslizar o aplicativo para fora do alternador de aplicativos, ele deverá ser reaberto manualmente para que as mensagens em segundo plano comecem a funcionar novamente.
- No Android, se o usuário forçar o encerramento do aplicativo nas configurações do dispositivo, ele deverá ser reaberto manualmente para que as mensagens comecem a funcionar.
- Na web, você deve ter solicitado um token (usando
getToken()
) com seu certificado web push.
Solicitar permissão para receber mensagens
No iOS, macOS, web e Android 13 (ou mais recente), antes que as cargas do FCM possam ser recebidas no seu dispositivo, você deve primeiro pedir permissão ao usuário.
O pacote firebase_messaging
fornece uma API simples para solicitar permissão por meio do método requestPermission
. Esta API aceita vários argumentos nomeados que definem o tipo de permissões que você gostaria de solicitar, como se mensagens contendo cargas de notificação podem acionar um som ou ler mensagens via Siri. Por padrão, o método solicita permissões padrão sensatas. A API de referência fornece documentação completa sobre a finalidade de cada permissão.
Para começar, chame o método da sua aplicação (no iOS será exibido um modal nativo, na web o fluxo da API nativa do navegador será acionado):
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
A authorizationStatus
do objeto NotificationSettings
retornado da solicitação pode ser usada para determinar a decisão geral do usuário:
-
authorized
: o usuário concedeu permissão. -
denied
: o usuário negou permissão. -
notDetermined
: o usuário ainda não escolheu se deseja conceder permissão. -
provisional
: o usuário concedeu permissão provisória
As outras propriedades em NotificationSettings
retornam se uma permissão específica está habilitada, desabilitada ou não tem suporte no dispositivo atual.
Depois que a permissão for concedida e os diferentes tipos de estado do dispositivo forem compreendidos, seu aplicativo poderá começar a lidar com as cargas úteis do FCM recebidas.
Tratamento de mensagens
Com base no estado atual do seu aplicativo, cargas recebidas de diferentes tipos de mensagens exigem implementações diferentes para lidar com elas:
Mensagens em primeiro plano
Para lidar com mensagens enquanto seu aplicativo está em primeiro plano, ouça o stream onMessage
.
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
O fluxo contém um RemoteMessage
, detalhando diversas informações sobre a carga útil, como de onde ela veio, o ID exclusivo, a hora de envio, se continha uma notificação e muito mais. Como a mensagem foi recuperada enquanto seu aplicativo estava em primeiro plano, você pode acessar diretamente o estado e o contexto do seu aplicativo Flutter.
Mensagens de primeiro plano e de notificação
As mensagens de notificação que chegam enquanto o aplicativo está em primeiro plano não exibirão uma notificação visível por padrão, tanto no Android quanto no iOS. No entanto, é possível substituir esse comportamento:
- No Android, você deve criar um canal de notificação de “Alta Prioridade”.
- No iOS, você pode atualizar as opções de apresentação do aplicativo.
Mensagens em segundo plano
O processo de tratamento de mensagens em segundo plano é diferente em plataformas nativas (Android e Apple) e baseadas na web.
Plataformas Apple e Android
Lide com mensagens em segundo plano registrando um manipulador onBackgroundMessage
. Quando as mensagens são recebidas, um isolamento é gerado (somente Android, iOS/macOS não requer um isolamento separado) permitindo que você manipule mensagens mesmo quando seu aplicativo não estiver em execução.
Há algumas coisas que você deve ter em mente sobre o gerenciador de mensagens em segundo plano:
- Não deve ser uma função anônima.
- Deve ser uma função de nível superior (por exemplo, não um método de classe que requer inicialização).
- Ao usar o Flutter versão 3.3.0 ou superior, o manipulador de mensagens deve ser anotado com
@pragma('vm:entry-point')
logo acima da declaração da função (caso contrário, ele pode ser removido durante a agitação da árvore para o modo de liberação).
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
Como o manipulador é executado isoladamente, fora do contexto do aplicativo, não é possível atualizar o estado do aplicativo ou executar qualquer lógica que afete a interface do usuário. Você pode, no entanto, executar lógicas como solicitações HTTP, realizar operações IO (por exemplo, atualizar o armazenamento local), comunicar-se com outros plug-ins, etc.
Também é recomendado completar sua lógica o mais rápido possível. A execução de tarefas longas e intensas afeta o desempenho do dispositivo e pode fazer com que o sistema operacional encerre o processo. Se as tarefas forem executadas por mais de 30 segundos, o dispositivo poderá encerrar automaticamente o processo.
Rede
Na Web, escreva um Service Worker JavaScript que seja executado em segundo plano. Use o service worker para lidar com mensagens em segundo plano.
Para começar, crie um novo arquivo no diretório da web
e chame-o firebase-messaging-sw.js
:
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");
firebase.initializeApp({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
});
const messaging = firebase.messaging();
// Optional:
messaging.onBackgroundMessage((message) => {
console.log("onBackgroundMessage", message);
});
O arquivo deve importar os SDKs do aplicativo e de mensagens, inicializar o Firebase e expor a variável messaging
.
Em seguida, o trabalhador deve ser cadastrado. Dentro do arquivo de entrada, após o carregamento do arquivo main.dart.js
, registre seu trabalhador:
<html>
<body>
...
<script src="main.dart.js" type="application/javascript"></script>
<script>
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// ADD THIS LINE
navigator.serviceWorker.register('/firebase-messaging-sw.js');
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
// ...
});
}
</script>
Em seguida, reinicie seu aplicativo Flutter. O trabalhador será registrado e quaisquer mensagens em segundo plano serão tratadas por meio deste arquivo.
Lidando com interação
Como as notificações são uma dica visível, é comum que os usuários interajam com elas (pressionando). O comportamento padrão no Android e no iOS é abrir o aplicativo. Se o aplicativo for encerrado, ele será iniciado; se estiver em segundo plano, será colocado em primeiro plano.
Dependendo do conteúdo de uma notificação, você pode querer lidar com a interação do usuário quando o aplicativo for aberto. Por exemplo, se uma nova mensagem de bate-papo for enviada por meio de uma notificação e o usuário pressioná-la, você pode querer abrir a conversa específica quando o aplicativo for aberto.
O pacote firebase-messaging
oferece duas maneiras de lidar com essa interação:
-
getInitialMessage()
: Se o aplicativo for aberto a partir de um estado finalizado, umFuture
contendo umaRemoteMessage
será retornado. Uma vez consumido, oRemoteMessage
será removido. -
onMessageOpenedApp
: umStream
que publica umRemoteMessage
quando o aplicativo é aberto em um estado de segundo plano.
É recomendável que ambos os cenários sejam tratados para garantir uma experiência de usuário tranquila para seus usuários. O exemplo de código abaixo descreve como isso pode ser alcançado:
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
// It is assumed that all messages contain a data field with the key 'type'
Future<void> setupInteractedMessage() async {
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message),
);
}
}
@override
void initState() {
super.initState();
// Run code required to handle interacted messages in an async function
// as initState() must not be async
setupInteractedMessage();
}
@override
Widget build(BuildContext context) {
return Text("...");
}
}
A maneira como você lida com a interação depende da configuração do seu aplicativo. O exemplo acima mostra uma ilustração básica usando um StatefulWidget.
Localizar mensagens
Você pode enviar strings localizadas de duas maneiras diferentes:
- Armazene o idioma preferido de cada um de seus usuários em seu servidor e envie notificações personalizadas para cada idioma
- Incorpore strings localizadas em seu aplicativo e use as configurações de localidade nativas do sistema operacional
Veja como usar o segundo método:
Android
Especifique suas mensagens no idioma padrão em
resources/values/strings.xml
:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
Especifique as mensagens traduzidas no diretório
values- language
. Por exemplo, especifique mensagens em francês emresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
Na carga útil do servidor, em vez de usar as chaves
title
,message
ebody
, usetitle_loc_key
ebody_loc_key
para sua mensagem localizada e defina-as como o atributoname
da mensagem que você deseja exibir.A carga útil da mensagem ficaria assim:
{ "data": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } }
iOS
Especifique suas mensagens de idioma padrão em
Base.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
Especifique as mensagens traduzidas no diretório
language .lproj
. Por exemplo, especifique mensagens em francês emfr.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
A carga útil da mensagem ficaria assim:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_loc_key": "NOTIFICATION_MESSAGE" } }
Habilitar exportação de dados de entrega de mensagens
Você pode exportar os dados da sua mensagem para o BigQuery para análise posterior. O BigQuery permite analisar os dados usando o BigQuery SQL, exportá-los para outro provedor de nuvem ou usar os dados para seus modelos de ML personalizados. Uma exportação para o BigQuery inclui todos os dados disponíveis para mensagens, independentemente do tipo de mensagem ou se a mensagem é enviada pela API ou pelo compositor do Notifications.
Para ativar a exportação, primeiro siga as etapas descritas aqui e depois siga estas instruções:
Android
Você pode usar o seguinte código:
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
Para iOS, você precisa alterar o AppDelegate.m
com o seguinte conteúdo.
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}
@end
Rede
Para Web, você precisa alterar seu service worker para usar a versão v9 do SDK. A versão v9 precisa ser empacotada, então você precisa usar um empacotador como esbuild
por exemplo, para fazer o service worker funcionar. Veja o aplicativo de exemplo para ver como conseguir isso.
Depois de migrar para o SDK v9, você poderá usar o seguinte código:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Não se esqueça de executar yarn build
para exportar a nova versão do seu service worker para a pasta web
.
Exibir imagens em notificações no iOS
Em dispositivos Apple, para que as notificações FCM recebidas exibam imagens da carga útil do FCM, você deve adicionar uma extensão de serviço de notificação adicional e configurar seu aplicativo para usá-la.
Se você estiver usando a autenticação por telefone do Firebase, deverá adicionar o pod do Firebase Auth ao seu Podfile.
Etapa 1 – Adicionar uma extensão de serviço de notificação
- No Xcode, clique em Arquivo > Novo > Destino...
- Um modal apresentará uma lista de possíveis alvos; role para baixo ou use o filtro para selecionar Extensão do serviço de notificação . Clique em Avançar .
- Adicione um nome de produto (use "ImageNotification" para acompanhar este tutorial), defina o idioma como Objective-C e clique em Finish .
- Habilite o esquema clicando em Ativar .
Passo 2 – Adicionar destino ao Podfile
Certifique-se de que sua nova extensão tenha acesso ao pod Firebase/Messaging
adicionando-a ao Podfile:
No Navegador, abra o Podfile: Pods > Podfile
Role para baixo até o final do arquivo e adicione:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
Instale ou atualize seus pods usando
pod install
no diretórioios
oumacos
.
Passo 3 – Use o auxiliar de extensão
Neste ponto, tudo ainda deve estar funcionando normalmente. A etapa final é invocar o auxiliar de extensão.
No navegador, selecione sua extensão ImageNotification
Abra o arquivo
NotificationService.m
.Na parte superior do arquivo, importe
FirebaseMessaging.h
logo apósNotificationService.h
conforme mostrado abaixo.Substitua o conteúdo de
NotificationService.m
por:#import "NotificationService.h" #import "FirebaseMessaging.h" #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService /* Uncomment this if you are using Firebase Auth - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { if ([[FIRAuth auth] canHandleURL:url]) { return YES; } return NO; } - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { for (UIOpenURLContext *urlContext in URLContexts) { [FIRAuth.auth canHandleURL:urlContext.URL]; } } */ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // Modify the notification content here... [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler]; } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. self.contentHandler(self.bestAttemptContent); } @end
Passo 4 – Adicione a imagem à carga útil
Na sua carga de notificação, agora você pode adicionar uma imagem. Consulte a documentação do iOS sobre como criar uma solicitação de envio . Lembre-se de que o tamanho máximo de imagem de 300 KB é imposto pelo dispositivo.