Configurando Por Completo o Sistema de Notificação Push - YouTuber Android App - Parte 10

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 /Configurando Por Completo o Sistema de Notificação Push - YouTuber Android App - Parte 10

Configurando Por Completo o Sistema de Notificação Push - YouTuber Android App - Parte 10

Vinícius Thiengo
(2450) (2)
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

Tudo bem?

Neste artigo vamos continuar com o nosso projeto de aplicativo Android para YouTubers.

Nesta parte dez do conteúdo vamos à configuração completa de um serviço de notificação push confiável e, acredite, trivial de utilizar quando comparado a outras opções de serviço de notificação em mercado:

Animação de uma notificação chegando ao app Android para YouTuber

Antes de prosseguir, saiba que:

A versão e-book (PDF 📙) do projeto completo está disponível somente para os inscritos da lista de e-mails 📧 do Blog.

Se você se inscrever ainda hoje é possível que o link do e-book esteja disponível na página de confirmação de sua inscrição na lista de e-mails do Blog.

A seguir os tópicos que estaremos abordando neste nono conteúdo do projeto de aplicativo Android para YouTubers:

O que já temos até aqui

Se você chegou no projeto somente agora, saiba que este não é o primeiro e nem mesmo o último artigo já publicado sobre essa proposta de aplicativo Android.

Todo o roteiro de construção do projeto está na listagem a seguir:

Para tirar o máximo proveito do projeto de aplicativo que estaremos desenvolvendo... para isso é inteligente seguir cada um dos conteúdos na ordem apresentada na lista anterior.

Repositório

Para ter acesso a todos os códigos fontes do projeto já finalizado, entre no repositório GitHub dele em:

➙ GitHub do projeto de aplicativo Android para YouTuber.

Será o OneSignal?

Sim, vamos utilizar o serviço de notificações push do OneSignal:

Home page OneSignal

O mesmo utilizado por Volkswagen, Verizon, Zynga e inúmeras outras empresas que como eu já utilizam este serviço de push message a alguns anos.

E com segurança falo a ti:

O OneSignal é sim a melhor opção para envio de notificações push quando o projeto não depende de uma base compartilhada de dados (e lógica de negócio em cima dessa base) para a geração de notificações.

Por que este serviço?

Thiengo, por que o OneSignal e não o Firebase Cloud Messaging (FCM)?

Primeiro é importante saber que na verdade o FCM é sim utilizado mesmo quando você trabalha somente com o OneSignal na geração de push messages.

Não somente no caso do OneSignal, mas para qualquer plataforma que promete entregar notificações a aparelhos Android...

... o FCM tem que ser utilizado, pois ele é o servidor de notificações push que mantém contato com os aparelhos Android.

Tem a excessão de servidores XMPP e alguns "vida louca" que teimam em manter conexões em background para verificação em base compartilhada (backend Web) para saber se tem ou não novos dados.

Já fiz muito este último caso e com propriedade falo: não vale a pena.

Voltando ao OneSignal...

... é um serviço de fácil uso e implantação em app. E será fácil delegar ao proprietário do aplicativo (o YouTuber) a tarefa de criar novas notificações a cada novo vídeo criado em canal.

Toda a interface é simples e, principalmente, o serviço funciona.

Além de tudo a versão gratuita do OneSignal não tem limitações de entrega de notificações push a aparelhos Android:

Planos OneSignal

Eu poderia ficar mais um bom tempo informando aqui o porquê do OneSignal.

Mas acredito que com o que já temos de informação sobre está plataforma de notificações, seguramente podemos prosseguir com os códigos.

Para o nosso domínio de problema o OneSignal é indiscutívelmente a melhor opção, e é gratuito!

Um detalhe:

Estamos falando muito sobre o OneSignal, mas em termos de código fonte em projeto a nossa dedicação será mesmo em torno do algoritmo de notificação em badeja Android assim que a mensagem push já foi entregue ao aplicativo.

Não se sinta confuso, no decorrer da configuração tudo ficará mais claro.

Dados que são pré-requisito

Neste ponto já sabemos o serviço de notificação que será utilizado e também qual configuração (APIs de notificação Android) exigirá mais atenção e código fonte novo em app.

Com isso, vamos aos códigos.

Iniciando pela parte fácil e que é pré-requisito de outros trechos de configuração:

Cadastro e obtenção de chaves e identificadores únicos de APIs e conta em projeto.

Cadastro e geração de Keys e IDs

Caso você seja novo no OneSignal, então serão necessários alguns cadastros (OneSignal e Google) para gerar as chaves de API e IDs necessários em configuração.

Vamos iniciar pelo Google.

Cadastro e registro no Cloud Messaging

O cadastro no Google é necessário, pois nosso projeto precisará de:

  • Google Cloud Message Server Key;
  • Google Cloud Message Sender ID.

Isso para a configuração de envio (backend Web) e recebimento (app Android) de notificações push.

Primeiro é preciso uma conta no Google Gmail.

Então se cadastre no link a seguir caso você não tenha um Gmail:

Agora é preciso entrar no Firebase Console e criar um novo projeto:

  • Acesse Firebase Console e realize o login com o seu Gmail;
  • Clique em "Ir para o console";
  • Agora clique em "Criar um projeto";

Home page Google Firebase

  • Na próxima tela, Passo 1, coloque o nome do novo projeto (preferencialmente o nome do aplicativo);

Nome do novo projeto no Firebase

  • Marque a opção "Aceito os Termos do Firebase" e clique em "Continuar";
  • No próximo passo nós temos a opção de ativar o Google Analytics. Não vamos utilizar aqui nenhuma API do analytics, então vamos seguir com ele desativado. Isso é opcional, pode ser que algum cliente exija que no projeto dele seja sim acionado o analytics, neste caso acione;

Opção de Google Analytics no Dashboard Firebase

  • Clique em "Continuar";
  • Aguarde o projeto ser criado;

Um novo projeto sendo criado Firebase

  • Ao final clique em "Continuar";

Um novo projeto criado no Firebase

Já no Dashboard do projeto siga:

  • Clique em "Configurações do projeto";

Acessando Configurações de projeto no Firebase

  • Então clique na aba "Cloud Messaging";

Obtendo a Key e o Sender ID do novo projeto no Firebase

  • Copie "Chave do servidor" e "Código do remetente".

É isso, chave e ID gerados. Salve esses dados que logo logo vamos coloca-los em uma nova classe de configuração que estaremos criando.

Cadastro e registro no OneSignal

Agora é preciso o cadastro na plataforma OneSignal:

Utilize a mesma conta de Gmail que você utilizou para a obtenção das chaves de API e ID.

Não é obrigatório, mas mantém toda a configuração externa de dependência de projeto em uma única conta.

Ou seja, organização.

Alias, como recomendação:

Para qualquer novo projeto de um novo canal cliente que você estiver atendendo. Sempre crie você um Gmail para o novo projeto (não solicite isso ao cliente) e então crie toda a configuração de chaves e serviço com esse novo e-mail.

Ao final, na entrega do projeto, entregue também o novo e-mail Gmail e informe ao cliente para trocar a senha.

Como estamos trabalhando em um framework, não precisamos se preocupar muito com requisitos. Desta forma, evite ao máximo pedir informações desnecessárias ao cliente, pois isso pode acarretar em não continuidade com o aplicativo.

Essa estratégia de "pedir menos dados ao cliente" é também comentada no livro Otimização da Página de Entrada de Tim Ash.

Voltando ao OneSignal...

... depois do cadastro realizado com sucesso, agora é fazer o login no OneSignal, acessar o Dashboard e:

  • Clique em "NEW APP/WEBSITE";

Criando um novo app no Dashboard OneSignal

  • Na próxima tela coloque o nome do aplicativo em "Name of your app or website" e então selecione "Android" dentre as opções para "What platform do you wish to use for this app? You can set up more later";

Escolhendo a plataforma Android para ser utilizada junto ao sistema OneSignal

  • Por fim clique em "Next: Configure Your Platform".

Você vai perceber que uma nova tela será aberta com a caixa de diálogo "Configure Platform".

Nesta nova tela:

  • Coloque nos campos definidos as Server Key e Sender ID, identificadores gerados anteriormente no Firebase Console;

Definindo os Firebase Server Key e Sender ID no OneSignal

  • Logo depois clique em "Next";
  • Na próxima tela, "Select your target SDK", selecione "Android Native";

Selecionando, no OneSignal, Android Nativo como SDK alvo

  • Clique em "Next" novamente;
  • Na próxima tela será apresentado o App ID OneSignal do aplicativo cadastrado. Copie ele e clique em "Save";

Obtendo o App ID OneSignal

  • Pode aparecer uma nova tela. Nela, clique em "Leave setup".

Passo final no Dashboard OneSignal, Leave Setup

Agora temos todas as chaves e IDs em mãos para seguir com as configurações no próprio aplicativo.

Configuração em projeto

Os códigos não são tão longos, digo, os códigos exclusivos do OneSignal, pois aqui teremos que também já adicionar os códigos que criam a notificação em bandeja de notificações Android.

Antes de prosseguir é importante ressaltar que:

Nosso projeto de aplicativo Android não fará uso da configuração default de notificação em bandeja que a OneSignal API oferece.

Animação do modelo padrão de notificação OneSignal

Criaremos toda a nossa estrutura de notificação que utilizará BigPicture e outros recursos.

So, let's code.

Atualizando arquivo Gradle

No Gradle Nível de Aplicativo, ou build.gradle (Module: app), adicione logo no início deste arquivo o seguinte código de configuração OneSignal em projeto:

buildscript {
repositories {
maven { url 'https://plugins.gradle.org/m2/'}
}
dependencies {
classpath 'gradle.plugin.com.onesignal:onesignal-gradle-plugin:[0.12.8, 0.99.99]'
}
}
apply plugin: 'com.onesignal.androidsdk.onesignal-gradle-plugin'

repositories {
maven { url 'https://maven.google.com' }
}
...

 

Ainda no mesmo arquivo Gradle, agora dentro do bloco defaultConfig, adicione manifestPlaceholders com o App ID gerado no OneSignal:

...
defaultConfig {
...

manifestPlaceholders = [
onesignal_app_id: 'e4c1751-...',
onesignal_google_project_number: 'REMOTE'
]
}
...

 

E por fim, ainda no mesmo arquivo Gradle Nível de Aplicativo, adicione no bloco dependencies a referência OneSignal como a seguir:

...
dependencies {
...

/*
* Para uso da OneSignal API. API redundante de
* notificações no projeto.
* */
implementation 'com.onesignal:OneSignal:[3.15.0, 3.99.99]'
}

 

Agora sincronize o projeto.

Classe de configuração

Como fizemos com alguns dados importantes do YouTube, aqui também teremos uma classe somente com dados de configuração OneSignal.

No nosso projeto Android, mais precisamente no pacote /config, crie a classe abstrata OneSignalConfig com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.config

/**
* Classe que contém todos os dados estáticos de
* configuração de notificações push no sistema
* via API OneSignal.
*
* As classes internas ([Firebase], [App], ...)
* e também os rótulos de todos os companion object.
* Estes estão presentes em código somente para
* facilitar a leitura dele. Ou seja, em termos de
* regras de sintaxe esses não são obrigatórios.
*/
abstract class OneSignalConfig {

abstract class Firebase {
companion object CloudMessage {
/**
* Constantes com dados de configuração
* do Firebase Cloud Message. Dados que
* devem ser colocados no dashboard do
* OneSignal.
*/
const val SERVER_KEY = "AAAAM..."
const val SENDER_ID = "221..."
}
}

abstract class App {
companion object {
/**
* Constante com dado de configuração
* do OneSignal em aplicativo. Esse dado
* entra na variável onesignal_app_id do
* Gradle Nível de Aplicativo, ou
* build.gradle (Module: app).
*/
const val ID = "e4c1751-..."
}
}
}

 

Nas constantes SERVER_KEYSENDER_ID e ID você colocará os dados que foram gerados seguindo os passo a passos em seções anteriores, de geração de chaves e IDs.

É aquilo, com essa classe de configuração, além da melhor leitura de código fonte de projeto, a mudança de canal em app fica facilitada.

Application personalizada

Agora vamos ao código de inicialização da API OneSignal em projeto.

Para isso teremos que criar uma classe Application personalizada.

Fora de qualquer pacote, na raiz do projeto, crie a classe CustomApplication com a seguinte configuração de código:

package thiengo.com.br.canalvinciusthiengo

import android.app.Application
import com.onesignal.OneSignal

/**
* Classe de sistema e de objeto único enquanto
* o aplicativo estiver em execução.
*
* Com essa característica está é a classe
* responsável por iniciar as entidades de
* WorkManager (trabalho em background) e
* OneSignal (notificação push). Entidades essas
* que precisam ser invocadas logo no início da
* execução do aplicativo ainda na primeira vez
* que ele é acessado pelo usuário.
*
* Note que para essa classe ser invocada pelo
* sistema ela precisa estar configurada no
* AndroidManifest.xml como:
*
* <application
* android:name=".CustomApplication"
* ...>
*
* @constructor cria um objeto completo do tipo
* [CustomApplication].
*/
class CustomApplication: Application() {

override fun onCreate(){
super.onCreate()
oneSignalInit()
}

/**
* Inicializa o OneSignal e registra o
* usuário do app para que ele já consiga
* receber as notificações push do canal
* do aplicativo.
*
* Com a configuração a seguir é preciso
* que também tenha definido no aplicativo
* um serviço do tipo
* [NotificationExtenderService] para que as
* notificações push sejam interceptadas
* e trabalhadas de maneira personalizada.
*/
private fun oneSignalInit(){
OneSignal.startInit( this )
.inFocusDisplaying( OneSignal.OSInFocusDisplayOption.Notification )
.unsubscribeWhenNotificationsAreDisabled( true )
.init()
}
}

 

Para a CustomApplication ser efetiva ainda temos que configura-la no AndroidManifest.xml. Como a seguir, na tag <application>:

...
<application
android:name=".CustomApplication"
...>
...

 

E sim, o código da CustomApplication também receberá os fontes de inicialização do WorkManager, como você deve ter percebido nos comentários desta classe de sistema:

/**
* ...
*
* Com essa característica está é a classe
* responsável por iniciar as entidades de
* WorkManager (trabalho em background) e
* OneSignal (notificação push). Entidades essas
* que precisam ser invocadas logo no início da
* execução do aplicativo ainda na primeira vez
* que ele é acessado pelo usuário.
*
* ...
*/
...

 

Mas não se preocupe com o WorkManager agora, ainda faltam alguns conteúdos antes de chegarmos a este ponto do projeto.

Pronto. Configuração de inicialização feita.

Mas ainda falta a criação de nossa notificação personalizada.

Classe utilitária

Antes de criarmos um serviço OneSignal para interceptar o comportamento comum do OneSignal na criação de notificação em bandeja...

... antes disso vamos aos algoritmos que criam toda a configuração do objeto de notificação a partir do app.

Na raiz do projeto crie o pacote /notification.

Dentro deste pacote crie a classe abstrata UtilNotification com a seguinte configuração inicial:

package thiengo.com.br.canalvinciusthiengo.notification

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.squareup.picasso.Picasso
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.LastVideo
import thiengo.com.br.canalvinciusthiengo.ui.MainActivity
import thiengo.com.br.canalvinciusthiengo.ui.MainActivityForegroundStatus

/**
* Classe utilitária que permite o fácil acesso à
* geração de notificações push no app.
*
* Assim é possível obter de maneira imediata e
* não verbosa uma notificação push quando um
* novo "último vídeo" liberado chega ao aplicativo.
*
* @property context contexto do aplicativo.
* @constructor cria um objeto completo do tipo
* [UtilNotification].
*/
class UtilNotification private constructor(
private val context: Context ){

companion object{
/**
* Propriedade responsável por conter a única
* instância de [UtilNotification] disponível
* durante toda a execução do aplicativo.
*/
private var instance: UtilNotification? = null

/**
* Método que aplica, junto à propriedade
* [instance], o padrão Singleton em classe.
* Garantindo que somente uma instância de
* [UtilNotification] estará disponível durante
* toda a execução do app. Ajudando a
* diminuir a possibilidade de vazamento
* de memória.
*
* @param context contexto do aplicativo.
* @return instância única de [UtilNotification].
*/
fun getInstance( context: Context ) : UtilNotification {
if( instance == null ){
instance = UtilNotification( context = context )
}
return instance!!
}
}
}

 

Novamente, onde tem force NullPointerException (!!, como em instance!!) é porque sabemos que nunca será null, pois conhecemos o fluxo do código.

Outro ponto a se notar: somente pelos imports em classe já é possível saber que muitas outras configurações serão adicionadas, certo?

Então vamos a elas.

Canal (channel) de notificação

A partir do Android O, API 26, as notificações somente aparecem em bandeja de notificação se elas fizerem parte de um "Notification Channel" criado no aplicativo.

Sendo assim o nosso primeiro método de configuração em UtilNotification será o método createNotificationChannel() como a seguir:

...
/**
* Cria uma Notification Channel para aparelhos
* Android com o Android Oreo (API 26) ou superior.
*
* Notification Channel é algo necessário nessas
* versões do Android para a notificação ser gerada.
*/
@RequiresApi( Build.VERSION_CODES.O )
private fun createNotificationChannel(){

val name = context.getString(
R.string.notification_verbose_name
)
val description = context.getString(
R.string.notification_verbose_description
)
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(
CHANNEL_ID,
name,
importance
)
.apply{
this.description = description
}

val notificationManager = context.getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager?

notificationManager?.createNotificationChannel( channel )
}
...

 

Note que este método fica fora do companion object, ele e todos os outros métodos que estaremos adicionando em UtilNotification.

A configuração @RequiresApi( Build.VERSION_CODES.O ) é somente para o Lint do Android Studio IDE não ficar "reclamando" que o código do método não roda para todo o suporte de Android APIs que o nosso aplicativo atende (a partir da API 21, Lollipop).

Com essa anotação nós estamos informando ao Lint que o método somente será chamado em versões do Android a partir do Android O. Teremos que construir uma lógica de negócio em projeto para garantir isso.

Ainda falta adicionar em projeto as Strings notification_verbose_name e notification_verbose_description.

Logo, em /res/values/strings.xml adicione as Strings a seguir:

...
<!-- UtilNotification -->
<string name="notification_verbose_name">
🎬 Vídeo novo no canal
</string>
<string name="notification_verbose_description">
Notificação de novos vídeos de desenvolvimento
Android que são liberados no canal YouTube
Vinícius Thiengo.
</string>
...

 

Outra adição necessária é a constante CHANNEL_ID que entra justamente no companion object de UtilNotification:

...
companion object{
/**
* Constante que contém o identificador único
* de Notification Channel da notificação push.
*/
const val CHANNEL_ID = "new_channel_video"

...
}
...

 

Por causa da configuração de notification channel, quando o nosso aplicativo estiver rodando em um aparelho com o Android O ou acima...

... na área de notificações do aplicativo, nas configurações do aparelho, teremos:

Área de notificações de app no sistema Android

É possível ter mais de um canal de notificações. São esses canais que permitem que o usuário escolha qual tipo de notificação quer continuar recebendo do aplicativo. 

Isso já existia anteriormente, mas éramos nós desenvolvedores que tínhamos de fazer tudo na mão.

O Google Android provavelmente percebeu que quase ninguém fazia e então resolveu forçar isso, adicionando as notification channels no Android.

Qual atividade abrir?

Se você é novo no Android ou novo no contexto "Notificações", então saiba que quando se configura uma notificação é preciso informar qual entidade ela vai acionar caso o usuário acione ela na bandeja de notificações do aparelho.

Em nosso caso temos somente uma atividade e ela deve ser acionada depois do acionamento de uma notificação do nosso app.

Sendo assim, vamos criar uma PendingIntent com a configuração correta para isso.

Em UtilNotification adicione o método getPendingIntent() com o código a seguir:

...
/**
* Configura e retorna uma [PendingIntent] que
* acionará a [MainActivity] do aplicativo caso
* a notificação push do app seja acionada pelo
* usuário.
*
* @return [PendingIntent] configurada para
* abertura de app.
*/
private fun getPendingIntent() : PendingIntent {

val intent = Intent(
context,
MainActivity::class.java
).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}

val pendingIntent: PendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)

return pendingIntent
}
...

 

Com a configuração da PendingIntent já dá para perceber que nossa notificação sempre terá o mesmo identificador único (devido a PendingIntent.FLAG_UPDATE_CURRENT) e se alguma nova notificação chegar à bandeja de notificações vindo novamente de nosso aplicativo, o que vai acontecer é...

... a notificação antiga perderá espaço para a mais atual, ou seja, não acumularemos notificações em bandeja.

Até porque não faz nem mesmo sentido a acumulação de notificações em nosso domínio de problema, pois a notificação é somente para avisar sobre um novo vídeo e em nosso domínio só um novo vídeo ficará exposto em app.

Note também que em:

...
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
...

 

Estamos, em resumo, informando ao sistema Android que:

Se já houver uma atividade (tarefa) de nosso app disponível em memória, então finalize essa atividade e inicie uma nova.

Desta forma nós não corremos o risco de deixar o usuário com duas atividades iguais do mesmo aplicativo em pilha de atividades.

Algo que seria um comportamento bug 🐞 aos olhos do usuário final.

Criando o objeto Notification

Agora podemos partir para o método que realmente vai criar um objeto de notificação.

Ainda em UtilNotification crie o método getNotification() com o código a seguir:

...
/**
* Retorna o objeto de notificação push completamente
* configurado para que a notificação seja apresentada
* de maneira a aumentar a conversão (toque / clique)
* nela.
*
* @param lastVideo último vídeo liberado em canal e
* que chegou ao aplicativo.
* @param bitmapBigPicture bitmap da thumb do último
* vídeo liberado.
* @return notificação adequadamente configurada.
*/
private fun getNotification(
lastVideo: LastVideo,
bitmapBigPicture: Bitmap? = null ) : Notification {

val notification = NotificationCompat
.Builder(
context,
CHANNEL_ID
)
.setSmallIcon( R.drawable.ic_circular_play )
.setContentTitle(
context.getString( R.string.notification_verbose_name )
)
.setContentText( lastVideo.title )
.setPriority( NotificationCompat.PRIORITY_HIGH )
.setCategory( NotificationCompat.CATEGORY_RECOMMENDATION )
.setContentIntent( getPendingIntent() )
.setVisibility( NotificationCompat.VISIBILITY_PUBLIC )
.setAutoCancel( true )

if( bitmapBigPicture != null ){
notification
.setLargeIcon( bitmapBigPicture )
.setStyle(
NotificationCompat
.BigPictureStyle()
.bigPicture( bitmapBigPicture )
.bigLargeIcon( null )
)
}

return notification.build()
}
...

 

Note que definimos uma notificação de prioridade alta, PRIORITY_HIGH, e com uma categoria que mais se enquadra ao que nossas notificações realmente são: recomendação, CATEGORY_RECOMMENDATION.

O máximo de configurações que for possível e cabível colocar nós temos que colocar porque isso ajuda para que a entrega da notificação em aparelho realmente ocorra.

Thiengo, como assim "(...) realmente ocorra"?

Mais para o final deste artigo vou lhe explicar alguns probleminhas que podemos ter mesmo realizando toda a configuração de notificação como definida em documentação oficial.

Outro ponto a se notar no código de getNotification() é que o ícone de nossas notificações será sempre o mesmo. Veja a definição em setSmallIcon():

...
.setSmallIcon( R.drawable.ic_circular_play )
...

 

É o exato mesmo ícone da primeira opção de nosso menu principal, o bottom menu:

Ícone vetorial de notificação

Você pode definir qualquer ícone como ícone e notificação. Mas que ele tenha o canal alpha, transparência, ativo.

Caso contrário ele aparecerá em bandeja de notificações como um quadrado branco.

Métodos de criação e configuração completa

Para que outros métodos do aplicativo possam acessar a criação de notificação, temos ainda que criar ao menos um método público que junta "todo mundo" que criamos até o momento em UtilNotification.

Na verdade vamos criar dois métodos, apenas um será púbico.

Ainda em UtilNotification crie o método createNotification() com a seguinte configuração:

...
/**
* Cria toda a configuração de notificação push
* (incluindo Notification Channel) do aplicativo.
*
* @param lastVideo último vídeo liberado em canal e
* que chegou ao aplicativo.
* @param bitmapBigPicture bitmap da thumb do último
* vídeo liberado.
*/
private fun createNotification(
lastVideo: LastVideo,
bitmapBigPicture: Bitmap? = null ){

if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
createNotificationChannel()
}

val notificationBuilder = getNotification(
lastVideo = lastVideo,
bitmapBigPicture = bitmapBigPicture
)

NotificationManagerCompat
.from( context )
.notify(
NOTIFICATION_ID,
notificationBuilder
)
}
...

 

Note que o identificador único da notificação será sempre o mesmo, NOTIFICATION_ID. Para forçar o update da notificação em bandeja de notificações.

Vamos configurar essa constante no companion object de UtilNotification:

...
companion object{
/**
* Constante que contém o identificador único
* para todas as notificações que forem criadas
* a partir deste app. Não precisamos de trabalho
* com acumulação de notificações, então é seguro
* seguir está estratégia.
*/
const val NOTIFICATION_ID = 1

...
}
...

 

É justamente o método criado anteriormente, createNotification(), que junta tudo e gera a notificação.

Agora vamos ao método que será público e terá também o carregamento, síncrono, de uma imagem de banner para BigPicture.

Em UtilNotification crie o método público createBigPictureNotification() com o código a seguir:

...
/**
* Cria uma notificação que pode conter também uma
* BigPicture como parte do conteúdo dela.
*
* @param lastVideo último vídeo liberado em canal e
* que chegou ao aplicativo.
*/
fun createBigPictureNotification(
lastVideo: LastVideo ){

val bitmapBigPicture = Picasso
.get()
.load( lastVideo.thumbUrl )
.get()

createNotification(
lastVideo = lastVideo,
bitmapBigPicture = bitmapBigPicture
)
}
...

 

Nós estamos "apelando" para uma notificação com BigPicture, pois o aplicativo precisa converter, ou seja, a notificação precisa ser acionada pelo seguidor do canal que é usuário do app.

Assim o aplicativo realmente estará ao menos se esforçando para aumentar as visualizações e consequentemente os ganhos do YouTuber.

Notificação completa, incluindo imagem, tem mais chances de ser acionada.

Serviço de interceptação de notificação

Agora podemos partir para a criação de uma NotificationExtenderService que será responsável por interceptar os dados de notificação push entregues em aplicativo e assim assumir a responsabilidade de criar uma notificação na bandeja de notificações do aparelho:

Notificação personalizada sendo entregue à bandeja de notificações Android

Sendo assim, no pacote /notification, crie o serviço CustomNotificationExtenderService com a seguinte configuração inicial:

package thiengo.com.br.canalvinciusthiengo.notification

import android.net.UrlQuerySanitizer
import com.onesignal.NotificationExtenderService
import com.onesignal.OSNotificationReceivedResult
import org.json.JSONObject
import thiengo.com.br.canalvinciusthiengo.config.OneSignalConfig
import thiengo.com.br.canalvinciusthiengo.config.YouTubeConfig
import thiengo.com.br.canalvinciusthiengo.data.dynamic.UtilDatabase
import thiengo.com.br.canalvinciusthiengo.model.LastVideo
import java.net.URI

/**
* Serviço responsável por interceptar a notificação
* OneSignal enviada ao aparelho e assim trabalhar
* a configuração personalizada dessa notificação
* push.
*
* Este serviço deve também estar configurado no
* AndroidManifest.xml do app como a seguir:
*
* <service
* android:name=".notification.CustomNotificationExtenderService"
* android:exported="false"
* android:permission="android.permission.BIND_JOB_SERVICE">
* <intent-filter>
* <action android:name="com.onesignal.NotificationExtender" />
* </intent-filter>
* </service>
*
* @constructor cria um objeto completo do tipo
* [CustomNotificationExtenderService].
*/
class CustomNotificationExtenderService: NotificationExtenderService() {

/**
* Processa a notificação OneSignal que foi
* enviada ao aparelho.
*
* @param notification notificação OneSignal.
* @return um [Boolean] que indica se o
* comportamento comum de geração de notificação
* da OneSignal API deve (false) ou não (true)
* continuar.
*/
override fun onNotificationProcessing(
notification: OSNotificationReceivedResult? ): Boolean {

/* TODO */

return true
}
}

 

Os dados da notificação criada no Dashboard OneSignal estarão todos presentes no parâmetro notification de onNotificationProcessing().

Parâmetros dos dados em notificação

Para que seja possível ler os dados de notificação OneSignal que chegarão ao app é preciso ter conhecimento dos parâmetros de acesso esses dados.

Nós é que definiremos esses parâmetros em Dashboard. Então já podemos defini-los antes aqui em nosso código.

Sendo assim, dentro da classe OneSignalConfig adicione as seguintes novas constantes (e classe abstrata):

...
abstract class Notification {
companion object Parameter {
/**
* Constantes com definições de campos
* de JSON para acesso aos dados
* enviados ao aplicativo via
* notificação push OneSignal.
*/
const val VIDEO = "video"
const val TITLE = "title"
const val DESCRIPTION = "description"

/**
* Constante com uma definição de
* [String] vazia para retornos onde
* dados opcionais (como o dado de
* [DESCRIPTION]) não foram fornecidos
* em notificação push.
*/
const val EMPTY = ""
}
}
...

 

Essas definições de rótulos de parâmetros em VIDEOTITLE e DESCRIPTION (está última será opcional) deverão ser seguidas a risca também na hora de criar uma notificação no Dashboard OneSignal.

Parâmetros dos dados em URL

Além dos parâmetros específicos de notificação via OneSignal, nós também teremos que trabalhar alguns valores e parâmetros em notificação que são específicos YouTube.

Desta forma, na classe YouTubeConfig, adicione a nova classe abstrata e constantes a seguir:

abstract class YouTubeConfig {
...

abstract class Notification {
companion object {
/**
* Constantes com definições para acesso aos
* dados de novo "último vídeo" liberado no
* canal YouTube do app. Dados presentes em
* notificação push OneSignal.
*/
const val ALTERNATIVE_URL = "https://youtu.be/"
const val VIDEO_PARAM = "v"
const val EMPTY = ""
}
}
}

 

Você vai notar, quando começar a ler todos os códigos, que devido aos rótulos que estamos utilizando, rótulos auto-comentados, todo o algoritmo ficará intuitivo e fácil de entender.

De qualquer forma, sempre que necessário também teremos comentários em código.

Agora podemos ir seguros ao restante dos métodos no serviço CustomNotificationExtenderService.

Descrição do vídeo é opcional

Como o dado de descrição do vídeo é opcional, já vamos criar para ele um método que verifica se ele existe ou não em notification e assim retorna um valor válido para processamento.

Na classe CustomNotificationExtenderService crie o método getDescription() com o código a seguir:

...
/**
* Retorna o dado de descrição do "último vídeo" se
* ele estiver presente no JSON de dados que chegou
* junto a notificação OneSignal.
*
* @param json dados JSON obtidos de notificação.
* @return dado de descrição do vídeo.
*/
private fun getDescriptionFromJson( json: JSONObject )
= if( !json.isNull( OneSignalConfig.Notification.DESCRIPTION ) ){
json.getString( OneSignalConfig.Notification.DESCRIPTION )
}
else {
OneSignalConfig.Notification.EMPTY
}
...

 

Note que os dados dentro de notification estão na verdade dentro de um objeto do tipo JSONObject.

Destrinchando o JSONObject

Agora vamos criar o método que realmente acessa os dados esperados em notification, mais precisamente os dados no JSONObject que chega junto a notification.

E assim retornar um objeto LastVideo se os dados estiverem "Ok".

Na classe CustomNotificationExtenderService crie o método getLastVideoFromJson() com o código a seguir:

...
/**
* Gera um objeto [LastVideo] completo de acordo com
* os dados JSON recebidos de notificação OneSignal.
*
* @param json dados JSON obtidos de notificação.
* @return objeto [LastVideo] completo.
*/
private fun getLastVideoFromJson(
json: JSONObject? ) : LastVideo? {

/**
* Padrão Cláusula de guarda. Se as premissas não
* estiverem presentes, então nem mesmo continue
* com a execução.
*/
if( json == null
|| json.isNull( OneSignalConfig.Notification.VIDEO )
|| json.isNull( OneSignalConfig.Notification.TITLE ) ){
return null
}

val url = json.getString( OneSignalConfig.Notification.VIDEO )
val title = json.getString( OneSignalConfig.Notification.TITLE )
var lastVideo : LastVideo? = null

if( !url.isEmpty() && !title.isEmpty() ){

val urlQuery = UrlQuerySanitizer( url )

if( !urlQuery.getValue( YouTubeConfig.Notification.VIDEO_PARAM ).isNullOrEmpty() ){

lastVideo = LastVideo(
uid = urlQuery.getValue( YouTubeConfig.Notification.VIDEO_PARAM ),
title = title,
description = getDescriptionFromJson( json )
)
.apply {
thumbUrl = YouTubeConfig.Notification.EMPTY
}
}
else if( url.contains( YouTubeConfig.Notification.ALTERNATIVE_URL ) ){

val uri = URI( url )
val path: String = uri.getPath()
val uid = path
.substring(
path.lastIndexOf('/') + 1
)

lastVideo = LastVideo(
uid = uid,
title = title,
description = getDescriptionFromJson( json )
)
.apply {
thumbUrl = YouTubeConfig.Notification.EMPTY
}
}
}

return lastVideo
}
...

 

Os blocos condicionais if...else mais internos de getLastVideoFromJson():

...
if( !urlQuery.getValue( YouTubeConfig.Notification.VIDEO_PARAM ).isNullOrEmpty() ){
...
}
else if( url.contains( YouTubeConfig.Notification.ALTERNATIVE_URL ) ){
...
}
...

 

Estes blocos são necessários, pois como é possível que o usuário envie até dois tipos diferentes de URL de vídeo...

... assim temos duas formas completamente diferente para acessar o identificador único (uid) do vídeo que vem em URL.

Ainda falta um método para verificar se é ou não necessário salvar os dados de vídeo em banco de dados local, Room.

Salvando os dados e gerando notificação

Na verdade esse novo método deverá resolver duas tarefas em uma única tacada:

  • Verificar se o vídeo que chegou na notificação é diferente do vídeo já presente em base de dados local;
  • e Se realmente for um vídeo diferente, salvar ele em base e também gerar uma nova notificação em bandeja Android de notificações.

Na classe CustomNotificationExtenderService crie o método ifNewLastVideoThenSaveAndNotify() com o código a seguir:

/**
* Salva em banco de dados e notifica o usuário
* caso os dados que chegaram ao app sejam de um
* novo "último vídeo" liberado no canal YouTube
* do aplicativo.
*
* @param lastVideo último vídeo liberado em canal e
* que chegou ao aplicativo.
*/
private fun ifNewLastVideoThenSaveAndNotify(
lastVideo : LastVideo ){

UtilDatabase
.getInstance( context = this )
.getLastVideo{

if( it == null
|| !it.uid.equals( lastVideo.uid )
|| !it.title.equals( lastVideo.title )
|| !it.description.equals( lastVideo.description ) ){

UtilDatabase
.getInstance( context = this )
.saveLastVideo( lastVideo = lastVideo )

UtilNotification
.getInstance( context = this )
.createBigPictureNotification( lastVideo = lastVideo )
}
}
}

 

Duas tarefas relativamente complexas em relação à proposta do aplicativo:

  • Salvar dado atual em base de dados local (deletando o dado antigo);
  • Gerar notificação personalizada em bandeja de notificações do aparelho.

Porém ambas invocadas com poucas linhas de código devido às nossas classes utilitárias.

Um detalhe importante a se comentar é a condição no if:

...
if( it == null
|| !it.uid.equals( lastVideo.uid )
|| !it.title.equals( lastVideo.title )
|| !it.description.equals( lastVideo.description )
...

 

É preciso verificar, além do uid do vídeo, o título (title) e a descrição (description). Pois é possível que seja enviado o novo vídeo ao app, porém com atualização somente de título ou de descrição.

Ou seja, o vídeo já estava no aplicativo, mas não com o título ou a descrição esperados pelo YouTuber.

Lendo a notificação

Finalmente, em onNotificationProcessing(), vamos juntar todos os códigos necessários e já criados para interpretar os dados em notification e então gerar a notificação em bandeja caso necessário.

Segue atualização do método onNotificationProcessing():

...
override fun onNotificationProcessing(
notification: OSNotificationReceivedResult? ) : Boolean {

val lastVideo = getLastVideoFromJson(
json = notification?.payload?.additionalData
)

if( lastVideo != null ){
ifNewLastVideoThenSaveAndNotify( lastVideo = lastVideo )
}

return true
}
...

Ainda é preciso ativar o serviço.

Ativando o serviço

No arquivo AndroidManifest.xml coloque a seguinte definição <service> dentro da tag <application>:

...
<application
...>

...

<service
android:name=".notification.CustomNotificationExtenderService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">

<intent-filter>
<action android:name="com.onesignal.NotificationExtender" />
</intent-filter>
</service>
</application>
...

 

Assim o serviço está pronto para interceptar as notificações push geradas no Dashboard OneSignal.

Mas, acredite, ainda há pendências a serem resolvidas.

Na atividade principal

Com a configuração que temos atualmente em projeto, ainda há os problemas:

  • Se o aplicativo for aberto pelo ícone dele na área de apps do aparelho ao invés de ser aberto pelo acionamento da notificação em bandeja, a notificação continua na bandeja. Comportamento não esperado.
  • Se o aplicativo estiver aberto, ainda sim é gerada uma notificação na bandeja de notificações Android. Comportamento errado;
  • Se o app estiver aberto e uma nova notificação chegar com dados de um novo vídeo liberado, o fragmento LastVideoFragment não é atualizado. Comportamento pouco profissional!

Vamos resolver esses problemas.

Removendo a notificação em bandeja

Se o aplicativo for aberto sem ser pelo toque na notificação... mesmo assim é preciso remove-la da bandeja de notificações Android.

Caso contrário vamos ter:

Problema de notificação em bandeja mesmo quando o app está em foreground

Logo, na MainActivity, adicione o método removeStatusBarNotification() com o código a seguir:

...
/**
* Garante que não haverá notificação do aplicativo
* na bandeja de notificações do aparelho quando o
* app for aberto.
*/
private fun removeStatusBarNotification(){

val notificationManager = getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager

notificationManager.cancelAll()
}
...

 

E no método onCreate() da mesma atividade adicione a invocação deste novo método como a seguir:

...
override fun onCreate( ... ){
...

removeStatusBarNotification()
}
...

 

Done. Este primeiro problema está resolvido.

Evitando notificação com o app aberto

Se o aplicativo estiver em foreground, então a notificação não poderá ser gerada em bandeja de notificações.

Caso contrário é isso que ocorre com o app aberto:

Problema de notificação abrindo em bandeja mesmo com o app Android aberto

O algoritmo que vamos construir será simples e trabalhará em cima do estado atual da MainActivity.

E para estado, como já feito em outros pontos do projeto para manter uma boa leitura em código, vamos utilizar uma enum class.

No pacote /ui crie a classe MainActivityForegroundStatus com o código fonte a seguir:

package thiengo.com.br.canalvinciusthiengo.ui

/**
* Contém os possíveis estados de "atividade
* principal em foreground" para permitir
* atualizações pontuais em tela do aplicativo.
*/
enum class MainActivityForegroundStatus {
IS_IN_FOREGROUND,
IS_NOT_IN_FOREGROUND
}

 

Agora, na MainActivity, vamos controlar o estado da atividade com uma propriedade estática sendo atualizada nos métodos onResume() e onPause() da própria MainActivity.

Adicione na atividade os novos códigos a seguir:

...
companion object{
...

/**
* Propriedade estática que mantém o estado
* atual da atividade. Se ela está ou não em
* foreground. Está propriedade é acessada
* em outras partes do aplicativo que
* dependem desta informação.
*/
var APP_FOREGROUND = MainActivityForegroundStatus.IS_NOT_IN_FOREGROUND
}

override fun onResume() {
super.onResume()
removeStatusBarNotification()
APP_FOREGROUND = MainActivityForegroundStatus.IS_IN_FOREGROUND
}

override fun onPause() {
super.onPause()
APP_FOREGROUND = MainActivityForegroundStatus.IS_NOT_IN_FOREGROUND
}
...

 

Note que o método removeStatusBarNotification() passou a ser invocado em onResume().

Isso, pois é possível que o aplicativo perca o foreground (a atividade entra em onPause()), mas mesmo assim não seja destruída, ou seja, a activity não volta a invocar o método onCreate() caso ela volte ao primeiro plano.

Sendo assim, pode remover a invocação de removeStatusBarNotification() que estava no método onCreate().

Para finalizar a solução do problema, precisamos somente de uma cláusula de guarda no método createBigPictureNotification() da classe UtilNotification.

Coloque a cláusula de guarda logo no início deste método:

...
fun createBigPictureNotification(
lastVideo: LastVideo ){

/*
* Cláusula de guarda para garantir que a notificação
* somente será gerada se o aplicativo não estiver
* já aberto em tela (em foreground).
* */
if( MainActivity.APP_FOREGROUND == MainActivityForegroundStatus.IS_IN_FOREGROUND ){
return
}
...
}
...

 

Note que é exatamente em createBigPictureNotification() que devemos colocar a cláusula de guarda e não no método ifNewLastVideoThenSaveAndNotify() da classe CustomNotificationExtenderService.

Em ifNewLastVideoThenSaveAndNotify() ainda há o código que salva o novo vídeo em banco de dados.

Esse código precisa continuar sendo processado mesmo quando um novo vídeo chega ao app enquanto ele está em foreground.

Pronto. Outro problema resolvido.

Atualizando a tela via Broadcast

Como a necessidade de atualização será de apenas um dado, último vídeo sendo apresentado no fragmento LastVideoFragment enquanto o aplicativo está aberto.

Como é somente isso de necessidade, um LocalBroadcastManager nos atenderá perfeitamente e com poucas linhas de código.

Esse broadcast somente deverá funcionar depois que um novo dado de vídeo foi salvo com sucesso no banco de dados local e o aplicativo está em foreground.

Não precisamos nos preocupar se o fragmento LastVideoFragment está ou não em foreground, pois iremos atualizar a propriedade lastVideo deste fragmento.

Sendo assim, vamos criar nosso broadcast no pacote /data/dynamic com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.data.dynamic

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import thiengo.com.br.canalvinciusthiengo.model.LastVideo
import thiengo.com.br.canalvinciusthiengo.ui.fragment.LastVideoFragment

/**
* Responsável por informar ao fragmento
* [LastVideoFragment] que dados de um novo
* "último vídeo" do canal YouTube do app já
* estão disponíveis.
*
* Assim que esses novos dados são salvos em
* base local eles são em seguida enviados,
* via [LocalBroadcastManager], ao objeto desta
* classe, [NewLastVideoBroadcast]. O
* recebimento somente ocorre se o aplicativo
* estiver em foreground, caso contrário nada
* chega ao objeto desta classe, pois ele nem
* mesmo existe.
*
* @property fragment fragmento que contém a
* UI com os dados de último vídeo disponível.
* @constructor cria um objeto completo do tipo
* [NewLastVideoBroadcast].
*/
class NewLastVideoBroadcast(
private val fragment: LastVideoFragment
): BroadcastReceiver() {

companion object{
/**
* Constante com o identificador único
* do objeto broadcast
* [NewLastVideoBroadcast] em lista de
* broadcasts locais registrados em app.
*/
const val FILTER_KEY = "LocalBroadcastLastVideo_key"

/**
* Constante com o identificador único
* do dado (objeto [LastVideo]) presente
* no objeto [Intent] que é recebido em
* onReceive().
*/
const val DATA_KEY = "LastVideo_key"
}

/**
* Método receptor de dados da chamada ao
* objeto broadcast do tipo [NewLastVideoBroadcast].
*
* @param context contexto do aplicativo.
* @param data [Intent] com dados enviados
* ao objeto broadcast.
*/
override fun onReceive(
context: Context,
data: Intent ){

val lastVideo = data
.getParcelableExtra<LastVideo>( DATA_KEY )!!
}
}

 

Apesar do tamanho, o código é pequeno (os comentários é que são grandes).

Note que estamos utilizando "force NullPointerException" (!!) em getParcelableExtra(), pois conhecemos o fluxo do código e sabemos que ele nunca será null.

O dado lido de getParcelableExtra() vai vir de UtilDatabase e será do tipo Parcelable. Vamos a essa configuração.

Parcelable em LastVideo

Nossa classe de domínio LastVideo ainda precisa se tornar uma classe Parcelable.

Vamos a essa implementação de Interface.

Em LastVideo adicione toda a configuração Parcelable como a seguir:

data class LastVideo(
...
) : Parcelable {

...

/**
* Todos os códigos a partir deste ponto são
* referentes ao [Parcelable] que é aplicado ao
* objeto do tipo [LastVideo] para que ele
* possa ser transportado dentro de objetos
* [Intent] de maneira eficiente.
*/
constructor( source: Parcel ) : this (
source.readString()!!,
source.readString()!!,
source.readString()!!
)

override fun describeContents() = 0

override fun writeToParcel(
dest: Parcel,
flags: Int )
= with( dest ){
writeString( uid )
writeString( title )
writeString( description )
}

companion object {
@JvmField
val CREATOR: Parcelable.Creator<LastVideo>
= object : Parcelable.Creator<LastVideo> {

override fun createFromParcel( source: Parcel ) : LastVideo
= LastVideo( source )

override fun newArray( size: Int ) : Array<LastVideo?>
= arrayOfNulls( size )
}
}
}

 

Agora sim podemos partir para os códigos que colocam objetos LastVideo em objetos Intent.

Depois de salvar no banco de dados

Em UtilDatabase adicione primeiro o método que criará um broadcast local com um novo objeto LastVideo:

...
/**
* Via [LocalBroadcastManager] informa ao
* fragmento [LastVideoFragment] os dados do
* novo "último vídeo" que deve aparecer em
* tela.
*
* Somente terá efeito se o aplicativo
* estiver em foreground (primeiro plano).
*
* @param lastVideo último vídeo disponível
* no canal.
*/
private fun newLastVideoBroadcast(
lastVideo: LastVideo ){

val intent = Intent( NewLastVideoBroadcast.FILTER_KEY )

intent.putExtra(
NewLastVideoBroadcast.DATA_KEY,
lastVideo
)

LocalBroadcastManager
.getInstance( context )
.sendBroadcast( intent )
}
...

 

Este método deve ser adicionado fora do companion object de UtilDatabase.

Agora, no método saveLastVideo(), adicione a invocação ao método newLastVideoBroadcast() como a seguir:

...
fun saveLastVideo( lastVideo: LastVideo ){
thread{
try {
...

newLastVideoBroadcast( lastVideo = lastVideo )
}
catch( e :Exception ){}
}
}
...

 

Assim podemos ir às atualizações em LastVideoFragment.

Atualizando fragmento de vídeo

Primeiro, em LastVideoFragment, vamos adicionar a propriedade que vai conter o broadcast local:

class LastVideoFragment : Fragment() {
...

private lateinit var localBroadcast: NewLastVideoBroadcast
...
}

 

Ainda no mesmo fragmento, adicione o método de inicialização de broadcast, initLocalBroadcast():

...
/**
* Inicializa o LocalBroadcast para que seja possível
* a atualização da UI de "último vídeo" liberado
* quando o aplicativo está em foreground e um novo
* "último vídeo" é recebido por ele.
*/
private fun initLocalBroadcast(){
val intentFilter = IntentFilter( NewLastVideoBroadcast.FILTER_KEY )

localBroadcast = NewLastVideoBroadcast(
fragment = this
)

LocalBroadcastManager
.getInstance( activity!! )
.registerReceiver(
localBroadcast,
intentFilter
)
}
...

 

Este novo método será invocado no onCreate() do fragmento:

...
override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
initLocalBroadcast()

...
}
...

 

Agora, ainda em LastVideoFragment, adicione o método que destrói o broadcast local, destroyLocalBroadcast():

...
/**
* Destrói o LocalBroadcast quando o aplicativo não
* mais está em foreground. Pois não será possível
* atualizar a UI neste caso.
*/
private fun destroyLocalBroadcast(){
LocalBroadcastManager
.getInstance( activity!! )
.unregisterReceiver( localBroadcast )
}
...

 

Este método de destruição de broadcast será invocado no método onDestroy() do fragmento:

...
override fun onDestroy(){
super.onDestroy()
destroyLocalBroadcast()
}
...

 

Por fim, adicione ainda em LastVideoFragment o método que será invocado em onReceive() de NewLastVideoBroadcast com um novo objeto LastVideo:

...
/**
* Configura na propriedade [lastVideo] os dados do
* último vídeo liberado em canal e recebidos em
* aplicativo.
*
* @param video último vídeo liberado em canal.
*/
fun newLastVideoData( video: LastVideo ){
lastVideo = video
setUiModel( lVideo = video )
}
...

 

Não esqueça de adicionar os seguintes imports no fragmento:

...
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
...

 

Agora, em NewLastVideoBroadcast, atualize onReceive() como a seguir:

...
override fun onReceive(
context: Context,
data: Intent ){

val lastVideo = data
.getParcelableExtra<LastVideo>( DATA_KEY )!!

fragment.newLastVideoData( video = lastVideo )
}
...

 

Pronto, problema resolvido.

A atualização em tempo real ocorrerá caso um novo vídeo chegue ao app via notificação push e o app esteja em foreground.

Vamos tomar um café e conversar sobre notificações

A essa altura do campeonato o aplicativo já está funcional.

Obviamente que ainda falta toda a configuração de comunicação com os servidores do YouTube, mas este passo é, acredite, opcional.

A parte de notificação via OneSignal Dashboard é a mais importante em termos de: informar ao usuário que há um novo vídeo liberado.

O que quero falar aqui é:

As notificações, mesmo com toda a configuração exatamente como informado em documentações oficiais (Google Android e OneSignal).

Mesmo assim é ainda possível que em alguns contextos, momentos, elas não sejam entregues a alguns usuários.

Como você deve saber, o Android roda em inúmeros modelos e marcas de aparelhos.

Essas marcas têm total autonomia para trabalhar configurações personalizadas no sistema Android.

E, acredite, algumas alteram o comportamento padrão de notificações push mesmo com o já conhecido e insatisfatório (por parte dos desenvolvedores) conjunto de limites de entrega de notificações.

O que eu realmente quero com isso é simplesmente lhe informar que é provável que em alguns de seus testes, exatamente quando o app não estiver em background...

... que nesse contexto é provável que a notificação não chegue de imediato e até mesmo algumas não sejam entregues em nenhum momento.

Pois até a carga presente na bateria influencia se a notificação será ou não entregue.

O OneSignal realiza um trabalho "soberano" de entrega de push message ao FCM, depois disso é com o FCM a entrega ao aparelho.

Quando terminarmos todos os códigos do aplicativo, vamos a alguns testes.

Assim teremos que gerar algumas notificações no Dashboard OneSignal e nesta parte vou lhe mostrar configurações que ajudam a melhorar a taxa de entrega...

... uma delas é preencher o campo "TIME TO LIVE" da push message.

Então é isso, vamos agora aos algoritmos de comunicação remota com o YouTube Data API.

Até este ponto do projeto temos a seguinte configuração física (pacotes das entidades de código dinâmico que foram adicionadas) em IDE:

Configuração física do projeto Android

Próximo conteúdo

Com toda a configuração de notificação push finalizada em projeto, podemos partir para a configuração de comunicação remota com a YouTube Data API.

Segue o link para acesso ao próximo conteúdo:

➙ Configurando a YouTube Data API Com a Bilbioteca Retrofit - Parte 11.

Então é isso.

Depois deste longo conteúdo, descanse um pouco 🏡. Tome um café ☕ 🥪 🍎. E...

... te vejo na Parte 11 do projeto.

Se houverem dúvidas ou dicas deste décimo conteúdo do aplicativo, então deixe nos comentários que logo eu lhe respondo.

Não esqueça de conhecer também o meu canal no YouTube (caso você ainda não conheça) e...

... não deixe de se inscrever na 📩 lista de e-mails para também garantir a versão em PDF não somente deste projeto de aplicativo Android, mas também de cada novo "conteúdo mini-curso".

Abraço.

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

Colocando Telas de Introdução em Seu Aplicativo AndroidColocando Telas de Introdução em Seu Aplicativo AndroidAndroid
Como Reter Objetos Utilizando Android-State APIComo Reter Objetos Utilizando Android-State APIAndroid
Data Binding Para Vinculo de Dados na UI AndroidData Binding Para Vinculo de Dados na UI AndroidAndroid
Porque e Como Utilizar Vetores no AndroidPorque e Como Utilizar Vetores no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (2)

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...
Marcia (1) (1)
26/08/2020
Só para avisar, o link no texto:

? Configurando a YouTube Data API Com a Bilbioteca Retrofit - Parte 11.

Está incorreto: https://www.thiengo.com.br/configurando-a-youtube-data-api-com-a-bilbioteca-retrofit-youtuber-android-app-parte-11

É este que está no índice, no começo do artigo: https://www.thiengo.com.br/configurando-a-youtube-data-api-com-a-biblioteca-retrofit-youtuber-android-app-parte-11
Responder
Vinícius Thiengo (0) (1)
27/08/2020
Marcia, tudo bem?

Obrigado pelo informe.

Link corrigido.

Abraço.
Responder