Dependendo do estado de um dispositivo, as mensagens recebidas são tratadas de forma diferente. Para entender esses cenários e como integrar o FCM em seu próprio aplicativo, primeiro é importante estabelecer os vários estados em que um dispositivo pode estar:
Estado | Descrição |
---|---|
Primeiro plano | Quando o aplicativo está aberto, em exibição e em uso. |
Fundo | Quando o aplicativo está aberto, mas em segundo plano (minimizado). Isso geralmente ocorre quando o usuário pressionou o botão "início" no dispositivo, mudou para outro aplicativo usando o alternador de aplicativos ou abriu o aplicativo em uma guia diferente (web). |
Rescindido | Quando o dispositivo está bloqueado ou o aplicativo não está em execução. |
Existem algumas pré-condições que devem ser atendidas antes que o aplicativo possa receber cargas úteis de mensagens via FCM:
- O aplicativo deve ter sido aberto pelo menos uma vez (para permitir o registro 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 (Apple e Web)
No iOS, macOS e web, antes que as cargas úteis do FCM possam ser recebidas em seu dispositivo, você deve primeiro solicitar a permissão do usuário.
O pacote firebase_messaging
fornece uma API simples para solicitar permissão por meio do método requestPermission
. Essa API aceita vários argumentos nomeados que definem o tipo de permissão 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 será acionado o fluxo da API nativa do navegador):
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 propriedade 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 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 sem 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, as cargas recebidas de diferentes tipos de mensagens exigem diferentes implementações para lidar com elas:
Mensagens em primeiro plano
Para manipular mensagens enquanto seu aplicativo está em primeiro plano, ouça o fluxo 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 stream contém um RemoteMessage
, detalhando várias informações sobre a carga útil, como de onde veio, a ID exclusiva, a hora de envio, se continha uma notificação e muito mais. Como a mensagem foi recuperada enquanto seu aplicativo está em primeiro plano, você pode acessar diretamente o estado e o contexto de seu aplicativo Flutter.
Primeiro plano e mensagens 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 de fundo
O processo de manipulação de mensagens de fundo é 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 isolado é gerado (somente Android, iOS/macOS não requer um isolado separado), permitindo que você manipule mensagens mesmo quando seu aplicativo não estiver em execução.
Há algumas coisas a serem lembradas sobre o manipulador 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).
- Deve ser anotado com
@pragma('vm:entry-point')
logo acima da declaração da função (caso contrário, 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 em seu próprio isolamento fora do contexto de seus aplicativos, 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ógica como solicitações HTTP, executar operações de E/S (por exemplo, atualizar o armazenamento local), comunicar-se com outros plug-ins, etc.
Também é recomendável concluir sua lógica o mais rápido possível. A execução de tarefas longas e intensivas afeta o desempenho do dispositivo e pode fazer com que o SO encerre o processo. Se as tarefas forem executadas por mais de 30 segundos, o dispositivo pode interromper automaticamente o processo.
Rede
Na Web, escreva um JavaScript Service Worker 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 seu diretório da web
e chame 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 o aplicativo e os SDKs de mensagens, inicializar o Firebase e expor a variável de messaging
.
Em seguida, o trabalhador deve ser registrado. 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 de fundo serão tratadas através deste arquivo.
Manipulando a Interação
Como as notificações são uma deixa 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á trazido para o primeiro plano.
Dependendo do conteúdo de uma notificação, você pode querer lidar com a interação do usuário quando o aplicativo é aberto. Por exemplo, se uma nova mensagem de bate-papo for enviada por meio de uma notificação e o usuário a pressionar, convém abrir a conversa específica quando o aplicativo for aberto.
O pacote firebase-messaging
fornece duas maneiras de lidar com essa interação:
-
getInitialMessage()
: Se o aplicativo for aberto a partir de um estado encerrado, umFuture
contendo umaRemoteMessage
será retornado. Uma vez consumido, oRemoteMessage
será removido. -
onMessageOpenedApp
: umStream
que publica umaRemoteMessage
quando o aplicativo é aberto em segundo plano.
Recomenda-se que ambos os cenários sejam tratados para garantir uma UX suave 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 de 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 com o atributoname
da mensagem que deseja exibir.O payload 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";
O payload da mensagem ficaria assim:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_loc_key": "NOTIFICATION_MESSAGE" }, }
Ativar 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 por meio da API ou do compositor de notificações.
Para habilitar a exportação, primeiro siga as etapas descritas aqui e, em seguida, siga estas instruções:
Android
Você pode usar o seguinte código: dart 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 bundler como esbuild
por exemplo, para fazer o service worker funcionar. Consulte o aplicativo de exemplo para ver como fazer isso.
Depois de migrar para o SDK v9, você pode 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
.