Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Receba mensagens em um aplicativo Flutter

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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:

  1. Não deve ser uma função anônima.
  2. Deve ser uma função de nível superior (por exemplo, não um método de classe que requer inicialização).
  3. 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, um Future contendo uma RemoteMessage será retornado. Uma vez consumido, o RemoteMessage será removido.
  • onMessageOpenedApp : um Stream que publica uma RemoteMessage 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

  1. 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>
    
  2. Especifique as mensagens traduzidas no diretório values- language . Por exemplo, especifique mensagens em francês em resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Na carga útil do servidor, em vez de usar as chaves title , message e body , use title_loc_key e body_loc_key para sua mensagem localizada e defina-as com o atributo name da mensagem que deseja exibir.

    O payload da mensagem ficaria assim:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      },
    }
    

iOS

  1. Especifique suas mensagens de idioma padrão em Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Especifique as mensagens traduzidas no diretório language .lproj . Por exemplo, especifique mensagens em francês em fr.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 .