A cell phone sitting on top of a table next to a cup of coffee.
Photo by @pawel_czerwinski on Unsplash

Introdução

Podemos entender Push Notifications como mensagens enviadas para uma ou várias aplicações, frequentemente exibidas como o próprio nome já diz em formato de notificações do sistema, elas permitem que os usuários estejam sempre atualizados com informações importantes e também chamar a atenção para utilizar certas funcionalidades.

Para a implementação é necessário uma plataforma que permita o envio e recebimento de mensagens, também chamado de "Mensageria", geralmente precisamos de uma implementação no lado do servidor e outra no cliente.

Confira abaixo uma representação de como funciona na prática:

Server Side Application Push Notification Provider send User Devices subscribe Client SDK Message Topics send message

Nesse caso podemos utilizar:

  1. Cloud Functions ou aplicação backend hospedada em um servidor
  2. Serviços que fornecem essa funcionalidade, como o Firebase Cloud Messaging
  3. Algum provedor externo, como Novu

O que é um tópico?

Em se tratando comunicação entre sistemas, um termo muito comum são os tópicos de mensagens, que pode ser entendido como um "canal", na qual as mensagens serão enviadas somente para este local específico.

Cada dispositivo ou sistema pode se inscrever em um tópico e com isso receber as mensagens desse canal, como por exemplo: um usuário em um total de 10, se inscreve em um tópico de "Notícias" de um aplicativo, nesse caso somente essa pessoa receberia essas mensagens como notificações.

Funcionamento do Firebase Cloud Messaging

Como dito anteriormente precisamos de um serviço que irá ser responsável por direcionar as mensagens para os interessados, vamos levar em consideração o esquema de Push Notifications.

Ao utilizar o FCM é necessário adicionar o SDK do Firebase no projeto e definir as configurações necessárias para que o app possa lidar com as notificações em segundo ou primeiro plano.

  • O primeiro passo a ser realizado é a inicialização dos métodos para "escuta" dos eventos e também a geração de um token para a instância do aplicativo.

Armazenamento do FCM token

Esse token é utilizado pelo Firebase no momento de envio das mensagens, o ideal é gerar o token ao inicializar o app e salvar no banco de dados atribuindo esse ID ao usuário, posteriormente na aplicação que será responsável por enviar as mensagens possa obter a lista de tokens para quem enviar.

Cloud Scheduler

Uma das maneiras de se trabalhar com o envio de mensagens (notificações) para os usuários é através de uma Cloud Function, nada mais é do que um código que será executado a partir de uma ação específica, e nesse caso pode ser qualquer coisa, como por exemplo:

  • Quando um usuário ganhar um novo seguidor, uma notificação poderá ser enviada para o aplicativo;
  • Quando um produto do carrinho de compras entrar em promoção;
  • Enviar uma notificação todos os dias;

As possibilidades são muitas, levando em consideração o último caso, podemos "desenhar" um fluxo simples, mas eficaz para nos dar a visão de como funcionaria esse esquema:

Cloud Function Firebase Cloud Messaging Runs the function code and send message to FCM Cloud Scheduler Publish a new message every day at 11:00 (UTC) 0 11 * * * deliver Push Notifications topic Triggered when a new message is published

Utilizamos algo chamado Cloud Scheduler para executar uma ação programada, no exemplo acima, todos os dias será publicada uma nova mensagem à um tópico específico, logo após temos nossa cloud function, que irá "ouvir" o evento e toda vez que uma nova mensagem for publicada nesse tópico, o código será executado e por fim enviará uma mensagem para os usuários através do Firebase Cloud Messaging.

Para entender mais sobre o padrão Pub/Sub (Publisher/Subscriber), confira meu outro artigo — Asynchronous programming and Rx-anything

Implementação

Para a implementação será utilizado:

  • Linguagem Python.
  • Serverless: Firebase (Cloud Messaging e Functions).
  • Flutter (Aplicação Client Side)

O primeiro passo é criar o projeto, para isso o Firebase providencia uma CLI (Command Line Interface) para facilitar o processo:

  1. Faça a instalação da CLI de acordo com seu sistema operacional, confira na documentação oficial.
  2. Rode o comando abaixo em algum local para que os arquivos iniciais do projeto sejam criados:
firebase init functions

Dado que o Python foi escolhido, a estrutura gerada será a seguinte:

myproject
+- .firebaserc    # Hidden file that helps you quickly switch between
|                 # projects with `firebase use`
|
+- firebase.json  # Describes properties for your project
|
+- functions/     # Directory containing all your functions code
      |
      +- main.py      # Main source file for your Cloud Functions code
      |
      +- requirements.txt  #  List of the project's modules and packages
      |
      +- venv/ # Directory where your dependencies are installed

Estrutura retirada da documentação oficial, confira os detalhes aqui.

Partindo para a escrita do código que será responsável por enviar as mensagem para o Firebase Cloud Messaging, temos o seguinte:

@https_fn.on_request()
def send_message_to_topic(req: https_fn.Request) -> https_fn.Response:
	topic = "app-general-messages"
	androidConfig = messaging.AndroidConfig( ... )

	message = messaging.Message(
			android=androidConfig,
			topic=topic,
		  notification=messaging.Notification(
			title="Notification Title",
			body="Notification Body"
		),
		data={"type": "notification"}
	)

	try:
		messaging.send(message)
		return https_fn.Response("Message to FCM was sent with success!")

    except Exception as e:
			return https_fn.Response("Message was not sent!", status=500)

Podemos observar o uso da notação @https_fn.on_request(), esse é um dos possíveis "triggers" ou acionadores disponibilizados pelo Firebase para que um método seja invocado, nesse caso será por meio de uma chamada HTTP ao endereço da function (que será gerado após o deploy).

Como citado anteriormente, existem inúmeros "acionadores" possíveis para a execução da função, um exemplo bastante útil seria para a implementação de um Scheduler:

@pubsub_fn.on_message_published(topic="pubsub-topic")
def on_message_published(event: pubsub_fn.CloudEvent[pubsub_fn.MessagePublishedData]) -> None:
  return ...

Ou seja, quando uma nova mensagem for publicada no tópico pubsub-topic, esse método seria invocado. Nos provedores de cloud geralmente existe a possibilidade de configurar essa programação e fazer o envio de uma mensagem para um tópico escolhido.

Após finalizadas as implementações do código, podemos realizar o deploy utilizando a CLI do Firebase:

firebase deploy --only functions

O código acima irá realizar o deploy e disponibilizar a cloud function diretamente na conta previamente definida, no início do processo.

Com o lado do servidor finalizado, podemos partir para a implementação do SDK no lado do client. Vamos utilizar como exemplo um app Flutter, também será necessário adicionar o FCM como dependência no projeto, podemos ter uma serviço que irá lidar com os processos de push notification:

abstract class NotificationService {
	static final _messagingService = FirebaseMessaging.instance;

	static Future<void> setUpNotifications() async {
		await _requestNotificationPermission();
		await _getToken();

		await _messagingService.setForegroundNotificationPresentationOptions(
			alert: true,
			badge: true,
			sound: true,
		);

		await _localNotification.initialize(
			NotificationConstants.settings,
			onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
		);

		await _subscribeToGeneralTopics();
		_setupMessageListeners();
	}

	static Future<void> _subscribeToGeneralTopics() async {
		await FirebaseMessaging.instance.subscribeToTopic(
			'general-messages',
		);
	}

	static void _setupMessageListeners() {
		FirebaseMessaging.onMessage.listen((message) {
		  _handleForegroundMessage(message);
		});

		FirebaseMessaging.onBackgroundMessage(_handleBackgroundMessage);
	}
}

Aqui é importante que seja realizada a inscrição do dispositivo (ou sessão) atual no tópico definido anteriormente, quando uma nova mensagem estiver disponível, o Firebase irá encaminhá-la para todos os dispositivos inscritos naquele tópico.

Após definir os métodos necessários para a inicialização da instância e registro do token, os métodos declarados em _setupMessageListeners irão ser invocados e exibirão a mensagem recebida como uma notificação do sistema.

Caso queira entender outros detalhes, sugiro conferir a documentação oficial, existem bons e completos exemplos por lá.

Alguns detalhes de implementação foram omitidos para tornar o fluxo de leitura dinâmico e direto, espero que esse artigo possa ter contribuído um pouco mais para seus conhecimentos técnicos e lhe inspirado a entender um pouco mais da jornada serverless, existem muito ainda a se explorar, bons códigos à você!! :)