Como Também Monetizar Usuários Inativos de Seu Aplicativo Android
(5342)
CategoriasAndroid, Design, Protótipo
AutorVinÃcius Thiengo
VÃdeo aulas186
Tempo15 horas
ExercÃciosSim
CertificadoSim
CategoriaEngenharia de Software
Autor(es)Vaughn Vernon
EditoraAlta Books
Edição1ª
Ano2024
Páginas160
Tudo bem?
Neste artigo vamos implementar uma API de monetização que também acrescenta um funcionalidade extra, funcionalidade além do domínio do problema de nossos aplicativos Android.
Com a Calldorado SDK será possível monetizarmos nossos apps Android em um trecho do aparelho do usuário onde APIs convencionais de anúncios mobile não conseguem rentabilizar, implicando assim na possibilidade de uso de ao menos mais de uma API para monetização: a Calldorado e alguma outra de sua preferência.
Como projeto de exemplo vamos trabalhar em um aplicativo onde o domínio do problema é a apresentação da sinopse dos filmes atuais em cartaz.
Antes de prosseguir, não esqueça de se inscrever 📫 na lista de e-mails do Blog para poder receber, em primeira mão, todos os conteúdos Android exclusivos.
A seguir todos os tópicos que estaremos abordando:
- Calldorado, visão geral:
- O que é?;
- Cálculo de ganhos aproximados;
- Configurações mínimas e como implementar a API;
- Ouvindo aos eventos de carregamento de SDK;
- Configuração de tema;
- Delay de inicialização de SDK;
- Reengajamento de usuário, estático e dinâmico;
- Não apresentando banners a usuários pagos;
- Inserindo a funcionalidade de busca de telefone;
- Trabalhando com target de usuário para melhorar a monetização;
- Permitindo a atualização das configurações da Calldorado API dentro do aplicativo Android;
- Melhores práticas;
- Modelo de recebimento.
- Projeto de exemplo, Android:
- Implementação da Calldorado API:
- Cadastro de conta;
- Passo a passo de integração da API;
- Primeiros testes;
- Atualizando a interface da funcionalidade de identificação de chamadas;
- Colocando a opção de atualização de configuração da Calldorado API;
- Criando um campo estático de reengajamento de usuário;
- Criando um campo dinâmico de reengajamento de usuário.
- Pontos negativos;
- Pontos positivos;
- Outras APIs de monetização no Android;
- Vídeo com implementação passo a passo da API;
- Conclusão;
- Fontes.
Caso queira pular a parte de implementação inicial do projeto de exemplo, depois da seção "Calldorado, visão geral" e das sub-seções dessa, vá direto a seção "Implementação da Calldorado API". De qualquer forma, recomendo que veja todo o artigo e o vídeo no final dele, onde a implementação é dinâmica.
Calldorado, visão geral
Como informado no início do artigo, a Calldorado API não somente nos permite a monetização como também permite que o usuário tenha uma nova funcionalidade no device dele: identificação de chamadas.
Na imagem anterior, liguei para a administração do cinema do principal shopping da capital do Espírito Santo, sem te-la nos contatos do device, a API Calldorado conseguiu, utilizando a base deles, identificar o local e me informar o nome. Posteriormente, no fim da ligação, é possível ver as avaliações e o local em mapa, além de podermos enviar um mensagem ou email, incluindo nossa própria avaliação.
O que é?
Uma API que nos permite acrescentar a funcionalidade de identificador de chamadas a aplicativos Android e assim o usuário passa a ter também essa característica no device dele.
Com isso podemos também aumentar os ganhos do App devido ao trabalho com anúncios, esses que, como outras APIs de monetização, vêm de ad networks famosas e com um fill rate alto. Networks como: AdMob, MoPub e Smaato.
Os anúncios sempre são apresentados na tela final, digo, caso tenha algum a ser apresentado. Nós não escolhemos os tipos de anúncios que serão mostrados (banner, vídeo, ...), essa escolha é com a Calldorado API.
Note que segundo a documentação e as FAQ da API em estudo, essa funcionalidade de identificação de chamada é muito bem vista pelos usuários de smartphones, logo, estudando essas fintes nós podemos perceber que na verdade o objetivo principal da startup dinamarquesa é fornecer essa característica de identificação de chamada, depois, como uma segunda meta, permitir o aumento dos ganhos do aplicativo por meio de anúncios que serão apresentados junto a essa funcionalidade.
É explicitado na documentação da Calldorado que a API terá uma melhor performance nos ganhos do aplicativo caso seja utilizada junto a outra API de anúncios, uma convencional que permita o uso de banners e interstitials, por exemplo.
E é isso mesmo que você leu, a startup é da Dinamarca, criada em 2016 e, segundo eles, com um crescimento rápido, hoje com mais de 50 colaboradores.
Note que a tela de identificação de chamada pode aparecer depois de uma ligação perdida, uma ligação não atendida (quando você realizou ela) e, obviamente, depois de uma ligação finalizada. Além do mais, com o consentimento da permissão de acesso a localização do usuário, é possível que a API apresente locais próximos e de mesmo business que o local da ligação anteriormente:
Acima a ligação foi realizada do emulador, que seguindo a documentação o número é de uma pizzaria em Berlim. Ao final, ligação encerrada, a API apresenta, além de outras opções, algumas pizzarias próximas do local onde o usuário está.
Durante o vídeo e o artigo, você notará que os anúncios não são apresentados em um device real e nem mesmo no emulador. Isso aparentemente acontece devido a liberação de anúncios somente depois de o App já estar na Google Play Store.
Entrei em contato com o suporte da Calldorado e garantiram que para o Brasil a API também funciona, digo, incluindo a monetização.
Para finalizar essa seção, a Calldorado não utiliza somente a base de dados dela para identificar qual o proprietário do número em ligação, são utilizadas também bases de dados de empresas parceiras. Tendo em mente que quando um número não é encontrado, nós usuários do aplicativo podemos fornecer o nome do dono do número:
Assim vamos ao calculador de ganhos aproximados.
Cálculo de ganhos aproximados
Na home page do site da API é possível saber aproximadamente quanto iremos ganhar quando instalando a Calldorado API em nosso aplicativo:
No testes anterior coloquei que o aplicativo que estarei vinculando a Calldorado API tem certa de 200 usuários ativos e estão todos no Brasil, ou seja, 0% em Estados Unidos e 0% na Europa.
Ok, mas o que é um "usuário ativo" para a Calldorado?
É um usuário que ao menos uma vez por dia atende a uma ligação ou realiza alguma, isso com a API de identificação de chamadas já ativa.
Um detalhe importante é que apesar de o resultado ter sido apresentado em dólar, por se tratar de uma startup dinamarquesa, o pagamento, segundo as FAQ da empresa, é em euro e não em dólar.
Alias, o mínimo exigido para poder proceder com a retirada do dinheiro, esse mínimo também é em euro.
Configurações mínimas e como implementar a API
A Calldorado API é uma das poucas, ou a única, que faz questão de explicitar as configurações mínimas de ambiente de desenvolvimento para poder utiliza-la.
Caso você sempre tenha o Android Studio atualizado, isso não será problema para ti, a configuração mínima exigida.
Segue:
- Android Studio a partir da versão 1.4;
- Versão Android instalada a partir da 22, bom, API 22 como recomendado, mas ande com a mais atual, que no momento da construção deste artigo era a 25;
- Gradle mais atual, recomendado a partir da versão 1.5;
- A partir do JDK 8 em uso, digo, já vinculado ao Android Studio.
Coloquei esse requerimento mínimo aqui, pois isso é enfatizado tanto na documentação da API como também no guia passo a passo dentro do dashboard de desenvolvedor.
Mas acredite, se você utiliza o Android Studio em seu ambiente de desenvolvimento, muito provavelmente já está em dia com os requisitos acima.
Caso utilize o Eclipse, ou Unity, ou o GameMaker, também é possível integrar a API.
Provavelmente você já deve ter percebido que a Calldorado somente tem suporte a aplicativos Android, é isso mesmo, devido a limitações nas outras plataformas mobile o pessoal da Calldorado API, até o momento, se limitou a somente atender aplicativos Android, nada ruim, tendo em mente que o Android é dono de 81.5% do mercado de sistemas operacionais mobile.
A partir deste ponto vamos a uma visão geral dos códigos e dashboard da API e vamos também a implementação passo a passo dela em um domínio do problema real.
Ouvindo aos eventos de carregamento de SDK
Depois da implementação da API, caso você queira tomar alguma ação de acordo com o status de carregamento da Calldorado SDK, implemente a interface CalldoradoEventsManager.CalldoradoEventCallback:
public class MainActivity extends AppCompatActivity
implements CalldoradoEventsManager.CalldoradoEventCallback {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
CalldoradoEventsManager.getInstance().setCalldoradoEventCallback(this);
}
...
@Override
public void onLoadingStarted() {
Log.i("Log", "onLoadingStarted()");
}
@Override
public void onLoadingFinished() {
Log.i("Log", "onLoadingFinished()");
}
@Override
public void onLoadingError(String s) {
Log.i("Log", "onLoadingError(): "+s);
}
}
Note que assim que é inicializado o carregamento da Calldorado SDK, na atividade principal de seu projeto, por exemplo, caso o usuário saia antes de o carregamento ter sido finalizado, é possível que ele não vá até o fim, assim a interface de identificador de chamadas ainda não será apresentada.
De qualquer forma, deixe que o carregamento do SDK seja "natural", ou seja, com o usuário entrando no aplicativo, em poucos segundos, menos de dois em meus testes. o SDK é carregado.
Não precisa forçar o usuário a aguardar o carregamento para libera-lo do aplicativo, faça isso somente se o identificador de chamadas for parte do domínio do problema do App.
Note que os métodos da Interface CalldoradoEventCallback somente são passíveis de serem utilizados no primeiro carregamento do SDK.
Configuração de tema
No box de sua App, criada no dashboard Calldorado, você tem a opção "Screen design", clique nela para ver como você pode selecionar um dos designs já prontos:
Ou então definir as cores e nome de um design (ou vários) criado por ti:
Logo depois é somente salvar e assim, nos próximos carregamentos de operação telefônica nos devices dos usuários, o novo template é que será utilizado. Esse novo design é refletido em toda UI da API, não somente em pós operações telefônicas.
Delay de inicialização de SDK
Uma das características da API Calldorado é a monetização até mesmo de usuários inativos, aqueles que ainda têm nosso aplicativo, mas pouco o utilizam.
Com isso, estudando os gráficos de seu aplicativo na Google Play Store, você entenderá quando os usuários deixam de ser ativos e assim pode a partir desse número criar algumas estratégias, como por exemplo: ativar a Calldorado API somente depois ou próximo desse período de inatividade.
No dashboard Calldorado você terá a opção "Activation timer" na App que quiser colocar o delay de inicialização. No dialog aberto você definirá a quantidade em dias de delay para a API poder ser inicializada:
Segundo meus testes, mesmo depois de remover o delay de inicialização, ou coloca-lo, os usuários que já instalaram o aplicativo e já o abriram com a configuração inicial de delay, ou não delay, não serão afetados caso você atualize essa configuração novamente.
Reengajamento de usuário, estático e dinâmico
Para maior efetividade de sua estratégia de re-ativação de user, junto com o delay de inicialização de SDK, você pode definir um botão de reengajamento, onde o usuário, na apresentação da tela de identificação de chamada, digo, na tela de pós-chamada, esse terá a opção de voltar a utilizar seu aplicativo quando tocando nesse Button:
Na tela anterior, a opção "Veja nossas novas promoções" é um botão de reengajamento, um estático, criado por mim no dashboard Calldorado.
Em um de seus Apps no dashboard, clique na opção "Engagement field", logo depois em "New field". Mantenha "Select type of field" com a opção "Over the Air Re-Engagement".
Em "Field name" coloque um nome de identificação do campo, isso para a identificação dos campos criados por ti.
Em "Deeplink to your app", caso você queira poder abrir uma atividade ou fragmento ou qualquer outro componente de acordo com a promoção de reengajamento, coloque um deeplink de seu aplicativo, digo, um que permitirá a identificação da promoção e assim a abertura do conteúdo correto.
Tenha em mente que mesmo com a definição do deeplink e você colocando a configuração correta de Intent-Filter em seu AndroidManifest.xml, mesmo assim sempre a atividade principal de abertura de sua aplicação é que será acionada.
Para saber mais sobre deeplink, estude o conteúdo a seguir: Enabling Deep Links for App Content. Ele é pequeno e vale muito a pena, você pode seguramente estuda-lo depois de terminar este artigo.
No campo "Select default text on field" escreva o rótulo que você quer que apareça no botão de reengajamento. Ou, no campo "Or enter text", defina você o rótulo.
Note que mesmo no caso da escolha sendo em inglês no campo "Select default text on field", a API tem suporte para o português brasileiro.
Por fim, dentre nove possíveis, escolha o "Field icon". Assim terá:
Marcando o campo "Activate field when saving" nós deixamos esse campo de reengajamento ativo para ele já começar a ser trabalhado nos devices que têm nosso aplicativo com a API Calldorado.
Essa opção de ativação é necessária, pois podemos ter vários campos de reengajamento, mas somente um poderá ser utilizado.
Depois de clicar em "Save", temos:
Acima a área de acesso aos campos de reengajamento criados.
O campo de reengajamento que foi ativado primeiro é o que será válido. O que fizemos até o momento foi a criação de um campo estático. Agora vamos a criação de um dinâmico.
Clicando novamente no botão "New field". Em "Select type of field" selecione a opção "Dynamic Re-Engagement".
Em "Field name" coloque o nome do campo que você também estará utilizando no Java code de seu projeto. Note que esse nome é somente para critério de identificação do botão de reengajamento, ele não terá participação da UI da API:
As configurações de: rótulo de botão; ícone e deeplink são todas definidas em tempo de execução no projeto Android.
Em algum lugar estratégico de seu aplicativo, digo, no algoritmo dele, adicione o código de criação de botão de reengajamento como a seguir:
...
Calldorado.ReEngagementField field = new Calldorado.ReEngagementField(
"re-app-dinamico",
"https://play.google.com/store/apps/details?id=br.thiengocalopsita&hl=pt_BR",
"Filmes que estão em cartaz" );
Calldorado.setupDynamicReEngagementField(this, field);
...
Os dados serão salvos em um SQLite local que é adicionado junto a API Calldorado. Note que o primeiro argumento é o nome do campo, deve ser exatamente igual ao definido no dashboard, caso contrário o Button de reengajamento não é apresentado.
Atualizando para false todos os outros fields presentes em sua App no dashboard Calldorado e então executando o aplicativo, você terá o botão sendo apresentado como definido em código.
Caso queira escolher um dos nove ícones disponíveis ou então fornecer o seu próprio (como um array de byte), há outros construtores para ReEngagementField. Siga na documentação: Documentação Calldorado, Re-Engagement Field.
Você também pode alterar o campo de reengajamento partindo de um listener de abertura de página de identificador de chamada. Para isso crie uma classe broadcast que responda a seguinte action: com.calldorado.android.intent.DYNAMIC_RE_ENGAGEMENT_SHOWN.
Veja o código de exemplo a seguir:
public class DynamicReEngagementReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if( intent.getAction().equalsIgnoreCase("com.calldorado.android.intent.DYNAMIC_RE_ENGAGEMENT_SHOWN") ){
String fieldName = intent.getExtras().getString("reEngagementName");
Calldorado.ReEngagementField field = new Calldorado
.ReEngagementField(
fieldName,
"https://play.google.com/store/apps/details?id=br.thiengocalopsita&hl=pt_BR",
"Thiengo Calopsita 2" );
Calldorado.setupDynamicReEngagementField(context, field);
}
}
}
Note que no condicional verifico qual é a action que está junto a mensagem de broadcast para assim prosseguir com o processamento.
Esse ato somente é necessário se você tiver algum broadcast a mais declarado no AndroidManifest.xml, caso contrário o condicional pode ser descartado.
Outro ponto a se notar é a captura do nome do field da mensagem broadcast disparada pela Calldorado API, no caso o field em reEngagementName.
Aqui, seguindo nosso exemplo de criação dinâmica de Button de reengajamento, o field é: re-app-dinamico.
Note que aquele primeiro código de reengajamento dinâmico sem broadcast não precisa ser apagado, podemos mante-lo.
O acesso a esse rótulo deve ser feito dessa maneira, pois não sabemos o que a API Calldorado poderá estar fazendo com o verdadeiro nome do field, hoje, com os testes, sei que ela mantém o nome que definimos no dashboard, mas pode ser que isso venha a mudar, logo, mesmo sabendo o nome do field, mantenha obtendo ele do Intent que vem como parâmetro de onReceive().
Lembrando que caso o field utilizado no Java code não corresponda ao field dinâmico criado no dashboard, o botão de reengajamento não será apresentado.
Antes de informar que você pode prosseguir com seus testes utilizando o botão dinâmico de reengajamento via BroadcastReceiver, devemos ainda atualizar o AndroidManifest.xml para que o sistema saiba dessa classe broadcast:
<receiver
android:name=".broadcast.DynamicReEngagementReceiver">
<intent-filter>
<action android:name="com.calldorado.android.intent.DYNAMIC_RE_ENGAGEMENT_SHOWN" />
</intent-filter>
</receiver>
Segundo meus teste, não é possível atualizar o Button antes de o usuário visualizar a tela final da Calldorado API, a que pode ter o anúncio e terá algumas informações e opções extras.
A atualização realizada será refletida somente na próxima tela de fim de operação telefônica.
Não apresentando banners a usuários pagos
A Calldorado API também permite que não sejam apresentados banners pós operação telefônica a usuários que pagaram para utilizar seu aplicativo.
No box do App que criou no dashboard, clique na opção "In-app product ID", no dialog que se abrir você pode adicionar o ID único de compra do usuário de seu APP e então salvar, clicando em "Add":
Esse ID é gerado para o usuário assim que ele compra o acesso ao aplicativo pela Play Store, você terá acesso a esse número em seu painel de desenvolvedor Android.
Com isso, os usuários de ID adicionados nessa parte do dashboard Calldorado, não mais terão apresentados a eles os anúncios da API.
Inserindo a funcionalidade de busca de telefone
Caso seu aplicativo precise, é possível acrescentar a ele a funcionalidade de busca de proprietário de telefone:
...
Calldorado.search(this, new CDOPhoneNumber("+551111"));
...
O telefone informado anteriormente tem também o código de país, telefone que também poderia ser escrito como: "00551111". Onde os "0055" representam o código de país. Essa é uma prática recomendada para facilitar uma busca mais precisa por parte da API Calldorado, digo, caso você precise dessa funcionalidade de busca em seu aplicativo.
Invocando o código anterior temos:
Note que somente é possível realizar 4 buscas por minuto, ou seja, muito provavelmente essa limitação vai lhe proibir de utilizar essa funcionalidade de busca direto dentro de seu App.
Ainda é possível trabalhar com um callback e então criar sua própria interface de apresentação de resultado, para mais sobre a funcionalidade de busca, acesse a documentação em: Documentação Calldorado, Search Phone In-app.
Trabalhando com target de usuário para melhorar a monetização
Para melhorar os tipos de anúncios apresentados aos usuários de seu App com a Calldorado API, é possível informar alguns dados do usuário com o método setTargetingOptions():
...
HashMap<Calldorado.TargetingOption, String> map = new HashMap<>();
map.put(Calldorado.TargetingOption.BirthDate, "2000-08-03" );
map.put(Calldorado.TargetingOption.Gender, "male" );
map.put(Calldorado.TargetingOption.Education, "high" );
map.put(Calldorado.TargetingOption.MaritalStatus, "single" );
map.put(Calldorado.TargetingOption.HouseholdIncome, "5000" );
map.put(Calldorado.TargetingOption.ParentalStatus, "childless" );
map.put(Calldorado.TargetingOption.Interests, "soccer,games" );
Calldorado.setTargetingOptions(
this,
map);
...
Aparentemente, segundo as FAQ da Calldorado, quanto maior o tempo ao qual os usuários ficam expostos aos anúncios, maior será seu ganho, devido a isso, caso você trabalhe com cadastro de usuário, forneça alguns dos dados a API, para uma apresentação mais relevante de anúncios.
Não esqueça de colocar nas políticas de privacidade de seu aplicativo que está enviando dados do usuário a uma API terceira. Fale também o motivo.
Para saber mais sobre targeting com a Calldorado API, entre na documentação dela em: Documentação Calldorado, Targeting.
Permitindo a atualização das configurações da Calldorado API dentro do aplicativo Android
Como uma recomendação de boas práticas para uso da Calldorado API, devemos permitir que dentro das configurações de nosso aplicativo o usuário também possa acessar a atividade de configurações dessa API.
Para fornecer também essa opção de atualização de configuração, somente invoque o método a seguir, isso em algum local estratégico de sua App e partindo de uma ação do usuário:
...
Calldorado.createCalloradoSettingsActivity(this);
...
Ele terá a seguinte tela para atualização da funcionalidade de identificação de chamadas:
Como informado anteriormente: a API também tem suporte ao idioma português. Mas a escolha do idioma no device do usuário é de acordo com o idioma já definido por ele no aparelho e não nas configurações da API.
Melhores práticas
Como melhores práticas temos:
- Tempo de ativação: ative a Calldorado API de acordo com o engajamento, ou perda dele, dos usuários de seu aplicativo. Então faça alguns testes com a opção de delay de inicialização de SDK para testar uma melhora dos números de retenção;
- Utilize o botão de reengajamento mesmo para usuários ativos, isso para manter o uso de seu App. Sempre que possível crie campanhas dinâmicas de acordo com o nível do usuário em relação ao engajamento em seu App;
- Não esqueça de informar aos usuários sobre a funcionalidade de identificador de chamadas e os dados compartilhados com a API dela. Incluir essa informação na descrição do aplicativo na Google Play Store e também nas políticas de privacidade de seu App;
- Desative os anúncios para usuários que pagaram para acessar o aplicativo.
Modelo de recebimento
Você pode receber sua quantia arrecadada de duas maneiras, ou por depósito direto em sua conta bancária, ou por depósito em sua conta no PayPal:
Para ambos os casos, segundo meus testes, é necessário o preenchimento da parte "Billing information". Note que o "VAT number" é somente para empresas na Europa.
Vou ser sincero contigo, não tenho expertise em recebimentos de APIs de anúncios, muito porque eu mesmo não as utilizo, mas em uma discussão com um dos seguidores do Blog / canal que têm experiência no assunto, tive como conclusão que a melhor opção para recebimento, digo, em termos de menores taxas, é via depósito bancário.
Com a Calldorado, o recebimento somente será possível se o mínimo do 100 euros de faturamento for atingido.
Com isso podemos ir ao nosso projeto de exemplo, onde simularemos a aplicação da Calldorado API em um aplicativo similar a um em produção.
Não se esqueça de se inscrever na lista de emails do Blog logo ao lado para receber os conteúdos em primeira mão.
Projeto de exemplo, Android
Como já venho fazendo em alguns artigos do Blog, vamos implementar a API Calldorado em um aplicativo similar ao que teríamos quando trabalhando em um App em produção.
O aplicativo será um de filmes em cartaz no cinema local (qualquer local). Temos a atividade principal listando os filmes e também uma activity de detalhes para a sinopse deles.
Caso já queira ter acesso a todos os arquivos, vá direto ao GitHub do projeto em: https://github.com/viniciusthiengo/cinema-local-app.
Abra o Android Studio e crie um novo projeto com uma "Empty Activity" e com o seguinte nome: Cinema Local App. Ao final dessa primeira parte teremos o seguinte aplicativo:
E a seguinte estrutura de projeto:
Configurações Gradle
Abaixo as configurações do Gradle Project Level, ou build.gradle (Project: CinemaLocalApp):
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Note que as configurações estão como as de um projeto inicial no Android Studio, você deve sempre seguir com a versão mais atual de configuração inicial, tanto para o Gradle Project Level como para o Gradle App Level.
A seguir o código do App Level, ou build.gradle (Module: app):
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "br.com.thiengo.cinemalocalapp"
minSdkVersion 10
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
testCompile 'junit:junit:4.12'
/* PARA USAR O RECYCLERVIEW E AS VIEWS DE BAR */
compile 'com.android.support:design:25.3.0'
/* CARREGAR IMAGENS REMOTAS */
compile 'com.squareup.picasso:picasso:2.5.2'
}
As libraries extras adicionadas para o correto trabalho com o domínio do problema do aplicativo foram justificadas nos comentários do código anterior.
Note que nenhuma delas é necessária para o trabalho com a Calldorado API, essa última já adicionará todas as dependências dela.
Voltaremos ao Gradle App Level para adicionarmos a API em estudo.
Configurações AndroidManifest
O AndroidManifest.xml continua muito similar a configuração original de uma novo projeto, somente adicionamos a permissão de Internet:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.cinemalocalapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="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=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Ainda voltaremos a esse arquivo para algumas atualizações.
Configurações de estilo
Todos os códigos de estilo são ainda simples como na criação de um novo projeto Android, o que fizemos foi adicionar as cores do tema e a tag que permite a trabalho com uma imagem de background logo no carregamento do aplicativo.
Vamos iniciar com o arquivo XML de definição de cores de tema, /res/values/colors.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#777777</color>
<color name="colorPrimaryDark">#222222</color>
<color name="colorAccent">#FF4081</color>
</resources>
Logo depois o de String, /res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Cinema Local App</string>
</resources>
É isso mesmo, somente uma String. Nem API KEY precisaremos adicionar ao arquivo anterior.
E por fim o arquivo de definição de estilo do tema, /res/values/styles.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:windowBackground">@drawable/background</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
Classe de domínio
Para a camada de domínio temos somente uma classe, Filme:
public class Filme implements Parcelable {
public static final String KEY = "filme";
private String nome;
private String urlImagem;
private int numSalas;
private String sinopse;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getUrlImagem() {
return urlImagem;
}
public void setUrlImagem(String urlImagem) {
this.urlImagem = urlImagem;
}
public int getNumSalas() {
return numSalas;
}
public void setNumSalas(int numSalas) {
this.numSalas = numSalas;
}
public String getSinopse() {
return sinopse;
}
public void setSinopse(String sinopse) {
this.sinopse = sinopse;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.nome);
dest.writeString(this.urlImagem);
dest.writeInt(this.numSalas);
dest.writeString(this.sinopse);
}
public Filme() {}
protected Filme(Parcel in) {
this.nome = in.readString();
this.urlImagem = in.readString();
this.numSalas = in.readInt();
this.sinopse = in.readString();
}
public static final Parcelable.Creator<Filme> CREATOR = new Parcelable.Creator<Filme>() {
@Override
public Filme createFromParcel(Parcel source) {
return new Filme(source);
}
@Override
public Filme[] newArray(int size) {
return new Filme[size];
}
};
}
A implementação da Interface Parcelable se faz necessária para que possamos enviar objetos do tipo Filme para a atividade de detalhe, isso por meio de uma Intent.
A classe Filme fica no pacote /domain do projeto.
Camada de dados
No pacote /data temos a classe Mock que é responsável pelos dados que simulam os dados que teríamos em um aplicativo em produção.
Já utilizei essa técnica de desenvolvimento várias vezes aqui no Blog. Os dados mock, ou simulados, devem representar fielmente o que teríamos em um ambiente de produção.
Segue estrutura da classe Mock:
public class Mock {
public static Filme gerarFilme(int posicao, int qtdSalas ){
String[] nomes = {
"Logan",
"A Grande Muralha",
"Cinquenta Tons Mais Escuros",
"Internet - O Filme",
"LEGO Batman: O Filme",
"BugiGangue no Espaço",
"La La Land",
"John Wick: Um Novo Dia Para Matar",
"Allied"
};
String[] imagens = {
"http://gcn.net.br/dir-arquivo-imagem/2017/02/20170228130021_64401194.jpg",
"http://imagens.cinemacomrapadura.com.br/2016/11/20161119-great-wall.jpg",
"http://br.web.img3.acsta.net/r_640_360/videothumbnails/16/12/07/17/40/499455.jpg",
"https://static.omelete.uol.com.br/media/extras/conteudos/internet-o-filme.jpg",
"http://cinequattro.com/files/2016/12/lego-batman-the-movie-dc-superheroes-unite-515f6fcf7781f.jpg",
"https://i.ytimg.com/vi/JjM0QtmjP1U/maxresdefault.jpg",
"http://www.lalaland.movie/assets/images/og.jpg",
"https://observatoriodocinema.bol.uol.com.br/wp-content/uploads/2016/09/john-wick-2-1.jpg",
"https://i.ytimg.com/vi/HSCQWX-pUSg/maxresdefault.jpg"
};
String[] sinopse = {
"Em 2024, os mutantes estão em declínio e as pessoas não sabem o motivo. Uma organização está transformando as crianças mutantes em assassinas e Wolverine, a pedido do Professor Xavier, precisa proteger a jovem e poderosa Laura Kinney, conhecida como X-23. Enquanto isso, o vilão Nathaniel Essex amplia seu projeto de destruição.",
"Um grupo de soldados britânicos está lutando na China e se depara com o início das construções da Grande Muralha. Eles percebem que o intuito não é apenas proteger a população do inimigo mongol e que a construção esconde na verdade um grande segredo.",
"Incomodada com os hábitos e atitudes de Christian Grey, Anastasia decide terminar o relacionamento e focar no desenvolvimento de sua carreira. O desejo, porém, fala mais alto e ela logo volta aos jogos sexuais do conturbado empresário.",
"Internet - O Filme traz às telas do cinema a espontaneidade das redes sociais, com suas principais estrelas. Em 8 esquetes, a comédia apresenta diferentes tramas que dialogam com situações recorrentes do dia a dia. A partir de um encontro em uma convenção de youtubers, os personagens vivenciam momentos de descobertas, ira, equívocos, raiva, inveja e confusões em busca pela fama.",
"Batman descobre que acidentalmente adotou um garoto órfão, que se torna ninguém menos que Robin. A dupla formada pelo arrogante Homem-Morcego e o empolgado ajudante deve combater o crime e prender o Coringa.",
"Enquanto Gustavinho, Fefa e os demais integrantes do clube Bugigangue estão preocupados com os trabalhos da escola, nem imaginam que em um ponto distante da galáxia o vilão Gana Golber tomou o poder da Confederação dos Planetas, ameaçando a paz do universo. Expulsos da confederação, sete Invas, alienígenas atrapalhados e ingênuos, conseguem escapar ao cerco de Gana, mas na fuga sua nave é danificada e cai na Terra. Logo os Invas fazem amizade com as crianças do clube, consertam a nave e embarcam juntos numa aventura intergaláctica para restaurar a paz do universo.",
"O pianista Sebastian conhece a atriz iniciante Mia e os dois se apaixonam perdidamente. Em busca de oportunidades para suas carreiras na competitiva cidade, os jovens tentam fazer o relacionamento amoroso dar certo enquanto perseguem fama e sucesso.",
"John Wick é forçado a deixar a aposentadoria mais uma vez por causa de uma promessa antiga e viaja para Roma, com o objetivo de ajudar um velho amigo a derrubar uma organização secreta, perigosa e mortal de assassinos procurados em todo o mundo.",
"Em uma missão para eliminar um embaixador nazista em Casablanca, no Marrocos, os espiões Max Vatan e Marianne Beausejour se apaixonam perdidamente e decidem se casar. Os problemas começam anos depois, com suspeitas sobre uma conexão entre Marianne e os alemães. Intrigado, Max decide investigar o passado da companheira e os dias de felicidade do casal vão por água abaixo."
};
Filme filme = new Filme();
filme.setNome( nomes[posicao] );
filme.setUrlImagem( imagens[posicao] );
filme.setNumSalas( qtdSalas );
filme.setSinopse( sinopse[posicao] );
return filme;
}
public static ArrayList<Filme> gerarFilmes(){
ArrayList<Filme> filmes = new ArrayList<>();
for( int i = 0; i < 9; i++ ){
int salas = 2 + (int)(Math.random() * 8);
filmes.add( gerarFilme( i, salas ) );
}
return filmes;
}
}
Lembrando que a estrutura mock de seu ambiente de desenvolvimento pode ter quantas entidades forem necessárias, aqui nós somente precisamos de uma classe.
Atividade principal
Nossa atividade principal, MainActivity, trabalha com uma lista, logo vamos iniciar com a codificação da lista, uma que utiliza o framework RecyclerView.
Vamos iniciar com o layout que é utilizado para cada item no adapter dessa lista, o /res/layout/item_filme.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="160dp">
<ImageView
android:id="@+id/iv_filme"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:scaleType="centerCrop" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:background="@drawable/sombra_item" />
<TextView
android:id="@+id/tv_nome"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/tv_qtd_salas"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:ellipsize="end"
android:maxLines="2"
android:paddingLeft="8dp"
android:textColor="@android:color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_qtd_salas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</RelativeLayout>
A seguir o diagrama do layout anterior:
Note que para termos o efeito de "sombra" nos itens da lista, estamos utilizando um drawable via tag <View>, utilizando ele como background dessa tag.
Segue código de /res/drawable/sombra_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:centerColor="#66000000"
android:endColor="#00ffffff"
android:startColor="#bb000000" />
<corners android:radius="0dp" />
</shape>
Com isso conseguimos o seguinte efeito gradiente em cada item:
Assim podemos ir ao código Java da classe adaptadora, FilmesAdapter:
public class FilmesAdapter extends RecyclerView.Adapter<FilmesAdapter.ViewHolder> {
private MainActivity activity;
private ArrayList<Filme> filmes;
public FilmesAdapter( MainActivity activity, ArrayList<Filme> filmes){
this.activity = activity;
this.filmes = filmes;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater
.from( parent.getContext() )
.inflate(R.layout.item_filme, parent, false);
ViewHolder viewHolder = new ViewHolder( view );
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setDados( filmes.get( position ) );
}
@Override
public int getItemCount() {
return filmes.size();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ImageView ivFilme;
private TextView tvNome;
private TextView tvQtdSalas;
private ViewHolder(View itemView) {
super(itemView);
ivFilme = (ImageView) itemView.findViewById(R.id.iv_filme);
tvNome = (TextView) itemView.findViewById(R.id.tv_nome);
tvQtdSalas = (TextView) itemView.findViewById(R.id.tv_qtd_salas);
itemView.setOnClickListener(this);
}
private void setDados( Filme filme ){
Picasso.with( activity )
.load( filme.getUrlImagem() )
.into( ivFilme );
tvNome.setText( filme.getNome() );
tvQtdSalas.setText( "Salas com o filme: " + filme.getNumSalas() );
}
@Override
public void onClick(View view) {
Intent intent = new Intent( activity, DetalhesActivity.class );
intent.putExtra(Filme.KEY, filmes.get( getAdapterPosition() ));
activity.startActivity( intent );
}
}
}
Código simples e com pouca lógica de negócio envolvida, na verdade há lógica somente nos métodos onClick() e setDados(). Com isso podemos seguramente ir ao código da atividade principal.
Vamos iniciar com o layout dessa atividade, /res/layout/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="br.com.thiengo.cinemalocalapp.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_filmes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
</RelativeLayout>
Segue o simples diagrama do layout anterior:
E assim prosseguirmos com o código Java inicial da MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRecycler();
}
private void initRecycler(){
RecyclerView rvFilmes = (RecyclerView) findViewById(R.id.rv_filmes);
rvFilmes.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
rvFilmes.setLayoutManager( layoutManager );
FilmesAdapter adapter = new FilmesAdapter( this, Mock.gerarFilmes() );
rvFilmes.setAdapter( adapter );
}
}
Tão simples quanto o código do adapter FilmesAdapter.
Informei "código inicial" anteriormente, pois voltaremos aqui para algumas modificações.
Atividade de detalhes
A atividade de detalhes, ou de sinopse, é ainda mais simples que a MainActivity. Vamos iniciar pelo layout, alias, a primeira parte do layout, /res/layout/content_detalhes.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.cinemalocalapp.DetalhesActivity"
tools:showIn="@layout/activity_detalhes">
<TextView
android:id="@+id/tv_sinopse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="15dp"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textSize="18sp" />
</android.support.v4.widget.NestedScrollView>
Segue diagrama do layout anterior:
Agora o layout que inclui o anterior via <Include> tag, /res/layout/activity_detalhes.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="@color/colorPrimary"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.cinemalocalapp.DetalhesActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/sombra_item" />
<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.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_detalhes" />
</android.support.design.widget.CoordinatorLayout>
A seguir o diagrama do layout principal da atividade de detalhes:
Você deve ter notado que novamente estamos utilizando a técnica de sombreamento de imagem com o arquivo/res/drawable/sombra_item.xml.
Com isso conseguimos o seguinte efeito:
Assim podemos prosseguir com o código Java de DetalhesActivity:
public class DetalhesActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detalhes);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if( getSupportActionBar() != null ){
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
Filme filme = getIntent().getParcelableExtra(Filme.KEY);
CollapsingToolbarLayout collapsing = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout) ;
collapsing.setTitle( filme.getNome() );
ImageView ivHeader = (ImageView) findViewById(R.id.iv_header);
Picasso
.with(this)
.load( filme.getUrlImagem() )
.into( ivHeader );
TextView tvSinopse = (TextView) findViewById(R.id.tv_sinopse);
tvSinopse.setText( filme.getSinopse() );
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
O que temos de fazer é somente atualizarmos as Views do layout, incluindo o carregamento remoto da imagem com a library Picasso. Como resultado, clicando nos itens de filme, temos:
Com isso podemos partir para a implementação da Calldorado API.
Implementação da Calldorado API
A partir daqui vamos a integração da API em estudo e aos testes. Nossas metas vão ser: permitir que o usuário também utilize a funcionalidade de identificador de chamadas; conseguir ganhos financeiros com a API; e também reengajar o usuário com informes sobre os filmes em cartaz. Essa última meta será atingida por meio de botões de reengajamento.
Cadastro de conta
Entre no site da API em: http://calldorado.com/. Clique em "Sign up" e então preencha todos os campos, incluindo o campo "Company", nele você pode colocar seu nome, não há problemas:
Confirme que leu os termos e condições de uso e envie o cadastro.
Logo depois você precisará entrar em seu email cadastrado para a confirmação de sua conta:
Por im, somente realize o login e Done! Você estará no dashboard:
Passo a passo de integração da API
No formulário "Create app", caso esteja seguindo o tutorial, coloque o nome do aplicativo, "Cinema Local App", e o package name dele. Logo depois clique em "Create". Você terá:
Assim clique em "Integrate". Logo depois clique em "Android Studio", caso esteja com esse IDE:
Confira que você está com os pré-requisitos todos ok e então clique em "I'm ready to go!":
Copie o código apresentado e em seguida cole-o no Gradle App Level de seu projeto, como abaixo:
...
buildscript {
repositories {
jcenter()
maven { url 'http://maven.calldorado.com/nexus/content/repositories/releases' }
}
dependencies {
classpath 'com.calldorado:calldorado-gradle-plugin:+'
}
}
apply plugin: 'calldorado'
calldorado {
accountId = "a1-ac3673e8-00da-45d9-ba76-ffde7051ca37"
appId = "b0-39cc69db-b8a6-433b-bf06-2ce4415b63ec"
}
dependencies {
compile('com.calldorado:calldorado-release:3.0.+@aar') {
transitive = true
}
}
Sincronize o projeto.
Pode ser que a sincronização não aconteça devido a conflitos de APIs sendo referenciadas diretamente no Gradle ou a partir de APIs referenciadas indiretamente.
Nesse caso notei em meu projeto que a Calldorado API também faz uso da library com.android.support:appcompat-v7, porém de uma versão ainda anterior a mais atual disponível.
O que fiz foi voltar a versão que eu estava referenciando diretamente no Gradle para a mesma versão da Calldorado API, downgrade, assim o conflito foi resolvido e o projeto sincronizado.
Agora, de volta ao dashboard, clique em "Next". Será apresentada uma tela com um texto em inglês que você deverá colocar na descrição de seu aplicativo na Play Store:
Há suporte para vários idiomas, aqui vamos ficar com a versão em português, logo, clique no Button "Download all languages". Será descarregado em sua máquina um arquivo .zip. Descompacte ele e em seguida abra o arquivo .xlsx descompactado. Busque pelo texto em "Portuguese (Brazil)". Será algo como:
O identificador de chamadas “App Name" ajuda você a identificar números em tempo real enquanto a ligação está acontecendo - mesmo os que não estão nos seus contatos. Chega de tentar adivinhar o número ou evitar ligações desconhecidas - o identificador de chamadas “App Name" prepara você para a ligação. Se a sua pizzaria não atender quando você ligar, o identificador de chamadas “App Name" irá sugerir outros lugares próximos. Você sempre pode ajustar as configurações do seu identificador de chamadas no menu de configurações.
Coloque esse texto na descrição de seu aplicativo na Play Store. Isso é necessário para que seu App não seja removido por não estar de acordo com as políticas do Google.
Agora volte ao dashboard, marque a opção "I have updated my app's description on Google Play" em seguida clique em "Next". Integração finalizada:
Agora somente clique em "Finish".
Antes de prosseguir para os testes iniciais, acesse sua atividade principal, você terá o seguinte novo código:
public class MainActivity extends AppCompatActivity {
private void initializeCalldorado() {
Calldorado.startCalldorado(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeCalldorado();
setContentView(R.layout.activity_main);
...
}
...
}
Esse é o código de inicialização da API. Lendo a documentação você vai ver que muitos outros códigos são acrescentados, mas todos estão no pacote da própria API.
Primeiros testes
Nossos testes serão no emulador, pois ele já está com a API 23 e assim poderemos visualizar a série de permissões que são necessárias para o uso da Calldorado API.
Executando o aplicativo, de início, como estamos sem trabalho de delay, temos a solicitação de permissão para identificação de chamadas telefônicas:
Apenas toque em "PERMITIR". Assim uma nova permissão será solicitada, a de gerenciamento de ligações:
Novamente, toque em "PERMITIR". A próxima permissão é para acesso a sua lista de contatos:
Toque em "PERMITIR". Agora a última solicitação de permissão In-app, a de acesso as coordenadas de localização do device:
Toque em "PERMITIR". Ainda temos de liberar que a Calldorado API consiga sobrepor Views na tela, isso para trabalhar a funcionalidade de identificador de chamadas:
Somente toque em "OK" para ser direcionado a página de liberação de permissão de overlay view:
Se acompanha o Blog, deve ter percebido que não é a primeira vez que trabalhamos com essa solicitação de overlay view permission. No artigo Como Colocar Notificações Bolha em Seu Aplicativo Android utilizamos essa mesma permissão.
Assim, voltando ao aplicativo, temos:
Tudo permitido e configurado. Podemos partir para um ligação de teste, exatamente como apresentado na documentação da API, ligação para o número 1111:
O Button "Arrastar" deixe você posicionar a float window em qualquer lugar da tela. O Button "Dispensar" permiti encerrar a ligação caso arrastando para a esquerda.
Finalizando a ligação, temos:
Essa tela de informe sobre a funcionalidade de identificador de chamadas e também sobre algumas regras necessárias que o usuário deverá estar de acordo para continuar utilizando essa funcionalidade. Essa tela é apresentada somente na primeira vez que a API Calldorado é ativada para o uso do identificador de chamadas.
A janela anterior também fala para o usuário sobre a possibilidade dele desativar a funcionalidade de identificação de chamadas acessando as configurações dela.
O usuário clica em "OK" e terá uma próxima tela com algumas informações a mais sobre o local que ele ligou, caso tenha dados desse local nas bases de acesso da Calldorado API:
Em alguns casos pode ser que alguns locais próximos e do mesmo tipo do local em ligação anterior sejam apresentados. Caso você clique no ícone de telefone dessas alternativas, a ligação ocorrerá.
Caso clique no ícone de telefone acima das tabs "Alternativas" e "Detalhes" a ligação será refeita ao número tentado anteriormente.
Caso clique no balão de comentário, será aberta ou uma Window com as Apps que permitem o envio de mensagem de texto para o número atual ou, caso somente tenha uma App, o aplicativo de mensagens de texto será aberto imediatamente.
Clicando no disquete (não sei se é de seu tempo) você terá a opção de salvar o número em sua agenda. Lembrando que a API cumpriu o que prometeu, identificou o número na chamada.
Clicando na engrenagem, no topo superior direito, temos:
Exatamente a tela de configurações da API, o usuário poderá atualizar de acordo com as preferências dele, incluindo a remoção completa, desativação de todas as opções, da funcionalidade de identificação de chamadas.
Posteriormente vamos permitir que o usuário também acesse essa tela de dentro de nosso aplicativo.
Note que a todo momento, mesmo que nosso aplicativo não tenha sido aberto, o ícone e nome dele fica no topo das janelas da Calldorado API.
Agora feche a tela de configuração e então clique na tab "Detalhes":
Na primeira opção, com o ícone de pin do mapa, temos a localização do local do número em ligação. Clicando nessa opção serão apresentadas as Apps que podem trabalhar com mapa, caso somente tenha uma, o mapa será apresentado diretamente.
Clicando na segunda opção, com o ícone de estrela, podemos avaliar o local, por isso que há algumas estrelas abaixo do nome do local que ligamos (ou recebemos a ligação):
A terceira e última opção é referente ao envio de email a entidade que estava na ligação, aqui uma pizzaria em Berlim. Como em outras opções, um aplicativo de envio de email deverá ser escolhido.
Com isso finalizamos a integração da API em nosso App, podemos seguramente coloca-lo na Play Store, assumindo que a política de privacidade foi atualizada e que foi adicionado o texto correto a descrição do aplicativo.
Nas próximas seções vamos a algumas atualizações para melhor apresentar a funcionalidade de acordo com as características de nosso aplicativo de cinema.
Atualizando a interface da funcionalidade de identificação de chamadas
No dashboard Calldorado, vá até o item de sua aplicação criada e clique no Button "Screen designer". Haverá um combo box, ou select, onde você poderá escolher um design de acordo com o tema de seu aplicativo:
Aqui escolhi a opção "Dark Gray/White", pois responde melhor as cores do tema do aplicativo de exemplo.
Note que além das opções prontas, você também pode selecionar a opção "+ New template" e assim construir seu próprio tema e, acredite, a construção do novo tema se resumirá em definição de cores:
Voltando a opção anterior, para seguirmos com o exemplo, volte a seleção em "Dark Gray/White" e clique em "Save design".
Tente uma nova ligação e em seguida encerre-a, assim teremos:
Nada temos de fazer no código Java de nosso projeto, apenas atualizações no dashboard Calldorado. Agora vamos ao menu de configurações.
Colocando a opção de atualização de configuração da Calldorado API
O código de ativação de atividade de configuração da Calldorado API é simples, aqui vamos coloca-lo junto ao algoritmo de menu do Android. Como nosso aplicativo ainda não tem um, vamos coloca-lo por completo.
Primeiro crie um folder /menu dentro do folder /res. Em seguida adicione o seguinte arquivo XML, menu.xml, a esse novo folder:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/calldorado_settings"
android:orderInCategory="100"
android:title="Configurações do identificador de chamadas"
app:showAsAction="never" />
</menu>
Assim vamos ao código da MainActivity, adicione os trechos em destaque:
public class MainActivity extends AppCompatActivity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.calldorado_settings){
Calldorado.createCalloradoSettingsActivity(this);
}
return super.onOptionsItemSelected(item);
}
}
Executando o aplicativo e então clicando na opção de menu "Configurações do identificador de chamadas" temos:
Altere as configurações que quiser e então realize alguns testes para ver o novo funcionamento da API.
Criando um campo estático de reengajamento de usuário
Acesse o dashboard Calldorado e no item de seu aplicativo clique em "Engagement field", logo depois clique em "+ New field".
Em "Select type of field" continue com a opção "Over the Air Re-Engagement". Em "Field name" coloque "re-app-ultimo-lancamento". Em "Deeplink to your app" coloque: android-app://br.com.thiengo.cinemalocalapp/lancamento.
Note que em deeplink, onde coloquei br.com.thiengo.cinemalocalapp você deve colocar o package name de seu aplicativo.
Mas lhe adianto que mesmo sem o package name o botão de reengajamento funcionará sem problemas. Estamos utilizando o package name somente para aumentar a exclusividade do deeplink.
Em "Or enter text" coloque "Veja o último lançamento". Em "Field icon" escolha a seta, "Arrow".
Marque a opção "Activate field when saving" e por fim clique em "Save":
Assim podemos ir as alterações do código Java da MainActivity para que a último filme acrescentado ao App seja o apresentado, digo, a sinopse dele. Segue em destaque o código adicionado:
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate() {
...
Uri data = getIntent().getData();
if( data != null ){
String host = data.getHost();
String path = data.getPath();
if( host.equalsIgnoreCase( getPackageName() )
&& path.equalsIgnoreCase("/lancamento") ){
/*
* NOTE QUE O ÚLTIMO FILME ADICIONADO É O PRIMEIRO
* DA LISTA DE FILMES, ENTÃO O ACESSO A ELE É VIA
* POSIÇÃO 0.
* */
Intent intent = new Intent(this, DetalhesActivity.class);
intent.putExtra( Filme.KEY, Mock.gerarFilmes().get( 0 ) );
startActivity( intent );
}
}
}
}
Note que para verificar se o path é correto, isso de acordo com o deeplink informado em dashboard, devemos também utilizar a barra, "/lancamento".
Execute o aplicativo, logo depois feche ele e realize uma ligação, encerre a ligação e vá a aba de "Detalhes" da API de identificação de chamadas. Agora clique em "Veja o último lançamento", assim temos:
Sempre somente a sinopse do primeiro filme da lista é que será aberta. Vamos aprimorar esse código com o uso do campo de reengajamento dinâmico.
Criando um campo dinâmico de reengajamento de usuário
Nosso objetivo com o reengajamento dinâmico é permitir que o usuário seja apresentado a somente opções de sinopse, filmes, que ele ainda não tenha visto em nosso aplicativo de cinema.
Para isso precisaremos trabalhar também com persistência local, aqui vamos utilizar o SharedPreferences como base local, isso, pois nosso domínio e algoritmo são bem simples.
Antes vamos ao dashboard Calldorado para criarmos um novo campo de reengajamento, dessa vez deixe o campo "Select type of field" com a opção "Dynamic Re-Engagement" selecionada.
No campo "Field name" coloque o seguinte nome: "re-app-filme-nao-visualizado". Assim, marque a opção "Activate field when saving" e por fim clique em "Save":
Não esqueça de editar o último campo estático de reengajamento que criamos na seção anterior, edita-lo para que permaneça inativo, ou seja, desmarque a opção "Activate field when saving" e salve a edição.
Agora vamos as atualizações no código Java. No package /data crie uma nova classe, SPFilme:
public class SPFilme {
private static final String SP_NOME = "pref";
public static void saveFilmeVisualizado(Context context, String filme){
SharedPreferences sp = context.getSharedPreferences( SP_NOME, Context.MODE_PRIVATE );
sp.edit().putBoolean( filme, true ).apply();
}
public static boolean hasFilmeParaVisualizar(Context context, List<Filme> filmes){
SharedPreferences sp = context.getSharedPreferences( SP_NOME, Context.MODE_PRIVATE );
for( Filme f : filmes ){
boolean hasFilme = sp.getBoolean( f.getUrlImagem(), false );
if( !hasFilme ){
return true;
}
}
return false;
}
public static Filme getFilmeMaisAtualNaoVisualizado(Context context, List<Filme> filmes){
SharedPreferences sp = context.getSharedPreferences( SP_NOME, Context.MODE_PRIVATE );
for( Filme f : filmes ){
boolean hasFilme = sp.getBoolean( f.getUrlImagem(), false );
if( !hasFilme ){
return f;
}
}
return null;
}
}
Essa classe é responsável por gerenciar se devemos ou não criar um botão de reengajamento para o usuário, e caso sim, com o filme que deve ser utilizado na criação desse botão. Sempre utilizaremos um filme que o usuário ainda não tenha visualizado a sinopse.
Nossa primeira atualização já utilizando a nova classe de persistência, SPFilmes, será na atividade de detalhes. Nessa atividade devemos já salvar que o usuário visualizou a sinopse do filme atual. Segue código atualizado de DetalhesActivity:
public class DetalhesActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
SPFilme.saveFilmeVisualizado( this, filme.getUrlImagem() );
}
...
}
Estamos utilizando a URL da imagem do filme para identifica-lo, pois com esse dado temos certeza de que não haverá caracteres especiais.
Assim podemos voltar a MainActivity para no método onResume() criarmos um campo de reengajamento com algum filme de sinopse ainda não visualizada. Segue:
public class MainActivity extends AppCompatActivity implements CalldoradoEventsManager.CalldoradoEventCallback {
private ArrayList<Filme> filmes;
...
private void initRecycler(){
filmes = Mock.gerarFilmes();
RecyclerView rvFilmes = (RecyclerView) findViewById(R.id.rv_filmes);
rvFilmes.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
rvFilmes.setLayoutManager( layoutManager );
FilmesAdapter adapter = new FilmesAdapter( this, filmes );
rvFilmes.setAdapter( adapter );
}
@Override
protected void onResume() {
super.onResume();
String path = null;
String fieldLabel = null;
if( SPFilme.hasFilmeParaVisualizar( this, filmes ) ){
Filme filme = SPFilme.getFilmeMaisAtualNaoVisualizado( this, filmes );
path = "android-app://br.com.thiengo.cinemalocalapp/" + filme.getUrlImagem();
fieldLabel = "Sinopse " + filme.getNome();
}
Calldorado.ReEngagementField field = new Calldorado.ReEngagementField(
"re-app-filme-nao-visualizado",
path,
fieldLabel );
Calldorado.setupDynamicReEngagementField(this, field);
}
...
}
Primeiro: a lista de filmes agora é uma variável de instância, isso, pois teremos de acessa-la também no onResume().
Segundo: caso não haja algum filme de sinopse ainda não visualizada, persistimos com o mesmo filed name, porém com os argumentos path e fieldLabel como null, assim o campo, caso já tenha sido criado, será desativado e nada de reengajamento será apresentado ao usuário até que um novo filme seja adicionado ao aplicativo.
Estamos utilizando a lógica de negócio de criação de botão de reengajamento no método onResume(), pois temos certeza de que esse método será invocado sempre que o usuário acessar a página de detalhes de algum filme e em seguida voltar a atividade principal, assim, se esse último filme de sinopse visualizada for o mesmo do último botão de reengajamento criado, esse botão será atualizado para um outro filme ou então removido, caso o usuário já tenha visto todas as sinopses.
Agora devemos atualizar o código no onCreate() da MainActivity para responder ao clique no botão dinâmico de reengajamento. Segue em destaque o código:
public class MainActivity extends AppCompatActivity
{
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Uri data = getIntent().getData();
if( data != null ){
String path = data.getPath();
path = path.substring(1, path.length());
Filme filme = getFilmeByUrlImagem( path );
if( filme != null ){
Intent intent = new Intent( this, DetalhesActivity.class );
intent.putExtra(Filme.KEY, filme);
startActivity( intent );
}
}
}
...
private Filme getFilmeByUrlImagem( String urlImagem ){
for( Filme f : filmes ){
if( f.getUrlImagem().equalsIgnoreCase( urlImagem ) ){
return f;
}
}
return null;
}
}
Sabendo que o path de nosso deeplink é a URL da imagem do filme de sinopse ainda não visualizada, criamos um método, getFilmeByUrlImagem(), que permiti o acesso a ele.
Agora podemos ir aos testes. Execute o aplicativo, feche-o, realize uma ligação e finalize ela. Depois clique em "Sinopse Logan":
Antes de deixar o aplicativo, volta a lista de filmes e acesse a sinopse do filme "A Grande Muralha". Agora deixe o App e realize os mesmos passos de ligação e acionamento de botão de reengajamento realizados anteriormente. Assim teremos:
Como o "A Grande Muralha" já tinha sido visualizado, o próximo filme mais atual ainda não visto era o "Cinquenta Tons Mais Escuros". Assim temos que nossa lógica de negócio funciona sem problemas.
Caso em seu ambiente de desenvolvimento você entre em todos os filmes, digo, nas sinopses deles, nenhum botão de reengajamento será apresentado até que um novo filme seja adicionado.
Note que não utilizamos a criação de botão de reengajamento também via broadcast, pois devido ao código no onResume() e sabendo que quando não há esse tipo de botão na tela a action "com.calldorado.android.intent.DYNAMIC_RE_ENGAGEMENT_SHOWN" não é acionada, devido a esses fatores o código via broadcast como apresentado na documentação da Calldorado API não teria validade alguma aqui.
Assim finalizamos a implementação da API em estudo. Não esqueça de se inscrever na 📩 lista de e-mails do Blog para receber conteúdos em primeira mão e também de assinar o canal do Blog em: Thiengo [Calopsita] YouTube Channel.
Pontos negativos
- A partir da API 23, Marshmallow, uma série de permissões é apresentada ao usuário, isso sem nós, developers, podermos informar o porquê delas, a solicitação é com o texto comum de uma solicitação de permissão no Android, ou seja, sem mais explicações de o porquê estarmos solicitando acesso a permissão de localização, por exemplo, em um aplicativo de sinopses de filmes em cinema;
- Não há modo de testes para que possamos visualizar como seria com os anúncios sendo apresentados;
- A documentação sobre a opção "In-app product ID" não existe, não é claro o que seria esse ID e como poderíamos utiliza-lo para não mais apresentar banners a usuários que pagaram pelo App.
Pontos positivos
- Uma maneira de monetizar que não influência na "não utilização" de outras APIs de anúncios mais convencionais, APIs que trabalham anúncios dentro do aplicativo;
- Adição de uma funcionalidade que é útil ao usuário, até porque todo mundo quer saber quem está na chamada atual;
- Possibilidade de reengajamento e monetização de usuário inativo;
- API de fácil integração.
Outras APIs de monetização no Android
Aqui no Blog já falamos sobre outras APIs de monetização, vale a pena o estudo, ainda mais sabendo que essas podem trabalhar em conjunto com a Calldorado API:
- Estratégia de Anúncios com In Loco Media no Android;
- Appnext para Monetizar sua APP Android;
- Monetização Eficiente no Android com APPODEAL;
- Monetizando Sua APP Android Com AppJolt no Uninstall;
- Monetização sem Anúncios utilizando a Huq SDK.
Vídeo com implementação passo a passo da API
Abaixo o vídeo com a implementação passo a passo da Calldorado API no aplicativo de Cinema:
Para acesso ao conteúdo completo do projeto, entre no seguinte GitHub: https://github.com/viniciusthiengo/cinema-local-app.
Conclusão
Para ampliação da monetização de seus aplicativos Android e também para a adição de uma funcionalidade que é útil ao usuário, tendo ainda em mente que essa API permitirá a monetização de até mesmo usuários inativos. Implementar a Calldorado API pode ser uma excelente opção.
Com a configuração certa, até mesmo o trabalho com o delay em dias, é possível adequar a API de acordo com a maneira onde você mais conseguirá reter os usuários ativos de seu App.
Adeque também o design da API e trabalhe com o reengajamento dinâmico, que para mim é a melhor opção ante ao estático, para sempre estar apresentando uma novidade ao usuário, isso depois das operações telefônicas.
O maior receio com essa API é realmente a aceitação por parte dos usuários, ainda mais em saber que não conseguimos colocar as explicações do porquê da solicitação de tantas permissões.
Mas o que também temos de ter em mente é que a empresa foi fundada em fevereiro de 2016 e no dia de hoje já tem mais de 50 colaboradores, isso devido aos resultados positivos gerados.
Então podemos sim dar um voto de confiança a documentação da API quando essa diz que a funcionalidade de identificação de chamadas pode até mesmo aumentar o número de usuários de seu App.
Somente não esqueça de acrescentar, nas políticas de privacidade do aplicativo, a seção sobre o trabalho com a Calldorado API e sobre a funcionalidade de identificação de chamadas acrescentada, mesmo essa saindo consideravelmente do domínio do problema do aplicativo.
Deixe seu comentário caso tenha alguma dúvida ou queira acrescentar algo e não se esqueça de se inscrever na 📩 lista de e-mails do Blog, logo ao lado ou abaixo.
Abraço.
Comentários Facebook