FCM Android - Relatório e Notificação Por Tópicos [Parte 2]

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /FCM Android - Relatório e Notificação Por Tópicos [Parte 2]

FCM Android - Relatório e Notificação Por Tópicos [Parte 2]

Vinícius Thiengo
(5336) (6)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloManual de DevOps: como obter agilidade, confiabilidade e segurança em organizações tecnológicas
CategoriaEngenharia de Software
Autor(es)Gene Kim, Jez Humble, John Willis, Patrick Debois
EditoraAlta Books
Edição
Ano2018
Páginas464
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Opa, blz?

Neste artigo continuamos com a série sobre o Firebase Cloud Messaging no Android. Dessa vez trabalhando com o envio de notificações push utilizando topics.

Com os tópicos FCM você, dependendo do domínio do problema, nem mesmo precisa gerenciar os tokens de instâncias em seu backend Web. Isso, pois com eles podemos apenas adicionar os códigos da API no aplicativo Android para que os usuários se cadastrem (ou se desvinculem) neles, permitindo assim que posteriormente utilizemos esses tópicos para envios de notificação.

Note que ainda vamos continuar com o projeto de domínio de problema de Blog, vamos realizar algumas atualizações nos códigos que já temos desde a parte um desta série sobre o FCM.

Antes de prosseguir, caso ainda não tenha visto o artigo e nem mesmo o vídeo da parte um, não deixe de primeiro estuda-la em: FCM Android - Domínio do Problema, Implementação e Testes Com Servidor de Aplicativo [Parte 1].

Aqui no conteúdo da parte dois, para acesso direto ao vídeo, vá a seção Vídeo com a implementação da atualização do projeto de exemplo.

Abaixo os tópicos que estaremos abordando:

Quando trabalhar com o envio de notificação push utilizando tópicos

Como já comentado no início no artigo: dependendo do domínio do problema que você estiver trabalhando, é melhor utilizar o envio de notificação por meio de tópicos do que gerenciar uma série de tokens de instâncias.

Ok, mas como saberei quando utilizar um ou o outro?

Não há uma série de domínios específicos, mas alguns indícios podem ser levados em conta:

  • As notificações serão entregues com classificações de categorias;
  • As notificações serão acionadas somente com a ação de uma lado da comunicação. Dashboard de administrador do sistema, por exemplo;
  • O usuário tem a opção de escolher temas que mais gostaria de ser informado sobre assim que houverem novidades.

Em nosso projeto de exemplo, apesar de estarmos utilizando o modo de "gerenciamento de token", ele melhor se encaixa no trabalho de notificação via tópico, mais precisamente: tópicos que indicam as categorias do aplicativo de Blog (Para iniciantes; Para intermediários; Para profissionais; Web e Android; Padrões).

Saiba que também é possível criar algoritmos de gerência de tópicos. Aqui mesmo no projeto de exemplo vamos criar um pequeno relatório sobre os tópicos (categorias) em uso.

Além de também ser possível gerenciar os tópicos, podemos trabalha-los juntos aos tokens de instâncias, alias, aqui no exemplo somente não vamos descarta os algoritmos de gerência de tokens, porque vamos utiliza-los juntos aos algoritmos de tópicos.

Verificando os tópicos de um token de instância

Trabalhando com tópicos, caso queira saber quais um determinado token de instância está vinculado, realize uma requisição a seguinte URL:

  • https://iid.googleapis.com/iid/info/TOKEN?details=true

Onde há TOKEN você coloca o token de instância que quer verificar. O details=true é para que a lista de tópicos também seja retornada.

A seguir um exemplo de requisição, com a URL anterior, utilizando códigos PHP:

...
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
'Authorization:key=' . $_SUA_FCM_KEY
]);
curl_setopt($ch, CURLOPT_URL, 'https://iid.googleapis.com/iid/info/' . $_TOKEN_DE_UMA_INSTANCIA . '?details=true');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$saida = curl_exec($ch);
...

 

Como saída temos um JSON similar ao seguinte:

{
"applicationVersion": "1",
"connectDate": "2017-05-03",
"attestStatus": "ROOTED",
"application": "br.com.thiengo.androidblogapp",
"scope": "*",
"authorizedEntity": "ID_PROJECT",
"rel": {
"topics": {
"categoria_1": {
"addDate": "2017-05-03"
},
"categoria_3": {
"addDate": "2017-05-03"
},
"categoria_2": {
"addDate": "2017-05-03"
},
"categoria_5": {
"addDate": "2017-05-03"
},
"categoria_4": {
"addDate": "2017-05-03"
}
}
},
"connectionType": "MOBILE",
"appSigner": "SHA_1_DO_PACKAGE_DO_PROJETO",
"platform": "ANDROID"
}

 

Em topics somente aparecem os tópicos aos quais o token em teste está vinculado. Vamos a explicação dos itens do JSON de retorno:

  • applicationVersion: versão do aplicativo;
  • connectDate: a data da última vez que a instância deste token, o aplicativo, foi aberta;
  • attestStatus: indica se o dispositivo onde se encontra a instância está ou não "rooted" (indicativo que vem nos possíveis valores: ROOTED, NOT_ROOTED, or UNKNOWN);
  • application: o package name do aplicativo, o mesmo que você pode encontrar no AndroidManifest.xml do projeto;
  • authorizedEntity: ID do projeto que tem autorização para enviar notificações ao token em teste;
  • rel: relacionamentos associados ao token em teste. Um deles é a lista de tópicos na qual o token está inscrito;
  • connectionType: o tipo de conexão utilizada pelo device que têm a instância do token. Pode retornar WIFI, MOBILE, OTHER ou simplesmente não retornar nada;
  • appSigner: o SHA1 de assinatura aplicado para o package do projeto. SHA1 local (android.debug) ou SHA1 de produção (Play Store);
  • platform: pode retornar ANDROID, IOS, ou CHROME para indicar a qual plataforma o token pertence.

Note que não abordei a chave scope, pois além de ela ter sido gerada junto a um valor que não indica muita coisa, *, não há nada sobre ela na documentação de tópicos do FCM.

Note que é possível que um erro seja retornado. O que mais requer ação de sua parte é o relacionado a "token inválido", ou seja, espera-se que seu código backend remova esse token da base de dados, pois ele não mais têm funcionalidade.

Para token inválido será retornada uma chave error com o valor "InvalidToken".

Adicionando uma relação token-tópico via código de servidor de aplicativo

É possível criar uma relação token-tópico direto do backend Web. Digo direto, pois é comum que o usuário escolha os tópicos aos quais ele quer se cadastrar, ou seja, é comum termos a relação token-tópico sendo criada no código da plataforma (Android, IOS, Chrome).

Para criar esse relacionamento, utilize a seguinte URL:

  • https://iid.googleapis.com/iid/v1/TOKEN/rel/topics/NOME_TOPICO

Em TOKEN coloque o token de alguma instância de seu aplicativo. Em NOME_TOPICO coloque o nome do tópico ao qual deseja vincular o token em TOKEN.

A seguir um código de exemplo, em PHP, sobre como realizar a requisição de criação de relacionamento token-tópico:

...
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
'Content-Length: 0',
'Authorization:key=' . $_SUA_FCM_KEY
]);
curl_setopt($ch, CURLOPT_URL,
'https://iid.googleapis.com/iid/v1/' . $_TOKEN_DE_UMA_INSTANCIA . '/rel/topics/' . $_NOME_TOPICO);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$saida = curl_exec($ch);
...

 

Como resultado, além dos possíveis erros, incluindo o já discutido "InvalidToken", em caso de sucesso será retornado um objeto JSON vazio.

Adicionando / removendo uma relação tokens-tópico via código de servidor de aplicativo

Similar ao código anterior, podemos criar uma relação direta entre um tópico e vários tokens de instâncias. Para isso utilize a URL a seguir:

  • https://iid.googleapis.com/iid/v1:batchAdd

Dessa vez não há "placeholders" na URL, pois os tokens e o tópico são enviados como dados POST em formato JSON. Segue um exemplo em código PHP:

...
$jsonData = new stdClass();
$jsonData->to = '/topics/categoria_1';
$jsonData->registration_tokens = [
'TOKEN_INSTANCIA_1',
'TOKEN_INSTANCIA_2'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
'Authorization:key=' . $_SUA_FCM_KEY
]);
curl_setopt($ch, CURLOPT_URL, 'https://iid.googleapis.com/iid/v1:batchAdd');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($jsonData));
$saida = curl_exec($ch);
...

 

Como resposta, similar ao que comentamos no primeiro artigo / vídeo, temos um feedback para cada token em registration_tokens. Neste caso, para trabalhar o feedback de cada token, as posições de envio e recebimento são as mesmas.

A seguir um exemplo de retorno, em JSON, dos servidores do FCM:

{
"results":[
{},
{"error":"INVALID_ARGUMENT"}
]
}

 

O resultado anterior indica que o primeiro token está ok e agora vinculado ao tópico "categoria_1". O segundo token em envio está com a formatação errada, logo, não houve o vinculo.

Para os erros possíveis temos:

  • NOT_FOUND: ou o token foi deletado ou o aplicativo foi removido do device do usuário;
  • INVALID_ARGUMENT: o token informado não é válido para o servidor de aplicativo que está realizando a requisição com sua chave FCM;
  • INTERNAL: os servidores FCM falharam devido a uma causa desconhecida;
  • TOO_MANY_TOPICS: o token / instância atual já está vinculado a um número excessivo de tópicos.

Detalhe para esse último possível problema, TOO_MANY_TOPICS. Isso, pois a documentação não indica um número máximo, somente que devemos investigar o porquê de uma instância estar vinculada a tantos tópicos. Ou seja, não diz muito para nós desenvolvedores sobre limites.

Nesta caso, tendo o problema TOO_MANY_TOPICS de forma recorrente, o que indico é que você comece a gerenciar os tópicos direto de sua base de dados, ou seja, não mais utilizará a interface de tópicos do FCM, voltará a utilizar somente os tokens de instâncias, pois a sua lógica de negócio, junto ao banco de dados, é que indicará quais os usuários deverão receber a mensagem atual do tópico em envio.

Para remover uma série de vínculos tokens-tópico, utilize o mesmo procedimento discutido anteriormente, porém com a URL a seguir:

  • https://iid.googleapis.com/iid/v1:batchRemove

Criando / removendo vinculo instância-tópico direto do código Android

Para criar um vinculo entre um token (instância) e tópico direto no código Android, somente realize uma requisição como à seguir:

...
FirebaseMessaging.getInstance().subscribeToTopic( "nome_topico" );
...

 

Para remover o vinculo:

...
FirebaseMessaging.getInstance().unsubscribeFromTopic( "nome_topico" );
...

 

Em meus testes não houve delay de registro ou remoção de registro em tópico.

Sintaxe de nome e chaves de envio

Quando for definir o nome de um tópico, saiba que ele somente pode ter: letras (maiúsculas, minúsculas), números, -, _, ., ~, %. Ou seja, respeitar a seguinte expressão regular: [a-zA-Z0-9-_.~%]+.

Para envio, as regras de negócio de chaves de objeto de notificação continuam sendo as mesmas discutidas na parte um da série, mas ao invés de utilizarmos os valores de tokens na chave to ou na chave registration_ids temos:

...
$notification->to = '/topics/nome_do_topico';
...

 

O código acima permitirá o envio da push notification para todas as instâncias que estão vinculadas a "nome_do_topico".

É possível também criar um envio condicional como:

...
$notification->to = "'categoria_1' in topics && ('categoria_2' in topics || 'categoria_3' in topics)";
...

 

Somente as instâncias que respeitam alguma das inscrições abaixo é que receberam a notificação:

  • Cadastrada nos tópicos "categoria_1" e "categoria_2";
  • Cadastrada nos tópicos "categoria_1" e "categoria_3";
  • Cadastrada nos tópicos "categoria_1", "categoria_2" e"categoria_3".

Somente podemos utilizar como operador o && (AND) e o || (OR), além dos parênteses, alias a resolução condicional inicia nos parênteses para depois ir para as operações a esquerda deles.

Para saber ainda mais sobre gerenciamentos de tokens e tópicos, acesse a página a seguir: Instance ID - Server Reference.

Assim podemos ir as atualizações do projeto de Blog da série.

Atualização do projeto Android

Em todo o projeto, incluindo o lado Web, teremos, além do cadastro / remoção de vinculo a algum tópico, um pequeno relatório para cada uma das categorias do sistema. Lembrando que aqui as categorias serão também os tópicos FCM.

As atualizações do lado Android serão somente para mantermos o controle de cadastro e remoção de vinculo a tópico. Nosso objetivo é permitir que a tela de configuração de tópico sempre esteja sendo apresentada corretamente e que rapidamente o usuário possa atualiza-la, digo, os status dos tópicos em relação a instância de aplicativo dele.

Ao final dessas modificações teremos uma tela similar a seguinte:

Como informado, nossos tópicos serão as categorias de nosso aplicativo de Blog. Adotaremos nomes aceitáveis, como: categoria_1, categoria_2, categoria_3, categoria_4 e categoria_5.

Note que é importante que seus aplicativos que trabalham com notificações deem aos usuários a oportunidade de, nas configurações do app, definir como eles querem que notificações sejam trabalhadas nos devices deles.

Adicionando métodos de status de tópico em SPUtil

Primeiro vamos aos métodos de status de tópico, isso, pois a API Android do FCM, até o momento da construção deste artigo, não oferecia um meio trivial de conseguir essa verificação: se uma instância já estava vinculada ou não a um determinado tópico.

Para isso, em SPUtil, adicione os seguintes métodos:

public class SPUtil {
...

public static void saveStatusCategoria(Context context, String key, boolean status){
SharedPreferences sp = context.getSharedPreferences(PREF, MODE);
sp.edit().putBoolean(key, status).apply();
}

public static boolean statusCategoria(Context context, String key){
SharedPreferences sp = context.getSharedPreferences(PREF, MODE);
return sp.getBoolean(key, true);
}
}

 

Note que nem mesmo de chave (key) local na classe precisaremos, pois os nomes categoria_1, ... a categoria_n serão utilizados.

Utilizaremos esses métodos na classe apresentadora que controlará a lógica de negócio para salvar ou remover uma instância de um vinculo com algum tópico.

Criando o algoritmo de status de primeira abertura de aplicativo

Para que o usuário inicie suas atividades com o aplicativo já estando vinculado a todos os tópicos, pois esse é o início padrão em nosso domínio do problema. Para que isso aconteça precisamos de um algoritmo que faça essa vinculação com todos os tópicos somente na primeira abertura do app.

Será necessário algum indicador de que "aquela abertura do aplicativo" é a primeira. Vamos trabalhar a persistência e verificação deste indicador também na classe SPUtil. Adicione os métodos e chave a seguir:

public class SPUtil {
...
private static final String PRIMEIRA_ABERTURA_KEY = "primeira_abertura";
...

public static void saveStatusPrimeiraAbertura(Context context, boolean status){
SharedPreferences sp = context.getSharedPreferences(PREF, MODE);
sp.edit().putBoolean(PRIMEIRA_ABERTURA_KEY, status).apply();
}

public static boolean statusPrimeiraAbertura(Context context){
SharedPreferences sp = context.getSharedPreferences(PREF, MODE);
return sp.getBoolean(PRIMEIRA_ABERTURA_KEY, true);
}
}

 

Esses métodos também serão utilizados na nova classe apresentadora que estaremos construindo para gerência de inscrição / remoção de vinculo em tópico.

Criando a classe PresenterNotificacao

No pacote /presenter adicione a classe PresenterNotificacao:

public class PresenterNotificacao {
private Context context;

public PresenterNotificacao( Context c ){
context = c;
}
}

 

Como em outras classes apresentadoras, precisaremos sim de um objetos de contexto para poder prosseguir com algumas lógicas, por exemplo, as que utilizam a classe SPUtil, onde sempre temos de entrar, em qualquer método, com um contexto para acesso ao SharedPreferences.

Agora precisamos de um método que permita a alteração de status da instância do usuário em relação a algum tópico. Já lhe adianto que estaremos trabalhando, na atividade de configuração de status em tópico, com CheckBox e com o listener de mudança de valor nesse tipo de View, mais precisamente implementando a Interface OnCheckedChangeListener.

Assim, podemos criar um método que tenha ao menos o mesmo nome do método de sobrescrita obrigatória de OnCheckedChangeListener. Em PresenterNotificacao adicione o seguinte método:

...
private void onCheckedChanged(String categoria, boolean status) {
if( status ){
FirebaseMessaging.getInstance().subscribeToTopic( categoria );
}
else {
FirebaseMessaging.getInstance().unsubscribeFromTopic( categoria );
}
SPUtil.saveStatusCategoria(context, categoria, status);
}
...

 

Note a última linha do método anterior:

...
SPUtil.saveStatusCategoria(context, categoria, status);
...

Sempre estamos salvando o status atual da instância de nosso aplicativo, no device do usuário, em relação ao tópico informado no parâmetro categoria.

Ok, mas e se o usuário acessar o aplicativo por outro device? Dessa forma não temos acesso ao status do tópico que está no SharedPreferences de outro aparelho, ou temos?

Você está certo, mas a regra de negócio de nosso aplicativo para o trabalho com tópico é: utilizando o vinculo por instância e não por login. Ou seja, caso o usuário tenha mais de um token / aparelho vinculado ao login dele, ele poderá definir para cada um quais notificações devem ou não ser entregues a cada um deles.

Agora precisamos de um método que retorne nomes aceitáveis para as categorias de nosso aplicativo, digo, nomes que não tenham acentos e outros caracteres não aceitos como nomes de tópicos FCM.

Ainda em PresenterNotificacao adicione o método getCategoria():

...
private String getCategoria( int id ){
switch( id ){
case R.id.categoria_1:
return "categoria_1";
case R.id.categoria_2:
return "categoria_2";
case R.id.categoria_3:
return "categoria_3";
case R.id.categoria_4:
return "categoria_4";
default:
return "categoria_5";
}
}
...

 

O ID informado como parâmetro é algum dos vinculados aos CheckBox que teremos na atividade de configuração de vinculo a tópico. São eles: R.id.categoria_1R.id.categoria_2R.id.categoria_3R.id.categoria_4R.id.categoria_5.

Algo a se notar é que o método onCheckedChanged() que criamos ainda não responde exatamente a versão exigida pela implementação da Interface OnCheckedChangeListener, versão onde no lugar de uma String no primeiro parâmetro temos um CompoundButton.

Assim, vamos a criação dessa sobrecarga ainda em PresenterNotificacao, pois ela é que será invocada na atividade de tópicos (agora você entendeu o porque do primeiro onCheckedChanged() ser private):

...
public void onCheckedChanged(CompoundButton cb, boolean b) {
String categoria = getCategoria( cb.getId() );
onCheckedChanged(categoria, b);
}
...

 

Assim podemos criar o método com a lógica de "primeira abertura de aplicativo". Note que esse método na verdade é referente a primeira abertura depois de o usuário ter realizado o login, ou seja, estaremos invocando ele na PostsActivity e não na LoginActivity.

Segue atualização de PresenterNotificacao, adição do método configPrimeiraAbertura():

...
public void configPrimeiraAbertura() {
if( !SPUtil.statusPrimeiraAbertura(context) ){
return;
}

onCheckedChanged( getCategoria(R.id.categoria_1), true);
onCheckedChanged( getCategoria(R.id.categoria_2), true);
onCheckedChanged( getCategoria(R.id.categoria_3), true);
onCheckedChanged( getCategoria(R.id.categoria_4), true);
onCheckedChanged( getCategoria(R.id.categoria_5), true);

SPUtil.saveStatusPrimeiraAbertura(context, false);
}
...

 

Veja que o único condicional do método é na verdade a aplicação do padrão de implementação Cláusula de Guarda, ou seja, se uma condição mínima não for encontrada, aplicativo ainda não aberto, não continuamos com a execução do script. Mesmo sabendo que o código depois do condicional somente será acionado uma única vez depois do login do usuário.

Antes de prosseguir para a atividade de tópicos, note que para configurarmos os valores e vínculos iniciais de qualquer CheckBox dessa atividade, nós teremos de invocar uma série de métodos. Assim é preferível colocar esses códigos de configuração também em PresenterNotificacao.

Ainda na classe desta seção, adicione o seguinte método:

...
public void setCheckBoxConf( int id ){
Activity a = (Activity) context;
String categoria = getCategoria( id );
CheckBox cb = (CheckBox) a.findViewById( id );

cb.setOnCheckedChangeListener( (CompoundButton.OnCheckedChangeListener) context );
cb.setChecked( SPUtil.statusCategoria(context, categoria) );
}
...

 

Veja que para podermos acessar findViewById() precisamos do cast em nosso context, cast para uma Activity. Esse comportamento pode ser mantido sem verificação de tipo, pois sabemos que a invocação deste método somente ocorrerá dentro da atividade de tópicos.

Importante ressaltar que setCheckBoxConf() somente será invocado com os IDs de CheckBox.

Adicionando a atividade de configuração de tópico

Agora a nova atividade do projeto Android, a responsável por permitir que o usuário consiga definir quais os tópicos (categorias) ele gostaria de ser informado sobre quando houvesse um novo conteúdo.

Vamos iniciar com os layouts dessa atividade. Em /res/layout adicione o XML content_notificacao.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.androidblogapp.view.NotificacaoActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Defina a seguir como será sua configuração de notificação. Para as categorias não selecionadas você somente terá acesso a novos conteúdos assim que entrar no aplicativo." />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="Somente selecione as opções desejadas para que a configuração já seja realizada." />

<CheckBox
android:id="@+id/categoria_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Para iniciantes" />

<CheckBox
android:id="@+id/categoria_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Para intermediários" />

<CheckBox
android:id="@+id/categoria_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Para profissionais" />

<CheckBox
android:id="@+id/categoria_4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Web e Android" />

<CheckBox
android:id="@+id/categoria_5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Padrões" />
</LinearLayout>

 

Simples, certo? Vamos ao diagrama do layout anterior:

Assim podemos ir ao layout que inclui o layout anterior, segue XML de /res/layout/activity_notificacao.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context="br.com.thiengo.androidblogapp.view.NotificacaoActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_notificacao" />

</android.support.design.widget.CoordinatorLayout>

 

Agora o diagrama de activity_notificacao.xml:

Assim o código Java da nova atividade, NotificacaoActivity:

public class NotificacaoActivity extends AppCompatActivity
implements CompoundButton.OnCheckedChangeListener {

private PresenterNotificacao presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notificacao);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbarFontFamily( toolbar );
if( getSupportActionBar() != null ){
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}

presenter = new PresenterNotificacao(this);
presenter.setCheckBoxConf( R.id.categoria_1 );
presenter.setCheckBoxConf( R.id.categoria_2 );
presenter.setCheckBoxConf( R.id.categoria_3 );
presenter.setCheckBoxConf( R.id.categoria_4 );
presenter.setCheckBoxConf( R.id.categoria_5 );
}

private void toolbarFontFamily(Toolbar toolbar ){
TextView tv = (TextView) toolbar.getChildAt(0);
Typeface font = Typeface.createFromAsset( getAssets(), "Timmana.ttf" );
tv.setTypeface( font );
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

@Override
public void onCheckedChanged(CompoundButton cb, boolean b) {
presenter.onCheckedChanged(cb, b);
}
}

 

Diferente das outras duas classes apresentadoras, PresenterLogin e PresenterPosts, aqui não precisamos de uma instância estática de PresenterNotificacao.

Os dois códigos condicionais de NotificacaoActivity são simples o suficiente para que não seja necessário o envio deles para a classe PresenterNotificacao.

Note que o método toolbarFontFamily() também está sendo utilizado em PostsActivity. Fica a seu critério encapsular esse método em uma classe utilitária ou não.

Por fim, o que ainda nos falta de atualização para o correto funcionamento de NotificacaoActivity é a adição da tag desta atividade no AndroidManifest.xml do projeto:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="br.com.thiengo.androidblogapp">
...

<application
android:name=".presenter.App"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...

<activity
android:name=".view.NotificacaoActivity"
android:label="@string/activity_notification"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" />
...
</application>
</manifest>

 

No arquivo /res/values/strings.xml adicione o título da NotificacaoActivity:

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<string name="activity_notification">Configurações de notificação</string>
</resources>

 

Assim podemos partir para as últimas atualizações no projeto Android, atualizações em PostsActivity.

Atualizando a atividade de posts

Em PostsActivity primeiro precisamos adicionar o código que permite a abertura da atividade de notificação. Logo, em /res/menu/activity_posts_drawer.xml, adicione o seguinte trecho em destaque:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
...

<item android:title="Configurações">
<menu>
...

<item
android:id="@+id/nav_conf_notif"
android:icon="@drawable/ic_notification"
android:title="Notificação" />
</menu>
</item>
</menu>

 

Agora em PostsActivity, mais precisamente no método onNavigationItemSelected(), adicione o trecho em destaque:

...
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();

if ( id == R.id.nav_conf_notif ) {
Intent intent = new Intent(this, NotificacaoActivity.class);
startActivity(intent);
return false;
}

DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
...

 

Para finalizar as atualizações nesta atividade, em onCreate() devemos invocar o método de: verificação de primeira abertura e vinculo de tópicos a instância do usuário.

Segue atualização:

...
protected void onCreate(Bundle savedInstanceState) {
...

PresenterNotificacao presenterNotificacao = new PresenterNotificacao(this);
presenterNotificacao.configPrimeiraAbertura();
}
...

 

Assim já temos todos os códigos necessários no lado Android para o correto gerenciamento de status de instância em tópico. Agora precisamos das atualizações em nosso servidor de aplicativo, backend Web.

Atualização do projeto Web

Para a parte Web nossa atualização será para que seja possível a visualização de um relatório sobre percentagem de cadastros em categorias (os tópicos) e também para que seja possível o envio de notificação utilizando a categoria do novo post criado.

Não teremos novas tabelas na base de dados, mas trabalharemos também um novo código nessa camada.

Atualizando a classe Categoria

Nossa primeira tarefa aqui é criar um algoritmo, método, que permita acesso aos nomes das categorias como nomes válidos para tópicos.

Recapitulando dos nomes já utilizados no código Android: categoria_1categoria_2categoria_3categoria_4categoria_5.

Na classe Categoria em /domain adicione o seguinte novo método:

...
public function getTopic()
{
return '/topics/categoria_' . $this->id;
}
...

 

Veja que já retornamos o termo completo para uso em um objeto de notificação, junto ao prefixo /topics/, e não somente o nome válido do tópico.

Como em nosso caso sabemos que os IDs de categorias correspondem fielmente os sufixos, números, de nomes de tópicos, podemos seguramente manter o uso de $this->id na lógica de topic name.

Ainda na classe Categoria temos de adicionar algumas entidades (métodos e atributos), pois o relatório que será colocado no dashboard de administrador do sistema terá a apresentação dos números em porcentagem.

Para isso, ainda nesta classe adicione os seguintes códigos em destaque:

class Categoria
{
...
public $count = 0;
public $percent = 0;
...

public function calcPercent( $totalTokens )
{
if( $totalTokens == 0 ){
return;
}
$this->percent = ($this->count / $totalTokens) * 100;
}

public function getPercentAsString()
{
return sprintf('%.1f', $this->percent).'%';
}
}

 

Para evitarmos divisão por zero, temos novamente o padrão Cláusula de Guarda sendo utilizado, agora no método calcPercent().

Algoritmo de obtenção de total de tokens registrados

Lembra da variável $totalTokens do método calcPercent() da classe Categoria? Essa variável representa a quantidade de tokens cadastrados em nossa base de dados.

Aqui vamos atualizar tanto a classe CgdUser como a classe AplUser para permite a obtenção deste número em qualquer parte do projeto.

Primeiro o novo método de CgdUser:

...
public function getTotalTokens()
{
$query = <<<SQL
SELECT
COUNT(*)
FROM
ba_token
SQL;
$database = (new Database($this))->getConn();
$statement = $database->prepare($query);

$statement->execute();
$database = null;
return $statement->fetchColumn(0);
}
...

 

Não precisamos trabalhar com JOIN para evitar tokens de mesmos usuários, pois como explicado nos códigos Android: estamos com foco em instâncias, mesmo quando sendo de um mesmo usuário.

Assim vamos a atualização na camada de lógica, segue novo método na classe AplUser:

...
public function getTotalTokens()
{
return $this->cgdUser->getTotalTokens();
}
...

 

Com isso podemos ir as atualizações da classe de gerencia de notificação.

Atualizando o algoritmo de envio de notificação em AplNotificacao

Na classe AplNotificacao, mais precisamente no método sendNotificacaoPush(), não mais precisamos do trabalho detalhado utilizando tokens de instâncias, pois agora somente utilizaremos o nome de tópico para envio.

Assim, neste método, coloque os seguintes novos códigos em destaque:

...
public function sendNotificacaoPush( Post $post ){
$notification = $this->getNotificacaoObj( $post );
$notification->to = $post->categoria->getTopic();

$curl = $this->getCurlObj( $notification );
$this->trabalhandoRequisicaoFCM( $curl );
}
...

 

Note que não mais precisamos de um array de objetos do tipo User entrando como argumento em trabalhandoRequisicaoFCM(). Também vamos a essa modificação de método.

Ainda em AplNotificacao, agora no método trabalhandoRequisicaoFCM(), coloque o seguinte novo código (em destaque):

private function trabalhandoRequisicaoFCM( $curl ){
$saida = curl_exec( $curl );
curl_close($curl);

$body = json_decode($saida);
return isset($body->message_id);
}

 

Nossa verificação de "sucesso de requisição" agora é somente conferindo se há um ID de mensagem no código JSON de retorno.

E o controle de remoção de tokens de instâncias? Até porque no Android nós ainda continuamos obtendo esses tokens e enviando-os ao backend Web.

Sim, teremos essa código de gerencia de validade de token, mas em outro método, o de relatório. Não mais em trabalhandoRequisicaoFCM(), mesmo porque a invocação de envio de notificação utilizando tópico não retorna os detalhes de cada token relacionado com o tópico informado.

Com apenas essas atualizações em sendNotificacaoPush() e em trabalhandoRequisicaoFCM() já conseguimos realizar envios de notificações push ao criarmos novos posts.

Criando o algoritmo de relatório de tópico (categoria) em AplNotificacao

Assim, antes de realizarmos baterias de testes, podemos prosseguir para a criação dos algoritmos de obtenção e apresentação de relatório de tópicos.

Vamos iniciar pelos códigos a serem adicionados em AplNotificacao.

Sabemos que a URL de obtenção de relatório de token é diferente da URL utilizada para envio de notificação push. Ela não precisa de envio em modo POST.

Logo, vamos criar um método que inicialize um objeto cUrl com as configurações corretas. Adicione a AplNotificacao o seguinte método:

...
private function getCurlObjReport( $userToken ){
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
'Authorization:key=' . Constante::FCM_KEY
]);
curl_setopt($curl, CURLOPT_URL, 'https://iid.googleapis.com/iid/info/'.$userToken.'?details=true');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

return $curl;
}
...

 

Agora precisamos de um método que tenha acesso a todos os tokens de instâncias em nossa base de dados e então utilize-as para as invocações remotas aos servidores FCM.

A seguir, ainda em AplNotificacao, adicione o método retrieveCategoriaRelatorio()

...
public function retrieveCategoriaRelatorio( $categorias ){
/*
* REUTILIZANDO O MÉTODO getUsersTokens() PARA A
* OBTENÇÃO DE TODOS OS TOKENS JÁ SALVOS. POR ISSO
* TAMBÉM MANTEMOS A LÓGICA DE NEGÓCIO UTILIZANDO
* UM while().
* */
$startUser = 0;
$users = $this->aplUser->getUsersTokens( $startUser );

while( count($users) > 0){

foreach( $users as $user ){
$curl = $this->getCurlObjReport( $user->token );
$resultado = curl_exec( $curl );
curl_close($curl);

$resultado = json_decode($resultado);

/*
* TRABALHANDO O RESULTADO RETORNADO DOS SERVIDORES
* DO FCM PARA A REQUISIÇÃO INDIVIDUAL DE RELATÓRIO DE
* CADA TOKEN, CASO NÃO HAJA ERRO TRABALHAMOS A
* CONTAGEM NAS CATEGORIAS PRESENTES NO RESULTADO DO
* TOKEN. CASO TENHA ERRO E ELE SEJA DE TOKEN INVÁLIDO,
* APENAS CONTINUAMOS O TRABALHO COM OS ALGORITMOS DE
* REMOÇÃO DE TOKEN DA BASE DE DADOS.
* */
if( empty($resultado->error) ){
$topics = $resultado->rel->topics;

for( $i = 0; $i < count($categorias); $i++ ){
if( property_exists($topics, "categoria_" . $categorias[$i]->id) ){
$categorias[$i]->count++;
}
}
}
else if( strcasecmp($resultado->error, 'InvalidToken') == 0 ){
$this->aplUser->deleteToken( $user );
}
}

/*
* ATUALIZANDO O ARRAY DE USUÁRIOS COM TOKENS QUE DEVEM
* SER AINDA TESTADOS.
* */
$startUser += Constante::MAX_TOKENS;
$users = $this->aplUser->getUsersTokens( $startUser );
}

/*
* INVOCANDO O MÉTODO DE CÁLCULO DAS PORCENTAGENS
* DE CADA CATEGORIA EM ARRAY.
* */
$this->calcCategoriaRelatorioPercent( $categorias );
}
...

 

O algoritmo de acesso a tokens é muito similar ao o que utilizávamos no método trabalhandoRequisicaoFCM(), reaproveitamos os métodos encontrados anteriormente lá, principalmente porque ainda temos o controle de remoção de tokens inválidos.

O método calcCategoriaRelatorioPercent() discutiremos a pouco ainda nesta seção, ele é bem simples.

Provavelmente você deve ter notado que há uma requisição remota para cada token cadastrado no banco de dados, algo ineficiente em termos de requisições síncronas, requisições que aqui estamos trabalhando junto ao código Ajax no frontend do dashboard de administrador do sistema.

Realmente essa não é a melhor maneira de trabalhar relatório de tokens FCM no código PHP, mas em nossos testes o algoritmo apresentado nos atende muito bem, pois temos poucos tokens.

Caso você queira utilizar o mesmo projeto, mas em produção. Neste caso recomendo que estude a possibilidade de realização de relatório de tópico FCM de forma assíncrona, utilizando para isso um servidor Redis, por exemplo.

Digo isso, pois no PHP não conseguimos criar Thread como no Java. Com o Redis você conseguirá manter a execução no backend e assim não travar o dashboard do administrador. Com a lógica de negócio correta será possível, ao final da construção do relatório, informar ao usuário sobre o término desse.

Assim podemos ir a apresentação do último novo método em AplNotificacaocalcCategoriaRelatorioPercent():

...
private function calcCategoriaRelatorioPercent( $categorias ){
$totalTokens = $this->aplUser->getTotalTokens();

foreach( $categorias as $categoria){
$categoria->calcPercent( $totalTokens );
}
}
...

 

Como informado anteriormente: simples. Apenas um loop para permitir a invocação do método calcPercent() em cada objeto Categoria no array $categorias.

Atualizando a classe AplPost

Na classe AplPost vamos somente adicionar o método que vai conter a lógica de negócio para a correta invocação de relatório, isso por parte do arquivo controlador de trabalho com objetos do tipo Post.

Em AplPost adicione o seguinte método:

...
public function getCategoriasRelatorio()
{
$categorias = $this->getCategorias();

$apl = new AplNotificacao();
$apl->retrieveCategoriaRelatorio( $categorias );

return $categorias;
}
...

 

O método getCategorias() retorna um array de objetos do tipo Categoria, assim as alterações em cada objeto desse array em retrieveCategoriaRelatorio() já são refletidas nos objetos do array $categoria fora de retrieveCategoriaRelatorio().

Atualizando o arquivo controlador CtrlPost

Aqui vamos a uma pequena atualização no arquivo /ctrl/CtrlPost.php. Adicione ao final dele o código a seguir:

...
else if( strcasecmp( $dados['metodo'], 'relatorio-categorias' ) == 0 ){
$apl = new AplUser();
$instalacoes = $apl->getTotalTokens();

$apl = new AplPost();
$categorias = $apl->getCategoriasRelatorio();

require_once('../view/relatorio/categorias.php');
echo json_encode( array('html'=>$html) );
}

 

Assim podemos ir aos códigos frontend atualizados e adicionados, incluindo o HTML, em categorias.php.

Adicionando o HTML de visualização de relatório

Primeiro vamos ao código que é carregado em /ctrl/CtrlPost.php, o /view/relatorio/categorias.php. Note que você terá de criar o diretório /relatorio em /view para prosseguir:

<?php
foreach( $categorias as $categoria ){
$html .= <<<HTML
<div class="relatorio">
<label>{$categoria->rotulo}:</label>
{$categoria->getPercentAsString()}
</div>
HTML;
}

$html = <<<HTML
<form>
<h3>Total instalações: {$instalacoes}</h3>
<br><br>

{$html}
</form>
HTML;

 

No código anterior estamos trabalhando com variável heredoc, $html, para facilitar a leitura do código HTML mesmo quando dentro de um arquivo PHP.

Assim podemos ir a atualização de /view/menu.php para adicionarmos a opção de solicitação de relatório de tópicos (categorias):

<?php
$menu = <<<HTML
<ul class="menu">
<li>
<a href="#CtrlPost|form-criar-post" title="Criar post" class="checked">
Criar post
</a>
</li>
<li>
<a href="#CtrlPost|form-atualizar-post" title="Atualizar post">
Atualizar post
</a>
</li>
<li>
<a href="#CtrlPost|relatorio-categorias" title="Relatório categorias">
Relatório categorias
</a>
</li>
<li>
<a href="#CtrlAdmin|form-atualizar-email-login" title="Atualizar email login">
Atualizar email login
</a>
</li>
<li>
<a href="#CtrlAdmin|form-atualizar-password-login" title="Atualizar senha login">
Atualizar senha login
</a>
</li>
<li>
<a href="#CtrlAdmin|sair" class="sair" title="Sair">
Sair
</a>
</li>
</ul>
HTML;

 

Com os HTMLs anteriores e o CSS que ainda apresentaremos, teremos um conteúdo no dashboard de administrador como o a seguir:

Agora vamos ao trecho final da atualização frontend, trecho CSS.

Atualizando arquivo CSS

A atualização CSS é simples, no arquivo /view/css/blog-android-app.css adicione os trechos em destaque:

...
form h3,
form label {
font-family: 'Timmana', sans-serif;
}
...

form .relatorio {
margin-bottom: 20px;
}

 

Assim podemos ir aos testes.

Testes e resultados

Primeiro, caso tenha já o aplicativo instalado em seu device / emulador de testes, então remova ele e realize todo o processo de instalação e login novamente.

Logo depois, abra o aplicativo e acesse a atividade de configuração de notificação. Desmarque a primeira categoria:

Agora feche o aplicativo e crie um novo post no dashboard de administrador, um post qualquer com a categoria sendo a primeira, "Para iniciantes". Verá que nenhuma notificação será entregue, mas caso o aplicativo seja aberto o novo post estará lá.

Agora vamos criar um post para qualquer categoria que não seja a primeira, como a seguir:

Com o aplicativo fechado e clicando em "Criar post", temos no Android:

Agora, no dashboard de administrador, clique em "Relatório categorias". Terá o relatório de categorias similar ao a seguir:

Em meu caso tenho dois usuários, tokens, em teste, por isso tive 50% onde deveria ser 0% e 100% onde deveria ser 50%, isso caso houvesse somente um token.

Assim terminamos essa segunda parte da série sobre o FCM. Não deixe de comentar suas dúvidas ou sugestões e também não deixe de se cadastrar na lista de emails do Blog (logo ao lado) para receber o conteúdo exclusivo e em primeira mão.

Inscreva-se também no canal do Blog no YouTube: Thiengo [Calopsita].

Vídeo com a implementação da atualização do projeto de exemplo

A seguir o vídeo com o código de atualização do projeto Android e Web com notificação push FCM:

Para acesso aos conteúdos completos do projeto de Blog, incluindo o backend Web, entre nos GitHub a seguir:

Conclusão

Firebase Cloud Messaging é algo que devemos falar sobre em mais de um artigo / vídeo, pois essa é uma API robusta que nos permite construir algoritmos de notificação push de maneira precisa ao domínio do problema que estamos trabalhando.

Apesar de já utilizando o gerenciamento de tokens para envio de push message em nosso software de blog, observamos, junto a API de tópicos do FCM, que era melhor permitir que os usuários escolhessem quais tipos de conteúdo poderiam ser sinalizados em forma de notificação.

Com isso, utilizando a API de tópicos para representar as categorias do aplicativo, conseguimos facilmente adicionar uma interface de configuração de notificação e ainda mantermos o gerenciamento de tokens no servidor de aplicativo.

Com a API de tópicos também podemos gerenciar, no backend Web, os relatórios, registros e remoções de vínculos entre tópicos e tokens de instâncias.

Note que permitir, dentro do app, como será o tratamento de notificação push é algo recomendo pelo Google, isso para que o usuário não tenha de ir as configurações do aparelho para conseguir esse tipo de controle.

Prosseguiremos com a série sobre o FCM. Não se esqueça de se inscrever na lista de emails do Blog para ser informado em primeira mão sobre novos conteúdos. Comente também o que achou.

Abraço.

Fontes

Enviar mensagens de tópico do servidor

Instance ID - Server Reference

Firebase Cloud Messaging Protocolo HTTP

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Estudando Android - Lista de Conteúdos do BlogEstudando Android - Lista de Conteúdos do BlogAndroid
API de Endereços Para Pré-Cadastro em APPs Android - Parte 1API de Endereços Para Pré-Cadastro em APPs Android - Parte 1Android
Como Implementar o Account Kit Login em Seu Aplicativo AndroidComo Implementar o Account Kit Login em Seu Aplicativo AndroidAndroid
FCM Android - Domínio do Problema, Implementação e Testes Com Servidor de Aplicativo [Parte 1]FCM Android - Domínio do Problema, Implementação e Testes Com Servidor de Aplicativo [Parte 1]Android

Compartilhar

Comentários Facebook

Comentários Blog (6)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
04/10/2017
Olá amigo Thiengo, eu consigo enviar notificações através de um código PHP e eu vi que no seu artigo você menciona o relatório de notificações enviadas, porém estou com dúvidas nesta parte, seria nesta url?
https://iid.googleapis.com/iid/info/TOKEN?details=true

este token seria o que exatamente?
Responder
Vinícius Thiengo (0) (0)
04/10/2017
Marcos, tudo bem?

Na verdade o relatório de notificações enviadas seria uma outra parte do dashboard Web do projeto.

Para isso teríamos de construir algumas outras tabelas no banco de dados para poder gravar cada dado de retorno das notificações já enviadas pelo Web app.

Depois seria somente formata-los e apresenta-los no dashboard de administrador, ou seja, não há link que nos forneça isso para todas as notificações enviadas.

O que você pode fazer é utilizar o Firebase Analytics para ter dados também sobre o uso no aplicativo Android, inclusive sobre notificações push. Mais sobre no link a seguir: https://firebase.google.com/docs/analytics/android/start/

Abraço.
Responder
05/10/2017
O que eu não estou conseguindo é justamente pegar este retorno, mesmo que seja individual pois organizaria isso em uma base.
Gostaria de saber quando a notificação foi entregue e quando foi aberta.
Responder
Vinícius Thiengo (0) (0)
05/10/2017
Marcos, o retorno de envio realizado você se tem assim que envia a notificação aos servidores do Google.

O retorno de envio realizado com sucesso, ou seja, de que a notificação chegou ao device do usuário e o retorno sobre a abertura da notificação, esses você terá de construir os algoritmos somente para isso, digo, algoritmos principalmente no lado Android.

Com uma API de comunicação externa, partindo do Android, é possível enviar essas dois feedbacks ao backend Web para melhorar ainda mais os relatórios.

Uma que recomendo e também é citada na documentação do Android é a Retrofit:

- http://square.github.io/retrofit/
- https://www.thiengo.com.br/library-retrofit-2-no-android

O fluxo seria o seguinte:

1 - Criar mensagem de notificação no backend Web;

2 - Selecionar destinatários;

3 - Enviar mensagem de notificação, junto aos destinatários, para os servidores de notificação do Google;

4 - Obter / aguardar o feedback dos servidores do google, feedback síncrono, quanto ao resultado do envio para cada destinatário;

5 - Assim que a notificação for entregue ao device / app, utilizar uma API de comunicação remota, Retrofit, por exemplo, para enviar uma fala ao backend, junto ao ID / token do usuário, para seja possível saber que aquele determinado usuário já recebeu a notificação com sucesso;

6 - Quando o usuário acionar a notificação, a API de comunicação remota deve ser ativada novamente agora enviando o ID / Tone do usuário junto ao uma flag informando que ele abriu a notificação.

Todos os dados deverão colocados em sua base de dados para posteriores relatórios: gerais e individuais, por usuário.

Marcos, siga nessa linha de desenvolvimento que assim você conseguirá construir um relatório apurado no contexto geral e individual.

Abraço.
Responder
09/05/2017
Olá Thiengo, parabéns pelo conteúdo, como sempre de primeira!

Assistindo este último artigo, percebi uma solução para um problema que eu já havia notado quando usei Notifications no próprio console do Firebase, não tem como enviar textos traduzidos de acordo com a localização.

Neste caso, poderíamos enviar para o backend web, junto aos dados de perfil do usuário, a língua que o SO está configurado, assim podemos enviar a notificação na língua preferida pelo usuário.

Você conhece alguma outra forma de resolver este problema?

Grande abraço! Não pare com a série, o conteúdo está ótimo!!
Responder
Vinícius Thiengo (1) (0)
09/05/2017
Thyerri, tudo bem?

Realmente na documentação não há sobre esse assunto, acaba sendo uma limitação da API FCM.

Essa solução que você comentou sobre é um possível caminho, provavelmente o melhor. Mas parte dela implica na melhoria do dashboard de administrador.

Por exemplo: o OneSignal nos permite criar a mesma mensagem em vários outros idiomas, até mesmo entrega-las de acordo com o fuso horário do assinante da push notification. Mesmo utilizando o GCM, essas características que permitem o fornecimento de mais de uma mensagem e fuso horário são características do próprio OneSignal e não do GCM / FCM.

Com a sua resposta e o que eu já conheço do OneSignal, minha dica seria a mesma: identificar o idioma e até mesmo o fuso no cadastro do usuário e assim enviar a mensagem corretamente configurada a ele.

Somente ressaltando que o OneSignal não traduz sua mensagem, ele apenas permite que você forneça ela em mais de um idioma? ou seja, terá de colocar cada uma a mão. Abraço.
Responder