Trabalhando Análise Qualitativa em seu Aplicativo Android

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 /Trabalhando Análise Qualitativa em seu Aplicativo Android

Trabalhando Análise Qualitativa em seu Aplicativo Android

Vinícius Thiengo
(4771) (3)
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ção1ª
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, passo a passo, ao estudo e a aplicação de um SDK para análise qualitativa de aplicativos Android, mais precisamente o SDK AppSee.

Como projeto de exemplo, depois da apresentação do SDK, teremos um aplicativo de mobile-commerce, uma simulação de app de venda de tênis, para que possamos estudar os insights oferecidos pelo AppSee:

Dashboard AppSee

Antes de prosseguir, não esqueça de se inscrever 📩 na lista de e-mails do Blog para receber os conteúdos exclusivos e em primeira mão.

Caso você queira algo rápido, somente o conteúdo sobre o SDK em estudo, sem uso de projeto de exemplo, ao final do artigo tem a versão dele em slides. Caso prefira o conteúdo em vídeo, logo depois dos slides está o vídeo.

Abaixo os tópicos que estaremos abordando:

Análise de dados via SDK mobile

Apesar do título, é comum termos SDKs de análise de dados que atendem também a aplicativos Web, um bem popular é o Google Analytics, que na versão Android mais atual vem integrado ao conjunto de APIs do Firebase.

De qualquer forma, você deve ter a seguinte dúvida em mente: para quem esse tipo de SDK é importante?

Para aqueles que querem trabalhar em aplicativos com objetivos de longo prazo.

Como assim?

SDKs de análise de dados lhe dão insights sobre o que melhorar em seu app e muitas vezes esses insights não são precisos, ou seja, você prossegue realizando modificações e então verificando se as conversões desejadas (vendas, cadastros, ...) aumentaram ou não.

Resumo: SDKs de análise de dados vão ajudar, ao longo do tempo, a construir um melhor aplicativo para os usuários alvo.

Com esse resumo podemos assumir que empresas e desenvolvedores focados em construir muitos aplicativos semanalmente para lucro com anúncios, por exemplo, tendem a não ter muitos benefícios com SDKs de análise de dados, isso, pois o foco deles acaba sendo o próximo aplicativo, curto prazo, que possivelmente tem um público alvo distinto dos já desenvolvidos anteriormente.

Para aqueles que têm foco no longo prazo e querem trabalhar melhor a evolução do aplicativo, não somente baseando-se em comentários de feedback, há dois principais ramos de análise de dados que SDKs como AppSee e Google Analytics fornecem: análise quantitativa e análise qualitativa.

Análise quantitativa e análise qualitativa

Análise quantitativa

No contexto de dados, de SDKs de análise de software, estamos falando de variáveis numéricas que apenas indicam se determinada alteração melhorou ou não as conversões desejadas, variáveis como:

  • Tempo de retenção;
  • Acessos diários;
  • Quantidade de usuários por etapa de fluxo definido;
  • entre outros.

O método quantitativo não é inferior ao método qualitativo, ambos se completam. Porém somente o quantitativo não é o suficiente para fornecer insights precisos sobre "aonde melhorar o aplicativo".

Somente com o quantitativo ainda temos de "martelar", testar na força bruta, até encontrar um ponto de atualização que aumentou as conversões.

Análise qualitativa

A análise qualitativa permite a descoberta de insights que estavam escondidos sob o mar de dados numéricos do método quantitativo.

Exemplo: na análise quantitativa podemos obter o passo, do fluxo definido, onde os usuários abandonam a compra em nosso mobile-commerce. Juntamente a análise qualitativa podemos ver o que os usuários fazem nessa tela de maior índice de abandono, se é a cor de um botão que aciona o escape ou um texto um pouco ofensivo ao cliente que faz o abandono crescer.

Com a gravação em vídeo do fluxo de alguns usuários é possível saber até mesmo onde ele tocou na tela antes de deixar o fluxo de compra. A gravação em vídeo de fluxo é uma característica da análise qualitativa.

Resumo: a análise qualitativa diminui consideravelmente o número de "marteladas" que teremos de dar em nosso aplicativo para conseguir aumentar as conversões nele.

AppSee

AppSee é uma empresa de análise de dados de aplicativos mobile, uma startup sediada em Nova Iorque que tem como propósito oferecer uma ferramenta completa que permita, a nós desenvolvedores, tomarmos melhores decisões sobre a evolução do aplicativo, decisões baseadas no comportamento dos usuários, comportamento obtido via ferramentas de análise quantitativa e qualitativa.

Um ponto importante a ressaltar é que o AppSee tem uma versão gratuita de seu SDK de análise, atendendo a aplicativos Android (a partir da versão 2.2) e a aplicativos iOS. Porém até mesmo para o plano gratuito (que é o que eu recomendo você iniciar o uso do SDK e é o que utilizaremos aqui) é possível, por 14 dias, utilizar todas as funcionalidades de análise do AppSee.

Ok, mas qual a real vantagem do AppSee ante a outros SDKs de análise de dados?

Primeiro, a disponibilidade de plano gratuito, que já contém o mínimo necessário de análise qualitativa. Segundo, que podemos integrar o AppSee a muitos outros SDKs de análise, incluindo o Google Analytics.

Terceiro e provavelmente o mais importante, nós conseguimos, via dashboard Web, obter todos os dados de análise quantitativa e qualitativa de maneira trivial, necessitando como configuração, no app, de apenas uma linha de código.

A seguir vamos a apresentação de algumas das principais funcionalidades que temos disponíveis, até mesmo com o plano gratuito, logo depois vamos a instalação e configuração do SDK, por fim vamos ao projeto de exemplo.

Gravações, em vídeo, do roteiro dos usuários

Provavelmente a funcionalidade de destaque do SDK. Exatamente a funcionalidade em ação na primeira animação deste artigo.

Para alguns usuários o AppSee SDK vai gravar o roteiro deles pelo app: onde realizaram touch; swipe; telas navegadas; em qual ponto do formulário pararam de preenche-lo; entre outros registros.

Esses dados ajudam nas respostas de perguntas como:

  • O que está fazendo os usuários abandonarem o cadastro?
  • Por que os usuários utilizam o aplicativo apenas uma vez e não mais retornam?
  • O que o usuário está fazendo para que o aplicativo falhe em determinado ponto?

Com vídeo será possível ver algo que somente dados numéricos não mostram, por exemplo: usuários que preenchem apenas parte do cadastro e então param, isso pode ser um forte indicio para a reformulação de todo o fluxo de cadastro do app.

É possível também definir parâmetros para que as seções com gravações em vídeo aconteçam somente para usuários que respeitem os parâmetros escolhidos, como: por quais telas o user tem de passar antes de a gravação ser iniciada.

As gravações são feitas em background e não pesam o aplicativo, nem mesmo necessitam de permissões e algoritmos extras por parte dos developers do app. A seguir um pequeno vídeo que mostra a funcionalidade de video recording em ação:

Ressaltando que somente alguns usuários terão também seções em vídeo. Mas todos terão suas seções gravadas em formato de texto. No dashboard AppSee também é possível definir a qualidade de vídeo e porcentagem de usuários que poderão ter vídeos gravados.

Todos os dados salvos pelo AppSee, mesmo em vídeo, não permitem a identificação de qualquer usuário, algo exigido pelas políticas de privacidade do Google Android.

Quadros de calor para toques em tela, heatmaps

Funcionalidade bem conhecida para aqueles que vêm da análise de dados de páginas Web. Característica também presente no SDK em estudo.

O AppSee colhe como touch qualquer toque em tela, incluindo o swipe de transição de telas ou de itens de lista. Com essa funcionalidade é possível saber sobre, por exemplo:

  • Botões e links do app que não estão funcionando, algo frustrante para qualquer usuário. O dashboard do SDK vai alertar o developer sobre problemas deste tipo;
  • Quais opções de login são mais utilizadas (por que continuar com os botões do GoogleTwitter sendo que 99.9% dos acessos são via login nativo ou via login com Facebook?);
  • Se os anúncios de fundo de tela estão sendo acionados;
  • entre outros.

Novamente, o SDK coleta tudo em background, sem necessidade de configurações extras. A seguir um pequeno vídeo da funcionalidade heatmap em ação:

Relatório de falhas com vídeo

Quando obtemos a pilha de erro de uma determinada falha, muitas vezes ainda é preciso simular o que pode ter ocorrido, até mesmo criar um emulador que represente fielmente o tipo de device e o SO Android que teve o problema.

A ferramenta de crash reporting do AppSee automaticamente salva a seção que teve problema, incluindo o vídeo dela, dessa forma podemos, via dashboard, acessar o vídeo e então atacar direto o código do trecho problemático, incluindo a simulação do evento que gerou a falha, para garantirmos que ela não voltará a ocorrer.

A seguir um pequeno vídeo com a funcionalidade de crash recording sendo utilizada:

Cadastro e inscrição de aplicativo

Na home do site clique em "START FREE TRIAL". Preencha todos os dados da página aberta. Em "Company email" você pode utilizar o seu email pessoal. Clique em criar conta.

Assim, na página de usuário logado, clique em "Add Your First App". Coloque o nome real do aplicativo e não o package name dele.

Logo depois marque o checkbox "Create an additional API key for pre-launch/development" para que você possa utilizar uma chave de API de testes em seu ambiente de desenvolvimento.

Cadastro de app no AppSee

Certifique-se de ler os termos e condições de uso, principalmente a parte de que você concorda em "não enviar nenhum dado sensível de usuário para os servidores do AppSee". Então clique em "Agree & Add App".

Depois do novo carregamento, certifique-se de, no topo esquerdo da tela, selecionar "Nome do App (Test)" para obter a chave de API do AppSee para o ambiente de desenvolvimento de seu aplicativo.

Seleção de app no AppSee

Quando for assinar seu app para envia-lo a Play Store, obtenha a outra chave de API, a da opção "Nome do App".

É possível ter mais de um aplicativo cadastrado e sempre haverá a conta do app em produção e a do app em ambiente de desenvolvimento caso o checkbox "Create an additional API key for pre-launch/development" tenha sido marcado no cadastro do aplicativo.

Instalação do SDK

Depois do cadastro, a integração do AppSee SDK a aplicativos Android é simples, precisamos apenas definir a referência ao SDK no Gradle App Level, ou build.gradle (Module: app):

dependencies {
implementation 'com.appsee:appsee-android:+'
}

 

E então colocar a linha de código a seguir no método onCreate() de todas as atividades de entrada ao aplicativo:

...
override fun onCreate(savedInstanceState: Bundle?) {
...
Appsee.start("Sua_chave_de_api_fornecida_pela_appsee_no_cadastro_do_app");
}
...

 

Atividades de entrada?

Sim, são aquelas atividades que permitem a abertura do aplicativo. Em um novo projeto no Android Studio uma atividade assim é a MainActivity (se você não alterou o nome dela). Essas atividades de abertura comumente têm, nas tags delas no AndroidManifest.xml, um IntentFilter.

Apesar de na documentação oficial não falar nada sobre, é esperado que você tenha adicionado em seu AndroidManifest.xmlpermissão de Internet:

...
<uses-permission android:name="android.permission.INTERNET" />
...

Proguard

Caso você esteja utilizando Proguard, terá de acrescentar a seguinte configuração ao arquivo proguard-project.txt:

-keep class com.appsee.** { *; }
-dontwarn com.appsee.**
-keep class android.support.** { *; }
-keep interface android.support.** { *; }
-keepattributes SourceFile,LineNumberTable

 

E ainda em proguard-project.txt remover a seguinte linha caso ela esteja presente:

-renamesourcefileattribute SourceFile

 

A partir daqui já é seguro testar o aplicativo por alguns segundos, minimiza-lo em background e, depois de aproximadamente cinco minutos, acessar o dashboard Web AppSee para ver alguns dados colhidos e apresentados de maneira simples.

Note que a gravação de vídeos somente ocorre a partir da versão 4.1 do Android, Jelly Bean, e tem de ser em devices reais, a gravação não ocorre em emuladores.

Outra informação importante é que a gravação de sessão, em vídeo ou em texto, inicia-se com a abertura do app e finaliza com alguma falha ou a ida dele para o background.

Configurações avançadas com a API do SDK

Depois de alguns testes com a API do AppSee SDK é possível que você queira melhorar ainda mais os insights, definindo somente alguns pontos para gravação de sessão de usuário, por exemplo.

Para isso, e também para outras configurações, há algumas outras chamadas de API, todas bem simples, que você pode estar colocando no aplicativo:

Outras funcionalidades

Ainda somente com a simples configuração de uma linha código, temos também as seguintes funcionalidades disponíveis:

Pontos negativos

  • Devido ao AppSee estar ainda entrando no Brasil, há poucas páginas em português, ao menos até a construção deste artigo;
  • Os planos que não são gratuitos somente têm os preços revelados depois de nós usuários conversarmos com a equipe de suporte e vendas do AppSee;
  • A equipe que entra em contato conosco, via ligação telefônica, somente fala em inglês, ao menos até a construção deste artigo. Acredite, eles vão te ligar, com DDI +1.

Pontos positivos

  • A leitura dos dados e insights no dashboard é bem simples;
  • A principio é o único SDK de análise qualitativa que contém registro em vídeo;
  • Tem plano gratuito;
  • Dá direito a 14 dias de uso de todas as funcionalidades, com custo zero;
  • A configuração do SDK em apps Android é de apenas uma linha de código;
  • Permite análise em tempo real dos dados do aplicativo;
  • Permite integração com vários outros SDKs de análise de dados;
  • A documentação é completa e com exemplos;
  • Há inúmeros tutorias e e-books para ajudar na análise mais aprofundada dos dados partindo do dashboard AppSee.

Considerações finais

Como informado nas seções iniciais do artigo: SDKs de análise de dados são realmente úteis para empresas e developers que querem evoluir o aplicativo deles no longo prazo, não somente baseando-se em comentários da página do app, indo na "martelada", mas também com auxílio de SDKs de análise quantitativa e qualitativa.

Com isso seguramente recomendo o uso também do AppSee, primeiro devido a qualidade e facilidade de uso do SDK e também por este nos permitir ter acesso a um plano gratuito.

A partir da próxima seção vamos a aplicação do AppSee SDK, em um app que simula um mobile-commerce, para posteriormente estudarmos alguns dados colhidos.

Projeto Android

Para um teste real do SDK do AppSee utilizaremos um mini aplicativo de venda de tênis, um m-commerce, simulando algumas telas que poderão mostrar muito do comportamento do usuário no dashboard AppSee.

Vamos dividir o conteúdo do aplicativo de vendas em duas partes: na primeira, sem o SDK do AppSee; na segunda, já com o SDK e testes sendo realizados.

Note que apesar do projeto completo ser apresentado aqui em artigo, você também pode obtê-lo, somente com comentários em código, diretamente no GitHub dele em: https://github.com/viniciusthiengo/tenis-shop-app-see.

Mesmo com o código do projeto disponível no GitHub, recomendo que acompanhe todo o artigo para ter uma melhor compreensão de uso e impacto do AppSee SDK em um aplicativo real. O insight obtido no aplicativo de exemplo será melhor e bem diferente do que estávamos buscando.

Protótipo estático

A seguir as imagens do protótipo estático, desenvolvido antes mesmo de iniciar um novo projeto no Android Studio:

Tela de entrada

 Tela de entrada

Tela de login

 Tela de login

Tela de cadastro

Tela de cadastro

Tela de cadastro (tooltip aberto)

Tela de cadastro (tooltip aberto)

Tela de listagem de tênis

Tela de listagem de tênis

Tela de detalhes de tênis

Tela de detalhes de tênis

 Dialog de pagamento

 Dialog de pagamento

 Tela de "obrigado pela compra"

 Tela de "obrigado pela compra"

Assim podemos partir para a criação e codificação do projeto Android.

Iniciando o projeto

Com o Android Studio aberto, inicie um novo projeto Kotlin (pode seguir com um projeto Java, caso prefira). Coloque como nome "Tênis Shop". Como API mínima escolha o Android Jelly Bean, API 16. Como atividade inicial escolha a "Empty Activity".

Ao final desta primeira parte teremos a seguinte arquitetura de projeto:

Estrutura do app Tênis Shop no Android Studio

Note que todas as imagens utilizadas no app estão disponíveis no GitHub do projeto, link compartilhado anteriormente.

Configurações Gradle

A seguir as configurações iniciais do Gradle Project Level, ou build.gradle (Project: TenisShop):

buildscript {
ext.kotlin_version = '1.2.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

Então as configurações iniciais do Gradle App Level, ou build.gradle (Module: app):

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 26
defaultConfig {
applicationId "thiengo.com.br.tenisshop"
minSdkVersion 16
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'

/* PARA TOOLTIP EM SIGNUPACTIVITY */
implementation 'it.sephiroth.android.library.targettooltip:target-tooltip-library:1.3.15'
}

 

Note que já iniciamos o Gradle App Level com uma referência externa a API Android-Target-Tooltip, isso, pois a API nativa para tooltip não nos dava opção de tooltip em ImageView, ao menos até a construção deste artigo.

Configurações AndroidManifest

Abaixo as configurações iniciais do AndroidManifest.xml, note que teremos muitas atividades neste projeto de comércio eletrônico:

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

<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=".LoginActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".SignUpActivity"
android:label="@string/title_activity_sign_up"
android:parentActivityName=".LoginActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="thiengo.com.br.tenisshop.LoginActivity" />
</activity>

<activity
android:name=".SneakersActivity"
android:label="@string/app_name" />

<activity
android:name=".SneakerDetailsActivity"
android:label="@string/title_activity_sneaker_details"
android:parentActivityName=".SneakersActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="thiengo.com.br.tenisshop.SneakersActivity" />
</activity>

<activity
android:name=".ThankYouActivity"
android:label="@string/title_activity_thank_you"
android:parentActivityName=".SneakersActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="thiengo.com.br.tenisshop.SneakersActivity" />
</activity>
</application>
</manifest>

Configurações de estilo

Para as configurações de tema, estilo, vamos iniciar com o arquivo de definição de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#c62828</color>
<color name="colorPrimaryDark">#8e0000</color>
<color name="colorAccent">#388e3c</color>
<color name="colorAccentDark">#00600f</color>
<color name="colorDarkRedButtonText">#d09696</color>
<color name="colorFacebook">#4266b2</color>
<color name="colorTwitter">#1da1f2</color>
<color name="colorDarkDialog">#5b646e</color>
</resources>

 

Assim o arquivo de dimensões, /res/values/dimens.xml:

<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

 

Agora o arquivo de definição de String, que diferente de muitos projetos já apresentados aqui no Blog, este contém todas as Strings do app de exemplo, /res/values/strings.xml:

<resources>
<string name="app_name">Tênis Shop</string>
<string name="desc_logo_app">Logo Tênis Shop App</string>
<string name="hint_email">Email</string>
<string name="hint_password">Senha</string>
<string name="bt_l_create_new_account">Cadastrar conta</string>
<string name="bt_l_loggin">Entrar</string>
<string name="bt_l_forgot_password">Esqueci minha senha</string>
<string name="bt_l_loggin_with">Entrar com</string>
<string name="bt_l_privacy_policy">Políticas de privacidade</string>
<string name="title_activity_sign_up">Cadastro cliente</string>
<string name="title_activity_thank_you">Voltar para Tênis Shop</string>
<string name="desc_profile_image">Imagem de perfil</string>
<string name="label_profile_image">Imagem de perfil</string>
<string name="label_address">Endereço</string>
<string name="hint_street">Rua</string>
<string name="hint_number">Nº</string>
<string name="hint_zip_code">CEP</string>
<string name="hint_complement">Complemento</string>
<string name="hint_city">Cidade</string>
<string name="label_personal_data">Dados pessoais</string>
<string name="hint_full_name">Nome completo</string>
<string name="hint_ddd">DDD</string>
<string name="hint_phone_number">Telefone</string>
<string name="desc_help_icon">Ícone de ajuda</string>
<string name="help_text">Será utilizado principalmente para promoções especiais por telefone</string>
<string name="label_access_data">Dados de acesso</string>
<string name="terms_of_use">Concordo com os termos de uso.</string>
<string name="bt_l_sign_up">Cadastrar</string>
<string name="desc_sneaker_first_gallery_image">Primeira imagem extra da galeria</string>
<string name="desc_sneaker_second_gallery_image">Segunda imagem extra da galeria</string>
<string name="desc_sneaker_third_gallery_image">Terceira imagem extra da galeria</string>
<string name="desc_male_icon">Ícone do sexo masculino</string>
<string name="desc_female_icon">Ícone do sexo feminino</string>
<string name="label_free">grátis</string>
<string name="label_put_on_shop_cart">Colocar no</string>
<string-array name="states">
<item>Estado</item>
<item>Acre (AC)</item>
<item>Alagoas (AL)</item>
<item>30 minutos</item>
<item>45 minutos</item>
<item>Amapá (AP)</item>
<item>Amazonas (AM)</item>
<item>Bahia (BA)</item>
<item>Ceará (CE)</item>
<item>Distrito Federal (DF)</item>
<item>Espírito Santo (ES)</item>
<item>Goiás (GO)</item>
<item>Maranhão (MA)</item>
<item>Mato Grosso (MT)</item>
<item>Mato Grosso do Sul (MS)</item>
<item>Minas Gerais (MG)</item>
<item>Pará (PA)</item>
<item>Paraíba (PB)</item>
<item>Paraná (PR)</item>
<item>Pernambuco (PE)</item>
<item>Piauí (PI)</item>
<item>Rio de Janeiro (RJ)</item>
<item>Rio Grande do Norte (RN)</item>
<item>Rio Grande do Sul (RS)</item>
<item>Rondônia (RO)</item>
<item>Roraima (RR)</item>
<item>Santa Catarina (SC)</item>
<item>São Paulo (SP)</item>
<item>Sergipe (SE)</item>
<item>Tocantins (TO)</item>
</string-array>
<string name="title_activity_sneaker_details">SneakerDetailsActivity</string>
<string name="brazil_free_delivery">Frete grátis para todo Brasil</string>
<string name="desc_truck_delivery">Ícone de caminhão para entrega, frete</string>
<string name="label_recommended">Recomendado para:</string>
<string name="label_type">Tipo:</string>
<string name="label_composition">Composição:</string>
<string name="label_access_rating">Acessar avaliações</string>
<string name="label_buy">Comprar</string>
<string name="label_description">Descrição:</string>
<string name="value_stok_invetory">Quantidade (4 estoque)</string>
<string name="value_recommended">Running</string>
<string name="value_type">Performance</string>
<string name="value_composition">68% Poliamida | 32% Sintético</string>
<string name="value_desc">O mais novo Fresh Foam Cruz conta com a nossa premiada tecnologia Fresh Foam para uma corrida estável e de alto amortecimento. Seu cabedal sofisticado combina a junção central do tênis com o mesh respirável para oferecer suporte com uma camada extra de conforto. O Fresh Foam Cruz proporciona uma sensação de frescor ao seu pé, treino após treino.</string>
<string name="excellent_buy">Excelente compra!</string>
<string name="keep_buying">Continuar comprando</string>
<string name="dialog_title">Pagamento</string>
<string name="desc_credit_cards">Bandeiras de cartão aceitas</string>
<string name="dialog_label_card_number">Número do cartão:</string>
<string name="dialog_hint_card_number">xxxx xxxx xxxx xxxx</string>
<string name="dialog_label_card_cvv">Código de segurança - CVV:</string>
<string name="dialog_hint_card_cvv">xxx ou xxxx</string>
<string name="dialog_label_card_owner">Nome do proprietário do cartão - como está no cartão:</string>
<string name="dialog_label_card_expire">Vencimento cartão</string>
<string name="dialog_label_card_expire_month">Mês:</string>
<string name="dialog_label_card_expire_bar">/</string>
<string name="dialog_label_card_expire_year">Ano:</string>
<string name="dialog_label_card_total_parcels"><![CDATA[Número de parcelas. Se > 10x tem adição de juros:]]></string>
<string name="label_bt_finish_buy">Finalizar compra</string>
<string name="dialog_label_total">Total:</string>
<string name="dialog_label_parcels_without_taxes">em até 10x sem juros</string>
<string-array name="colors">
<item>Preto</item>
<item>Vermelho</item>
<item>Branco</item>
<item>Azul</item>
<item>Verde</item>
</string-array>
<string-array name="sizes">
<item>36</item>
<item>37</item>
<item>38</item>
<item>39</item>
<item>40</item>
<item>41</item>
<item>42</item>
<item>43</item>
<item>44</item>
</string-array>
<string-array name="amount">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
<string-array name="months">
<item>01</item>
<item>02</item>
<item>03</item>
<item>04</item>
<item>05</item>
<item>06</item>
<item>07</item>
<item>08</item>
<item>09</item>
<item>10</item>
<item>11</item>
<item>12</item>
</string-array>
<string-array name="years">
<item>2018</item>
<item>2019</item>
<item>2020</item>
<item>2021</item>
<item>2022</item>
<item>2023</item>
<item>2024</item>
<item>2025</item>
<item>2026</item>
</string-array>
<string-array name="parcels">
<item>1x</item>
<item>2x</item>
<item>3x</item>
<item>4x</item>
<item>5x</item>
<item>6x</item>
<item>7x</item>
<item>8x</item>
<item>9x</item>
<item>10x</item>
<item>11x (2% juros)</item>
<item>12x (2.5% juros)</item>
</string-array>
</resources>

 

E por fim o arquivo de definição de temas, /res/values/styles.xml:

<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<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" />

<!-- SignUpActivity Spinner -->
<style name="SpinnerTheme" parent="AppTheme">
<item name="android:textViewStyle">@style/TextViewStyle</item>
</style>
<style name="TextViewStyle" parent="android:Widget.TextView">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="SpinnerThemeDark">
<item name="android:textViewStyle">@style/TextViewStyleDark</item>
</style>
<style name="TextViewStyleDark" parent="android:Widget.TextView">
<item name="android:textColor">@color/colorDarkDialog</item>
</style>

<!-- TargetTooltip API -->
<style name="ToolTipLayoutCustomStyle">
<item name="ttlm_strokeColor">@android:color/transparent</item>
<item name="ttlm_backgroundColor">#99000000</item>
<item name="ttlm_strokeWeight">0dp</item>
<item name="ttlm_cornerRadius">10dp</item>
<item name="android:gravity">center</item>
<item name="ttlm_overlayStyle">@style/ToolTipOverlayCustomStyle</item>
<item name="android:textAppearance">@style/ToolTipTextStyle</item>
</style>
<style name="ToolTipTextStyle">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="ToolTipOverlayCustomStyle">
<item name="android:color">@android:color/white</item>
<item name="ttlm_repeatCount">1</item>
<item name="ttlm_duration">1000</item>
<item name="android:alpha">0.2</item>
<item name="android:layout_margin">8dp</item>
</style>
</resources>

Classes de domínio do problema

Teremos quatro classes de domínio, uma delas é na verdade uma classe utilitária, porém somente com métodos úteis ao aplicativo Tênis Shop, logo, colocaremos esta classe também no pacote /domain.

Informo que todos os nomes de métodos, propriedades e classes, em todo o projeto, estarão em inglês, somente os comentários e os valores de Strings é que estarão em português.

Porque este estilo de abordagem?

Porque assim teremos mais coerência na codificação, tendo em mente que métodos nativos que serão utilizados estarão todos em inglês. Mas acredite, isso não atrapalhará em nada no entendimento do projeto e uso do AppSee SDK.

Voltando as classes de domínio, no pacote /domain, crie ele caso ainda não tenha feito, coloque a classe Brand, classe que representará as marcas dos tênis:

class Brand(
val imageResource: Int,
val name: String) : Parcelable {

constructor(source: Parcel) : this(
source.readInt(),
source.readString()
)

override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeInt(imageResource)
writeString(name)
}

companion object {
@JvmField
val CREATOR: Parcelable.Creator<Brand> = object : Parcelable.Creator<Brand> {
override fun createFromParcel(source: Parcel): Brand = Brand(source)
override fun newArray(size: Int): Array<Brand?> = arrayOfNulls(size)
}
}
}

 

Note que tirando a classe utilitária, todas as outras classes implementam a Interface Parcelable, isso, pois estaremos enviando objetos dessas classes de uma atividade a outra por meio de intenções.

Assim a classe Rating, que representará as avaliações de cada tênis:

class Rating(
val amount: Int,
val stars: Int) : Parcelable {

constructor(source: Parcel) : this(
source.readInt(),
source.readInt()
)

override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeInt(amount)
writeInt(stars)
}

companion object {
@JvmField
val CREATOR: Parcelable.Creator<Rating> = object : Parcelable.Creator<Rating> {
override fun createFromParcel(source: Parcel): Rating = Rating(source)
override fun newArray(size: Int): Array<Rating?> = arrayOfNulls(size)
}
}
}

 

Trabalharemos somente com os números das avaliações, isso para não termos uma codificação de exemplo exaustiva contendo também os comentários e cada usuário que enviou a sua avaliação.

Agora a classe principal de domínio, Sneaker (tênis), que representa os tênis no app:

class Sneaker(
val imageResource: Int,
val album: IntArray,
val model: String,
val brand: Brand,
val isForMale: Boolean,
val isForFemale: Boolean,
val rating: Rating,
val price: Double) : Parcelable {

/*
* Método responsável por retornar o valor do tênis
* em um formato humano, brasileiro.
* */
fun getPriceAsString(): String {
return String.format(Locale.FRANCE, "R$ %.2f", price)
}

/*
* Método responsável por retornar o valor do tênis,
* em parcelas de 10x, em um formato humano, brasileiro.
* */
fun getPriceParcelsAsString(): String {
val value = price / 10
return String.format(Locale.FRANCE, "10x R$ %.2f", value)
}

constructor(source: Parcel) : this(
source.readInt(),
source.createIntArray(),
source.readString(),
source.readParcelable<Brand>(Brand::class.java.classLoader),
1 == source.readInt(),
1 == source.readInt(),
source.readParcelable<Rating>(Rating::class.java.classLoader),
source.readDouble()
)

override fun describeContents() = 0

override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeInt(imageResource)
writeIntArray(album)
writeString(model)
writeParcelable(brand, 0)
writeInt((if (isForMale) 1 else 0))
writeInt((if (isForFemale) 1 else 0))
writeParcelable(rating, 0)
writeDouble(price)
}

companion object {
val KEY = "SNEAKER_KEY"

@JvmField
val CREATOR: Parcelable.Creator<Sneaker> = object : Parcelable.Creator<Sneaker> {
override fun createFromParcel(source: Parcel): Sneaker = Sneaker(source)
override fun newArray(size: Int): Array<Sneaker?> = arrayOfNulls(size)
}
}
}

 

Não deixe de ler todos os comentários presentes nos códigos, pois eles também são explicações que complementam o conteúdo em texto corrido do artigo.

Por fim a classe utilitária, Util:

class Util {
companion object {

/*
* Defini a cor de background do ImageView, que na verdade será
* útil para que a border com radius não tenha contraste com as
* cores laterais da imagem.
* */
fun setImageViewBgColor(context: Context, imageView: ImageView) {
val bitmap = (imageView.getDrawable() as BitmapDrawable).bitmap
/*
* Estamos obtendo a cor do pixel na coordenada (2.1dp, 2.1dp),
* pois sabemos que nesta coordenada se inicia a verdadeira
* cor de borda da imagem original, isso tendo em mente
* que o shape image_view_sneaker.xml tem um padding de
* 2dp e um background branco e não queremos obtê-lo para a
* definição dinâmica de imagem de background.
* */
val initPixelPosition = Util.convertDpToPixel(context.resources, 2.1F)
val pixel = bitmap.getPixel(initPixelPosition, initPixelPosition)

val bgShape = imageView.background.current as GradientDrawable
bgShape.setColor(pixel)
}

fun convertDpToPixel(resources: Resources, dp: Float): Int {
val metrics = resources.getDisplayMetrics()
val px = dp * (metrics.densityDpi / 160f)
return Math.round(px)
}

/*
* Apresenta ou esconde os ImageViews de gênero de acordo com o perfil
* do tênis em teste.
* */
fun setGenre(sneaker: Sneaker, male: ImageView, female: ImageView){
male.visibility =
if( sneaker.isForMale )
View.VISIBLE
else
View.GONE

female.visibility =
if( sneaker.isForFemale )
View.VISIBLE
else
View.GONE
}

/*
* Coloca estrela cheia ou vazia no ImageView de rating de tênis.
* */
fun setStar(parent: View, starResourceId: Int, position: Int, rating: Int){
val ivStar = parent.findViewById(starResourceId) as ImageView

ivStar.setImageResource(
if( position <= rating )
R.drawable.ic_star_black_18dp
else
R.drawable.ic_star_border_white_18dp
)
}
}
}

 

Com exceção do método convertDpToPixel(), todos os outros, mesmo com comentários, farão mais sentido a ti, leitor e desenvolvedor, quando estiverem sendo acionados em classes da camada de visualizacão.

Base de dados simulados, mock data

Com uma base de dados simulados conseguiremos manter o uso completo do aplicativo de exemplo sem a necessidade de esforços extras para a construção de uma base real.

Como já informei em outros projetos de exemplo aqui do Blog, base de dados mock é muito útil no processo de desenvolvimento de aplicativos, nos economiza tempo.

Crie um pacote /data e em seguida acrescente a ele a classe Database:

class Database {
companion object {

/*
* Retorna os dados de teste do aplicativo, dados
* simulados ou dados mock
* */
fun getSneakers() =
listOf<Sneaker>(
Sneaker(
R.drawable.shoes_01_a,
intArrayOf(R.drawable.shoes_01_b, R.drawable.shoes_01_c, R.drawable.shoes_01_d),
"Fresh Foam Cruz",
Brand(R.drawable.ic_new_balance, "New Balance"),
true,
true,
Rating(42, 5),
499.90
),
Sneaker(
R.drawable.shoes_02_a,
intArrayOf(R.drawable.shoes_02_b, R.drawable.shoes_02_c, R.drawable.shoes_02_d),
"Epic React Flyknit",
Brand(R.drawable.ic_nike, "Nike"),
true,
false,
Rating(91, 5),
699.90
),
Sneaker(
R.drawable.shoes_03_a,
intArrayOf(R.drawable.shoes_03_b, R.drawable.shoes_03_c, R.drawable.shoes_03_d),
"Supernova",
Brand(R.drawable.ic_adidas, "Adidas"),
true,
false,
Rating(29, 3),
599.99
),
Sneaker(
R.drawable.shoes_04_a,
intArrayOf(R.drawable.shoes_04_b, R.drawable.shoes_04_c, R.drawable.shoes_04_d),
"GEL-Kenun",
Brand(R.drawable.ic_asics, "Asics"),
true,
false,
Rating(84, 4),
699.90
),
Sneaker(
R.drawable.shoes_05_a,
intArrayOf(R.drawable.shoes_05_b, R.drawable.shoes_05_c, R.drawable.shoes_05_d),
"Charged Bandit 3",
Brand(R.drawable.ic_under_armour, "UnderArmour"),
false,
true,
Rating(19, 5),
349.90
),
Sneaker(
R.drawable.shoes_06_a,
intArrayOf(R.drawable.shoes_06_b, R.drawable.shoes_06_c, R.drawable.shoes_06_d),
"Wave Sky",
Brand(R.drawable.ic_mizuno, "Mizuno"),
true,
true,
Rating(42, 5),
749.99
)
)

/*
* Ambos os métodos abaixo trabalham com o SharedPreferences
* para que seja possível simular que o usuário atual está ou
* não logado ao aplicativo.
* */
fun setLoggedUser(context: Context, value: Boolean){
context
.getSharedPreferences("PREF_DATA", Context.MODE_PRIVATE)
.edit().putBoolean("is-logged", value)
.apply()
}
fun isUserLogged(context: Context): Boolean{
return context
.getSharedPreferences("PREF_DATA", Context.MODE_PRIVATE)
.getBoolean("is-logged", false)
}
}
}

Atividade de login

Assim podemos partir para a atividade de entrada ao aplicativo, LoginActivity. Vamos iniciar com o layout da activity, /res/layout/activity_login.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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"
android:fillViewport="true"
tools:context="thiengo.com.br.tenisshop.LoginActivity">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_login"
android:paddingBottom="8dp">

<ImageView
android:id="@+id/iv_logo"
android:layout_width="133dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="130dp"
android:contentDescription="@string/desc_logo_app"
android:scaleType="fitCenter"
android:src="@drawable/logo_app" />

<EditText
android:id="@+id/et_email"
android:layout_width="290dp"
android:layout_height="wrap_content"
android:layout_below="@+id/iv_logo"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_email"
android:inputType="textEmailAddress"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<EditText
android:id="@+id/et_password"
android:layout_width="290dp"
android:layout_height="wrap_content"
android:layout_below="@+id/et_email"
android:layout_centerHorizontal="true"
android:layout_marginTop="14dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_password"
android:inputType="textPassword"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<Button
android:id="@+id/bt_sign_up"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignLeft="@+id/et_password"
android:layout_alignStart="@+id/et_password"
android:layout_below="@+id/et_password"
android:layout_marginTop="18dp"
android:background="@drawable/button_dark_background"
android:onClick="callSignUpActivity"
android:paddingBottom="0dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="0dp"
android:text="@string/bt_l_create_new_account"
android:textAllCaps="false"
android:textColor="@color/colorDarkRedButtonText"
android:textSize="15sp" />

<Button
android:id="@+id/bt_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/et_password"
android:layout_alignLeft="@+id/bt_forgot_password"
android:layout_alignRight="@+id/et_password"
android:layout_alignStart="@+id/bt_forgot_password"
android:layout_alignTop="@+id/bt_sign_up"
android:background="@drawable/button_green_background"
android:onClick="callSneakersActivity"
android:text="@string/bt_l_loggin"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp" />

<Button
android:id="@+id/bt_forgot_password"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignEnd="@+id/et_password"
android:layout_alignRight="@+id/et_password"
android:layout_below="@+id/bt_login"
android:layout_marginTop="9dp"
android:background="@drawable/button_dark_background"
android:paddingBottom="0dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="0dp"
android:text="@string/bt_l_forgot_password"
android:textAllCaps="false"
android:textColor="@color/colorDarkRedButtonText"
android:textSize="15sp" />

<Button
android:id="@+id/bt_login_facebook"
android:layout_width="140dp"
android:layout_height="48dp"
android:layout_alignLeft="@+id/et_password"
android:layout_alignStart="@+id/et_password"
android:layout_below="@+id/bt_forgot_password"
android:layout_marginTop="18dp"
android:background="@drawable/button_facebook_background"
android:onClick="callSneakersActivity"
android:paddingRight="24dp"
android:text="@string/bt_l_loggin_with"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp" />

<Button
android:id="@+id/bt_login_twitter"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_alignEnd="@+id/et_password"
android:layout_alignRight="@+id/et_password"
android:layout_alignTop="@+id/bt_login_facebook"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/bt_login_facebook"
android:layout_toRightOf="@+id/bt_login_facebook"
android:background="@drawable/button_twitter_background"
android:onClick="callSneakersActivity"
android:paddingRight="24dp"
android:text="@string/bt_l_loggin_with"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp" />

<Button
android:id="@+id/bt_privacy_policy"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/bt_login_twitter"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="10dp"
android:background="@drawable/button_dark_background"
android:paddingBottom="0dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="0dp"
android:text="@string/bt_l_privacy_policy"
android:textAllCaps="false"
android:textColor="@color/colorDarkRedButtonText"
android:textSize="15sp" />
</RelativeLayout>
</ScrollView>

 

Então o diagrama do layout anterior:

Diagrama do layout activity_login.xml

Abaixo o XML de background dos campos de entrada de texto, /res/drawable/field_border_and_background.xml, para que eles tenham as bordas brancas, cantos arredondados e o fundo transparente:

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

<solid android:color="@android:color/transparent" />

<corners android:radius="2dp" />

<stroke
android:width="2dp"
android:color="@android:color/white" />
</shape>

 

Abaixo o efeito do XML anterior quando aplicado a EditTexts:

Campos com shape personalizado

Agora o XML de background de todos os botões de fundo verde, /res/drawable/button_green_background.xml, não somente os presentes na tela de login, mas em todo o projeto:

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

<solid android:color="@color/colorAccent" />

<corners android:radius="2dp" />
</shape>

 

A seguir um Button com o XML anterior sendo utilizado:

Botão com background verde customizado

Agora o XML de background dos botões de fundo vermelho escuro, /res/drawable/button_dark_background.xml:

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

<solid android:color="@color/colorPrimaryDark" />

<corners android:radius="2dp" />
</shape>

 

A seguir uma imagem de um dos botões que utilizam o XML anterior de shape:

Botão com background vermelho escuro customizado

Assim o XML de background do botão de login com dados do Facebook, /res/drawable/button_facebook_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorFacebook"/>
<corners android:radius="2dp" />
</shape>
</item>
<item
android:drawable="@drawable/ic_facebook_white_24dp"
android:bottom="14dp"
android:left="104dp"
android:right="14dp"
android:top="14dp"/>
</layer-list>

 

Abaixo a imagem de como o botão ficaria:

Botão com background azul customizado para Facebook login

E agora o XML de background do botão de login via Twitter, /res/drawable/button_twitter_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorTwitter"/>
<corners android:radius="2dp" />
</shape>
</item>
<item
android:drawable="@drawable/ic_twitter_white_24dp"
android:bottom="14dp"
android:left="104dp"
android:right="14dp"
android:top="14dp"/>
</layer-list>

Abaixo a imagem de como o botão de login via Twitter ficaria:

Botão com background azul customizado para Twitter login

Por fim o código Kotlin da atividade LoginActivity:

class LoginActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)

/*
* Se o usuário já tiver logado no aplicativo anteriormente,
* no mesmo device, ele já entrará direto. Lembrando que esse
* trecho é simulado, pois o app é de testes e não há um
* algoritmo completo de login.
* */
if( Database.isUserLogged(this) ){
callSneakersActivity(null)
}
}

fun callSignUpActivity(view: View?){
val intent = Intent(this, SignUpActivity::class.java)
startActivity(intent)
}

fun callSneakersActivity(view: View?){
/*
* Modificando o valor de login do usuário para garantir que
* ele já entre na área de listagem de tênis na próxima
* abertura de aplicativo.
* */
Database.setLoggedUser(this, true)

val it = Intent(this, SneakersActivity::class.java)
startActivity(it)
finish()
}
}

 

Para facilitar o exemplo, nós precisaremos somente acionar os botões corretos para já estar logado no aplicativo, o SharedPreferences trabalhado na classe Database nos ajudará com isso.

Assim temos a seguinte tela de login:

Tela de login

Atividade de cadastro

Como na atividade anterior, para a atividade SignUpActivity começaremos com a apresentação dos XMLs de layout. Abaixo o XML de conteúdo, /res/layout/content_sign_up.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"
android:background="@color/colorPrimary"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="thiengo.com.br.tenisshop.SignUpActivity"
tools:showIn="@layout/activity_sign_up">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">

<ImageView
android:id="@+id/iv_profile"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:background="@drawable/image_view_profile"
android:contentDescription="@string/desc_profile_image"
android:scaleType="center" />

<TextView
android:id="@+id/tv_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/iv_profile"
android:layout_centerHorizontal="true"
android:layout_marginTop="12dp"
android:fontFamily="@font/chewy_regular"
android:text="@string/label_profile_image"
android:textColor="@android:color/white"
android:textSize="24sp" />

<TextView
android:id="@+id/tv_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_profile"
android:layout_marginTop="40dp"
android:fontFamily="@font/chewy_regular"
android:text="@string/label_address"
android:textColor="@android:color/white"
android:textSize="24sp" />

<LinearLayout
android:id="@+id/ll_address_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_address"
android:layout_marginTop="6dp"
android:orientation="horizontal">

<EditText
android:id="@+id/et_street"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.32"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_street"
android:inputType="text"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<EditText
android:id="@+id/et_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="14dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginStart="14dp"
android:layout_weight="0.20"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_number"
android:inputType="number"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<EditText
android:id="@+id/et_zip_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.48"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_zip_code"
android:inputType="textPostalAddress"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />
</LinearLayout>

<EditText
android:id="@+id/et_complement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_address_1"
android:layout_marginTop="14dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_complement"
android:inputType="text"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<LinearLayout
android:id="@+id/ll_address_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_complement"
android:layout_marginTop="14dp"
android:orientation="horizontal">

<EditText
android:id="@+id/et_city"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="14dp"
android:layout_marginRight="14dp"
android:layout_weight="1"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_city"
android:inputType="text"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<Spinner
android:id="@+id/et_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/spinner_border_and_background"
android:entries="@array/states"
android:padding="12dp"
android:popupBackground="#4c4c4c"
android:textColor="@android:color/white"
android:theme="@style/SpinnerTheme" />
</LinearLayout>

<TextView
android:id="@+id/tv_personal_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_address_2"
android:layout_marginTop="40dp"
android:fontFamily="@font/chewy_regular"
android:text="@string/label_personal_data"
android:textColor="@android:color/white"
android:textSize="24sp" />

<EditText
android:id="@+id/et_full_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_personal_data"
android:layout_marginTop="6dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_full_name"
android:inputType="text"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<LinearLayout
android:id="@+id/ll_personal_data_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_full_name"
android:layout_marginTop="14dp"
android:orientation="horizontal">

<EditText
android:id="@+id/et_phone_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_ddd"
android:inputType="number"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<EditText
android:id="@+id/et_phone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="12dp"
android:layout_marginStart="14dp"
android:layout_weight="0.8"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_phone_number"
android:inputType="phone"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<ImageView
android:id="@+id/iv_help"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/desc_help_icon"
android:onClick="helpText"
android:scaleType="fitCenter"
android:src="@drawable/ic_help_white" />
</LinearLayout>

<TextView
android:id="@+id/tv_access_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_personal_data_1"
android:layout_marginTop="40dp"
android:fontFamily="@font/chewy_regular"
android:text="@string/label_access_data"
android:textColor="@android:color/white"
android:textSize="24sp" />

<EditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_access_data"
android:layout_marginTop="6dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_email"
android:inputType="textEmailAddress"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et_email"
android:layout_marginTop="14dp"
android:background="@drawable/field_border_and_background"
android:hint="@string/hint_password"
android:inputType="textPassword"
android:padding="10dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white" />

<CheckBox
android:id="@+id/cb_terms_of_use"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/et_password"
android:layout_marginTop="24dp"
android:buttonTint="@android:color/white"
android:text="@string/terms_of_use"
android:textColor="@android:color/white"
android:textSize="16sp" />

<Button
android:id="@+id/bt_sign_up"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/cb_terms_of_use"
android:layout_marginTop="24dp"
android:background="@drawable/button_green_background"
android:enabled="false"
android:onClick="callSneakersActivity"
android:text="@string/bt_l_sign_up"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp" />

</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

Note que para todos os arquivos XML nós estaremos fazendo o máximo possível o uso do RelativeLayout, pois assim evitamos aninhamentos de Views e ao mesmo tempo trabalhamos também a performance do aplicativo.

A seguir o diagrama do XML anterior:

Diagrama do layout content_sign_up.xml

Estudando o XML anterior você notará que estamos trabalhando com fonte personalizada, para saber mais sobre como utilizar fontes externas em seu aplicativo Android, entre no conteúdo a seguir: Fontes em XML, Android O. Configuração e Uso.

A fonte utilizada é a Chewy Regular.

Abaixo o XML de background que está sendo utilizado em todos os Spinners do aplicativo (com excessão do Spinner de dialog de pagamento), /res/drawable/spinner_border_and_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="2dp" />
<stroke
android:width="2dp"
android:color="@android:color/white" />
</shape>
</item>
<item android:right="8dp">
<bitmap
android:gravity="center_vertical|right"
android:src="@drawable/ic_keyboard_arrow_down_white_18dp" />
</item>
</layer-list>

 

O <layer-list> permite o uso de outros objetos desenháveis em um único objeto desenhável, o empilhamento é de acordo com o posicionamento em lista, onde o último <item> é o que aparece no topo da visualização.

Para a apresentação dos Spinners com o mesmo design dos campos de entrada de dados é necessário também o uso de um tema de auxílio, já apresentado na seção de Configurações de estilo:

...
<!-- SignUpActivity Spinner -->
<style name="SpinnerTheme" parent="AppTheme">
<item name="android:textViewStyle">@style/TextViewStyle</item>
</style>

<style name="TextViewStyle" parent="android:Widget.TextView">
<item name="android:textColor">@android:color/white</item>
</style>
...

 

Abaixo um Spinner com o efeito dos XMLs anteriores, quando aplicados:

Spinner com shape personalizado

Agora o arquivo XML container do layout de conteúdo, /res/layout/activity_sign_up.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"
tools:context="thiengo.com.br.tenisshop.SignUpActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

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

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

<include layout="@layout/content_sign_up" />
</android.support.design.widget.CoordinatorLayout>

 

Então o diagrama do layout acima:

Diagrama do layout activity_sign_up.xml

Agora o código Kotlin de SignUpActivity:

class SignUpActivity : AppCompatActivity(),
CompoundButton.OnCheckedChangeListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_up)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

cb_terms_of_use.setOnCheckedChangeListener(this)
}

/*
* Para a liberação do button de cadastro, somente se os termos
* de uso estiverem de acordo, o usuário aceita-los.
* */
override fun onCheckedChanged(checkButton: CompoundButton?, status: Boolean) {
bt_sign_up.isEnabled = status
}

fun callSneakersActivity(view: View?){
Database.setLoggedUser(this, true)

val it = Intent(this, SneakersActivity::class.java)
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(it)
finish()
}

/*
* Apresenta um ToolTip de ajuda quando o ícone de interrogação é
* acionado. O algoritmo interno é de configuração da API de ToolTip
* TargetTooltip.
* */
fun helpText(view: View?){

Tooltip.make(this,
Tooltip.Builder(1)
.anchor(iv_help, Tooltip.Gravity.TOP)
.closePolicy(
ClosePolicy()
.insidePolicy(true, false)
.outsidePolicy(true, false), 3000)
.activateDelay(1000)
.text(resources.getString(R.string.help_text))
.maxWidth( Util.convertDpToPixel( resources,200F) )
.withArrow(true)
.withStyleId(R.style.ToolTipLayoutCustomStyle)
.withOverlay(true)
.floatingAnimation(AnimationBuilder.DEFAULT)
.build()
).show()
}
}

 

A seguir a animação do que acontece quando a API Android-Target-Tooltip é acionada via helpText():

Animação do tooltip acionado

Com isso temos a seguinte tela de cadastro:

Tela de cadastro

Classe adaptadora de listagem de tênis

Para o adapter do RecyclerView de SneakersActivity, atividade da próxima seção, vamos iniciar com o arquivo XML de layout de item, /res/layout/sneaker.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="wrap_content"
android:layout_marginBottom="7dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="7dp"
android:background="@drawable/item_border_and_background"
android:padding="6dp"
tools:context="thiengo.com.br.tenisshop.SneakersActivity">

<ImageView
android:id="@+id/iv_sneaker"
android:layout_width="81dp"
android:layout_height="81dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_sneaker_gallery_01"
android:layout_width="23dp"
android:layout_height="23dp"
android:layout_alignTop="@+id/iv_sneaker"
android:layout_toEndOf="@+id/iv_sneaker"
android:layout_toRightOf="@+id/iv_sneaker"
android:background="@drawable/image_view_sneaker"
android:contentDescription="@string/desc_sneaker_first_gallery_image"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_sneaker_gallery_02"
android:layout_width="23dp"
android:layout_height="23dp"
android:layout_below="@+id/iv_sneaker_gallery_01"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:layout_toEndOf="@+id/iv_sneaker"
android:layout_toRightOf="@+id/iv_sneaker"
android:background="@drawable/image_view_sneaker"
android:contentDescription="@string/desc_sneaker_second_gallery_image"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_sneaker_gallery_03"
android:layout_width="23dp"
android:layout_height="23dp"
android:layout_below="@+id/iv_sneaker_gallery_02"
android:layout_toEndOf="@+id/iv_sneaker"
android:layout_toRightOf="@+id/iv_sneaker"
android:background="@drawable/image_view_sneaker"
android:contentDescription="@string/desc_sneaker_third_gallery_image"
android:scaleType="fitCenter" />

<View
android:id="@+id/vv_vertical_line"
android:layout_width="1dp"
android:layout_height="81dp"
android:layout_alignParentTop="true"
android:layout_marginEnd="6dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_marginStart="6dp"
android:layout_toEndOf="@+id/iv_sneaker_gallery_01"
android:layout_toRightOf="@+id/iv_sneaker_gallery_01"
android:background="@android:color/white" />

<TextView
android:id="@+id/tv_model"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/tv_free_delivery"
android:layout_alignParentTop="true"
android:layout_alignRight="@+id/tv_free_delivery"
android:layout_toEndOf="@id/vv_vertical_line"
android:layout_toRightOf="@id/vv_vertical_line"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />

<ImageView
android:id="@+id/iv_brand"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:layout_below="@+id/tv_model"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@id/vv_vertical_line"
android:layout_toRightOf="@id/vv_vertical_line"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_genre_male"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_below="@+id/iv_brand"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:layout_toEndOf="@id/vv_vertical_line"
android:layout_toRightOf="@id/vv_vertical_line"
android:contentDescription="@string/desc_male_icon"
android:scaleType="fitCenter"
android:src="@drawable/ic_gender_male_white_18dp"
android:visibility="gone" />

<ImageView
android:id="@+id/iv_genre_female"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_below="@+id/iv_brand"
android:layout_toEndOf="@id/iv_genre_male"
android:layout_toRightOf="@id/iv_genre_male"
android:contentDescription="@string/desc_female_icon"
android:scaleType="fitCenter"
android:src="@drawable/ic_gender_female_white_18dp"
android:visibility="gone" />

<ImageView
android:id="@+id/iv_rating_star_01"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentBottom="true"
android:layout_toEndOf="@id/vv_vertical_line"
android:layout_toRightOf="@id/vv_vertical_line"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_02"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_01"
android:layout_toRightOf="@id/iv_rating_star_01"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_03"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_02"
android:layout_toRightOf="@id/iv_rating_star_02"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_04"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_03"
android:layout_toRightOf="@id/iv_rating_star_03"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_05"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_04"
android:layout_toRightOf="@id/iv_rating_star_04"
android:scaleType="fitCenter" />

<TextView
android:id="@+id/tv_rating_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_05"
android:layout_toRightOf="@id/iv_rating_star_05"
android:textColor="@android:color/white"
android:textSize="13sp" />

<TextView
android:id="@+id/tv_free_delivery"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="@drawable/text_view_free_delivery_background"
android:gravity="end"
android:text="@string/label_free"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_put_shop_cart"
android:layout_width="wrap_content"
android:layout_height="22dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/tv_free_delivery"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/text_view_shop_cart_background"
android:gravity="start"
android:text="@string/label_put_on_shop_cart"
android:textColor="@android:color/white"
android:textSize="12sp" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/tv_put_shop_cart"
android:background="@drawable/dark_red_background"
android:gravity="end"
android:orientation="vertical"
android:paddingBottom="1dp"
android:paddingLeft="4dp"
android:paddingRight="4dp">

<TextView
android:id="@+id/tv_price_parcels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="13sp"
android:textStyle="bold|italic" />

<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-4dp"
android:textColor="@android:color/white"
android:textSize="9sp"
android:textStyle="bold|italic" />
</LinearLayout>
</RelativeLayout>

 

Então o diagrama do layout anterior:

Diagrama do layout sneaker.xml

Abaixo o XML de shape, /res/drawable/item_border_and_background.xml, que permiti a colocação de borda personalizada em cada item da lista de tênis:

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

<solid android:color="@android:color/transparent" />

<corners android:radius="2dp" />

<stroke
android:width="1dp"
android:color="@android:color/white" />
</shape>

 

A seguir o XML de design do botão de "Frete grátis", /res/drawable/text_view_free_delivery_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorPrimaryDark" />
<corners android:radius="2dp" />
<padding
android:bottom="4dp"
android:left="4dp"
android:right="6dp"
android:top="2dp" />
</shape>
</item>
<item
android:width="22dp"
android:height="20dp"
android:bottom="0dp"
android:drawable="@drawable/ic_truck_white_24dp"
android:left="0dp"
android:right="39dp"
android:top="0dp" />
</layer-list>

 

Agora o XML de design do botão "Colocar no carrinho", /res/drawable/text_view_shop_cart_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorAccentDark" />
<corners android:radius="2dp" />
<padding
android:bottom="4dp"
android:left="4dp"
android:right="6dp"
android:top="2dp" />
</shape>
</item>
<item
android:width="17dp"
android:height="16dp"
android:bottom="0dp"
android:drawable="@drawable/ic_shopping_cart_black_18dp"
android:left="63dp"
android:right="0dp"
android:top="0dp" />
</layer-list>

 

E por fim o XML de background da seção de preços de cada item, /res/drawable/dark_red_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorPrimaryDark" />
<corners android:radius="2dp" />
</shape>

 

Assim a imagem de item com o efeito do uso dos XMLs anteriores:

Item de listagem com bordar branca personalizada

E por fim o código Kotlin da classe adaptadora, SneakersAdapter, que deve vir no pacote /adapter:

class SneakersAdapter(
private val context: Context,
private val sneakers: List<Sneaker>) :
RecyclerView.Adapter<SneakersAdapter.ViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int ) : SneakersAdapter.ViewHolder {

val v = LayoutInflater
.from(context)
.inflate(R.layout.sneaker, parent, false)
return ViewHolder(v)
}

override fun onBindViewHolder(
holder: ViewHolder,
position: Int) {

holder.setData( sneakers[position] )
}

override fun getItemCount(): Int {
return sneakers.size
}

inner class ViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView),
View.OnClickListener {

var ivSneaker: ImageView
var ivSneakerGallery01: ImageView
var ivSneakerGallery02: ImageView
var ivSneakerGallery03: ImageView
var tvModel: TextView
var ivBrand: ImageView
var ivGenreMale: ImageView
var ivGenreFemale: ImageView
var tvPriceParcels: TextView
var tvPrice: TextView

init {
itemView.setOnClickListener(this)

ivSneaker = itemView.findViewById(R.id.iv_sneaker)
ivSneakerGallery01 = itemView.findViewById(R.id.iv_sneaker_gallery_01)
ivSneakerGallery02 = itemView.findViewById(R.id.iv_sneaker_gallery_02)
ivSneakerGallery03 = itemView.findViewById(R.id.iv_sneaker_gallery_03)
tvModel = itemView.findViewById(R.id.tv_model)
ivBrand = itemView.findViewById(R.id.iv_brand)
ivGenreMale = itemView.findViewById(R.id.iv_genre_male)
ivGenreFemale = itemView.findViewById(R.id.iv_genre_female)
tvPriceParcels = itemView.findViewById(R.id.tv_price_parcels)
tvPrice = itemView.findViewById(R.id.tv_price)
}

fun setData( sneaker: Sneaker ) {
setGallery(sneaker)

tvModel.text = sneaker.model

ivBrand.setImageResource( sneaker.brand.imageResource )
ivBrand.contentDescription = sneaker.brand.name

Util.setGenre(sneaker, ivGenreMale, ivGenreFemale)
setRating(sneaker)
setPrice(sneaker)
}

private fun setGallery(sneaker: Sneaker){
/* IMAGEM PRINCIPAL DO TÊNIS */
ivSneaker.setImageResource( sneaker.imageResource )
ivSneaker.contentDescription = "Tênis ${sneaker.model}"

ivSneakerGallery01.setImageResource( sneaker.album[0] )
ivSneakerGallery02.setImageResource( sneaker.album[1] )
ivSneakerGallery03.setImageResource( sneaker.album[2] )

/*
* Atualizando a cor de background das imagens para que
* a borda fique sem contraste.
* */
Util.setImageViewBgColor(context, ivSneaker)
Util.setImageViewBgColor(context, ivSneakerGallery01)
Util.setImageViewBgColor(context, ivSneakerGallery02)
Util.setImageViewBgColor(context, ivSneakerGallery03)
}

/*
* Coloca os resources corretos de estrelas de acordo com os
* números das avaliações dos tênis.
* */
private fun setRating(sneaker: Sneaker){
val tvRatingAmount = itemView.findViewById(R.id.tv_rating_amount) as TextView
tvRatingAmount.text = "(${sneaker.rating.amount})"

Util.setStar(itemView, R.id.iv_rating_star_01, 1, sneaker.rating.stars)
Util.setStar(itemView, R.id.iv_rating_star_02, 2, sneaker.rating.stars)
Util.setStar(itemView, R.id.iv_rating_star_03, 3, sneaker.rating.stars)
Util.setStar(itemView, R.id.iv_rating_star_04, 4, sneaker.rating.stars)
Util.setStar(itemView, R.id.iv_rating_star_05, 5, sneaker.rating.stars)
}

private fun setPrice(sneaker: Sneaker){
tvPriceParcels.text = sneaker.getPriceParcelsAsString()
tvPrice.text = sneaker.getPriceAsString()
}

override fun onClick(view: View?) {
val intent = Intent( context, SneakerDetailsActivity::class.java )
intent.putExtra( Sneaker.KEY, sneakers[adapterPosition] )
context.startActivity(intent)
}
}
}

 

No código acima temos todos os métodos de visualizações da classe Util sendo utilizados, métodos que foram colocados na classe utilitária, pois serão também invocados em outras classes do projeto. Novamente: não deixe de ler os comentários dos códigos para entendimento completo do conteúdo.

Atividade de listagem de tênis

Para a SneakersActivity vamos iniciar com o layout, que é simples e dispensa o uso de diagrama. Segue /res/layout/activity/sneakers.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rv_sneakers"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:paddingTop="7dp" />

 

Agora o XML de menu que permiti a colocação dos ícones de "busca" e de "carrinho de compras", /res/menu/menu.xml:

<?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/it_search"
android:icon="@drawable/ic_search_white_24dp"
android:title="Buscar"
app:showAsAction="ifRoom" />
<item
android:id="@+id/it_cart"
android:icon="@drawable/ic_shopping_cart_white_24dp"
android:title="Carrinho"
app:showAsAction="ifRoom" />
</menu>

 

Então o código Kotlin de SneakersActivity:

class SneakersActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sneakers)
}

override fun onStart() {
super.onStart()

rv_sneakers.setHasFixedSize(true)
val layoutManager = LinearLayoutManager(this)
rv_sneakers.layoutManager = layoutManager
rv_sneakers.adapter = SneakersAdapter(this, Database.getSneakers())
}

/*
* Somente para apresentarmos os itens de "buscar" e
* "carrinho de compras"
* */
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu, menu)
return true
}
}

 

Com isso temos a seguinte tela de listagem:

Tela de listagem de tênis

Atividade de detalhes de produto

Para a SneakerDetailsActivity vamos iniciar com o layout de conteúdo, /res/layout/content_sneaker_details.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"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="thiengo.com.br.tenisshop.SneakerDetailsActivity"
tools:showIn="@layout/activity_sneaker_details">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="14dp">

<ImageView
android:id="@+id/iv_brand"
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_new_balance" />

<View
android:id="@+id/vv_horizontal_line_01"
android:layout_width="40dp"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/iv_brand"
android:layout_marginBottom="3dp"
android:layout_marginTop="14dp"
android:background="@android:color/white" />

<ImageView
android:id="@+id/iv_genre_male"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_01"
android:contentDescription="@string/desc_male_icon"
android:padding="9dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_gender_male_white_18dp"
android:visibility="gone" />

<ImageView
android:id="@+id/iv_genre_female"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/iv_genre_male"
android:contentDescription="@string/desc_female_icon"
android:padding="9dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_gender_female_white_18dp"
android:visibility="gone" />

<View
android:id="@+id/vv_horizontal_line_02"
android:layout_width="40dp"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/iv_genre_female"
android:layout_marginBottom="14dp"
android:layout_marginTop="3dp"
android:background="@android:color/white" />

<Spinner
android:id="@+id/sp_cor"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_02"
android:background="@drawable/spinner_mini_border_and_background"
android:entries="@array/colors"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:popupBackground="#4c4c4c"
android:textColor="@android:color/white"
android:theme="@style/SpinnerTheme" />

<View
android:id="@+id/vv_horizontal_line_03"
android:layout_width="40dp"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/sp_cor"
android:layout_marginBottom="14dp"
android:layout_marginTop="14dp"
android:background="@android:color/white" />

<Spinner
android:id="@+id/sp_size"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_03"
android:background="@drawable/spinner_mini_border_and_background"
android:entries="@array/sizes"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:popupBackground="#4c4c4c"
android:textColor="@android:color/white"
android:theme="@style/SpinnerTheme" />

<View
android:id="@+id/vv_vertical_line_01"
android:layout_width="1dp"
android:layout_height="240dp"
android:layout_alignParentTop="true"
android:layout_marginEnd="14dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginStart="14dp"
android:layout_toEndOf="@+id/iv_brand"
android:layout_toRightOf="@+id/iv_brand"
android:background="@android:color/white" />

<LinearLayout
android:id="@+id/ll_center_and_right"
android:layout_width="wrap_content"
android:layout_height="240dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_toEndOf="@+id/vv_vertical_line_01"
android:layout_toRightOf="@+id/vv_vertical_line_01"
android:orientation="horizontal">

<ImageView
android:id="@+id/iv_sneaker_01"
android:layout_width="0dp"
android:layout_height="240dp"
android:layout_weight="1"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter" />

<View
android:id="@+id/vv_vertical_line_02"
android:layout_width="1dp"
android:layout_height="240dp"
android:layout_marginEnd="14dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginStart="14dp"
android:background="@android:color/white" />

<LinearLayout
android:id="@+id/ll_gallery"
android:layout_width="68dp"
android:layout_height="240dp"
android:orientation="vertical">

<ImageView
android:id="@+id/iv_sneaker_02"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_sneaker_03"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="14dp"
android:layout_marginTop="14dp"
android:layout_weight="1"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_sneaker_04"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter" />

</LinearLayout>
</LinearLayout>

<RelativeLayout
android:id="@+id/rl_buy_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_center_and_right"
android:layout_marginTop="14dp"
android:background="@drawable/dark_red_background"
android:padding="6dp">

<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:textColor="@android:color/white"
android:textSize="17sp"
android:textStyle="bold|italic" />

<TextView
android:id="@+id/tv_price_parcels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_price"
android:layout_marginTop="1dp"
android:textColor="@android:color/white"
android:textSize="17sp"
android:textStyle="bold|italic" />

<View
android:id="@+id/vv_vertical_line_03"
android:layout_width="1dp"
android:layout_height="36dp"
android:layout_alignTop="@+id/tv_price"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:layout_toEndOf="@+id/tv_price_parcels"
android:layout_toRightOf="@+id/tv_price_parcels"
android:background="@android:color/white" />

<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/vv_vertical_line_04"
android:layout_alignLeft="@+id/vv_vertical_line_03"
android:layout_alignParentTop="true"
android:layout_alignRight="@+id/vv_vertical_line_04"
android:layout_alignStart="@+id/vv_vertical_line_03"
android:layout_below="@+id/tv_price"
android:layout_marginBottom="4dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="1dp"
android:text="@string/value_stok_invetory"
android:textColor="@android:color/white"
android:textSize="9sp" />

<Spinner
android:id="@+id/sp_amount"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:layout_alignEnd="@+id/vv_vertical_line_04"
android:layout_alignLeft="@+id/vv_vertical_line_03"
android:layout_alignRight="@+id/vv_vertical_line_04"
android:layout_alignStart="@+id/vv_vertical_line_03"
android:layout_below="@+id/tv_amount"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/spinner_mini_border_and_background"
android:entries="@array/amount"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingTop="2dp"
android:popupBackground="#4c4c4c"
android:textColor="@android:color/white"
android:theme="@style/SpinnerTheme" />

<View
android:id="@+id/vv_vertical_line_04"
android:layout_width="1dp"
android:layout_height="36dp"
android:layout_alignTop="@+id/tv_price"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="6dp"
android:layout_toLeftOf="@+id/bt_buy"
android:layout_toStartOf="@+id/bt_buy"
android:background="@android:color/white" />

<Button
android:id="@+id/bt_buy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="@drawable/button_green_background"
android:text="@string/label_buy"
android:textAllCaps="false"
android:textColor="@android:color/white" />
</RelativeLayout>

<TextView
android:id="@+id/tv_free_delivery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/rl_buy_data"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:text="@string/brazil_free_delivery"
android:textColor="#ccffffff"
android:textSize="12sp" />

<ImageView
android:layout_width="16dp"
android:layout_height="15dp"
android:layout_alignTop="@+id/tv_free_delivery"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="1dp"
android:layout_toLeftOf="@+id/tv_free_delivery"
android:layout_toStartOf="@+id/tv_free_delivery"
android:contentDescription="@string/desc_truck_delivery"
android:src="@drawable/ic_truck_white_24dp" />

<View
android:id="@+id/vv_horizontal_line_04"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_free_delivery"
android:layout_marginBottom="9dp"
android:background="@android:color/white" />

<TextView
android:id="@+id/tv_label_recommended"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_04"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:text="@string/label_recommended"
android:textColor="#CCCCCC" />

<TextView
android:id="@+id/tv_recommended"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/tv_label_recommended"
android:layout_toEndOf="@+id/tv_label_recommended"
android:layout_toRightOf="@+id/tv_label_recommended"
android:text="@string/value_recommended"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_label_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_label_recommended"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:text="@string/label_type"
android:textColor="#CCCCCC" />

<TextView
android:id="@+id/tv_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/tv_label_type"
android:layout_toEndOf="@+id/tv_label_type"
android:layout_toRightOf="@+id/tv_label_type"
android:text="@string/value_type"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_label_composition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_label_type"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:text="@string/label_composition"
android:textColor="#CCCCCC" />

<TextView
android:id="@+id/tv_composition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/tv_label_composition"
android:layout_toEndOf="@+id/tv_label_composition"
android:layout_toRightOf="@+id/tv_label_composition"
android:text="@string/value_composition"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_label_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_label_composition"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:text="@string/label_description"
android:textColor="#CCCCCC" />

<TextView
android:id="@+id/tv_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/tv_label_desc"
android:layout_toEndOf="@+id/tv_label_desc"
android:layout_toRightOf="@+id/tv_label_desc"
android:text="@string/value_desc"
android:textColor="@android:color/white" />

<View
android:id="@+id/vv_horizontal_line_05"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_desc"
android:layout_marginBottom="14dp"
android:layout_marginTop="9dp"
android:background="@android:color/white" />

<LinearLayout
android:id="@+id/rl_rating"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/vv_horizontal_line_05"
android:background="@drawable/dark_red_background"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="6dp">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="20dp"
android:paddingLeft="14dp"
android:paddingRight="20dp"
android:paddingStart="14dp">

<ImageView
android:id="@+id/iv_rating_star_01"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_02"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentTop="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_01"
android:layout_toRightOf="@id/iv_rating_star_01"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_03"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentTop="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_02"
android:layout_toRightOf="@id/iv_rating_star_02"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_04"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentTop="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_03"
android:layout_toRightOf="@id/iv_rating_star_03"
android:scaleType="fitCenter" />

<ImageView
android:id="@+id/iv_rating_star_05"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentTop="true"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
android:layout_toEndOf="@id/iv_rating_star_04"
android:layout_toRightOf="@id/iv_rating_star_04"
android:scaleType="fitCenter" />

<TextView
android:id="@+id/tv_rating_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="-2dp"
android:layout_toEndOf="@id/iv_rating_star_05"
android:layout_toRightOf="@id/iv_rating_star_05"
android:textColor="@android:color/white" />
</RelativeLayout>

<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/button_green_background"
android:text="@string/label_access_rating"
android:textAllCaps="false"
android:textColor="@android:color/white" />
</LinearLayout>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout

Agora o layout XML container do layout anterior, /res/layout/activity_sneaker_details.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"
tools:context="thiengo.com.br.tenisshop.SneakerDetailsActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

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

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

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_share_white_24dp" />
</android.support.design.widget.CoordinatorLayout>

 

A seguir o diagrama do layout anterior:

Diagrama do layout activity_sneaker_details.xml

Então o layout XML do dialog de pagamento que será acionado a partir desta atividade de detalhes, segue /res/layout/dialog_payment.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fillViewport="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">

<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="@string/dialog_title"
android:textColor="@color/colorDarkDialog"
android:textSize="25sp" />

<View
android:id="@+id/vv_horizontal_line_01"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_title"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:background="#e4e6e7" />

<ImageView
android:id="@+id/iv_cards"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_01"
android:contentDescription="@string/desc_credit_cards"
android:scaleType="fitCenter"
android:src="@drawable/credit_cards" />

<View
android:id="@+id/vv_horizontal_line_02"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/iv_cards"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:background="#e4e6e7" />

<TextView
android:id="@+id/tv_card_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_02"
android:text="@string/dialog_label_card_number"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<EditText
android:id="@+id/et_card_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_card_number"
android:layout_toLeftOf="@+id/et_card_cvv"
android:layout_toStartOf="@+id/et_card_cvv"
android:background="@drawable/field_border_and_background_dark"
android:hint="@string/dialog_hint_card_number"
android:inputType="number"
android:padding="10dp"
android:textColor="@color/colorDarkDialog" />

<TextView
android:id="@+id/tv_card_cvv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/et_card_cvv"
android:layout_alignStart="@+id/et_card_cvv"
android:layout_below="@+id/vv_horizontal_line_02"
android:text="@string/dialog_label_card_cvv"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<EditText
android:id="@+id/et_card_cvv"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/tv_card_cvv"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:background="@drawable/field_border_and_background_dark"
android:hint="@string/dialog_hint_card_cvv"
android:inputType="number"
android:padding="10dp"
android:textColor="@color/colorDarkDialog" />

<TextView
android:id="@+id/tv_card_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/et_card_number"
android:layout_marginTop="12dp"
android:text="@string/dialog_label_card_owner"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<EditText
android:id="@+id/et_card_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_card_name"
android:background="@drawable/field_border_and_background_dark"
android:inputType="textPersonName"
android:padding="10dp"
android:textColor="@color/colorDarkDialog" />

<TextView
android:id="@+id/tv_card_expired"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/et_card_name"
android:layout_marginTop="12dp"
android:text="@string/dialog_label_card_expire"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_card_expired_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_card_expired"
android:layout_marginTop="-2dp"
android:text="@string/dialog_label_card_expire_month"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<Spinner
android:id="@+id/sp_card_expired_month"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_card_expired_month"
android:background="@drawable/spinner_mini_border_and_background_dark"
android:entries="@array/months"
android:paddingBottom="12dp"
android:paddingTop="12dp"
android:popupBackground="#eee"
android:textColor="@android:color/white"
android:theme="@style/SpinnerThemeDark" />

<TextView
android:id="@+id/tv_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/sp_card_expired_month"
android:layout_marginEnd="6dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_marginStart="6dp"
android:layout_marginTop="7dp"
android:layout_toEndOf="@+id/sp_card_expired_month"
android:layout_toRightOf="@+id/sp_card_expired_month"
android:text="@string/dialog_label_card_expire_bar"
android:textColor="@color/colorDarkDialog"
android:textSize="20sp" />

<TextView
android:id="@+id/tv_card_expired_year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/sp_card_expired_year"
android:layout_alignStart="@+id/sp_card_expired_year"
android:layout_alignTop="@+id/tv_card_expired_month"
android:text="@string/dialog_label_card_expire_year"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<Spinner
android:id="@+id/sp_card_expired_year"
android:layout_width="61dp"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_card_expired_year"
android:layout_toEndOf="@+id/tv_bar"
android:layout_toRightOf="@+id/tv_bar"
android:background="@drawable/spinner_mini_border_and_background_dark"
android:entries="@array/years"
android:paddingBottom="12dp"
android:paddingTop="12dp"
android:popupBackground="#eee"
android:textColor="@android:color/white"
android:theme="@style/SpinnerThemeDark" />

<View
android:id="@+id/vv_vertical_line_01"
android:layout_width="1dp"
android:layout_height="68dp"
android:layout_alignTop="@+id/tv_card_expired"
android:layout_marginBottom="6dp"
android:layout_marginEnd="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_toEndOf="@+id/sp_card_expired_year"
android:layout_toRightOf="@+id/sp_card_expired_year"
android:background="#e4e6e7" />

<TextView
android:id="@+id/tv_buy_parcels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/tv_card_expired"
android:layout_toEndOf="@+id/vv_vertical_line_01"
android:layout_toRightOf="@+id/vv_vertical_line_01"
android:text="@string/dialog_label_card_total_parcels"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<Spinner
android:id="@+id/sp_buy_parcels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/tv_buy_parcels"
android:layout_toEndOf="@+id/vv_vertical_line_01"
android:layout_toRightOf="@+id/vv_vertical_line_01"
android:background="@drawable/spinner_mini_border_and_background_dark"
android:entries="@array/parcels"
android:paddingBottom="12dp"
android:paddingTop="12dp"
android:popupBackground="#eee"
android:textColor="@android:color/white"
android:theme="@style/SpinnerThemeDark" />

<Button
android:id="@+id/bt_finish_buy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/vv_vertical_line_01"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignStart="@+id/vv_vertical_line_01"
android:layout_below="@+id/sp_buy_parcels"
android:layout_marginTop="12dp"
android:background="@drawable/button_green_background"
android:text="@string/label_bt_finish_buy"
android:textAllCaps="false"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignTop="@+id/bt_finish_buy"
android:text="@string/dialog_label_total"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_total_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_total"
android:layout_marginTop="-4dp"
android:textColor="@color/colorDarkDialog"
android:textSize="21sp"
android:textStyle="bold|italic" />

<TextView
android:id="@+id/tv_total_parcels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_total_price"
android:layout_marginTop="-4dp"
android:text="@string/dialog_label_parcels_without_taxes"
android:textColor="@color/colorDarkDialog"
android:textSize="11sp"
android:textStyle="bold" />
</RelativeLayout>
</ScrollView>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout dialog_payment.xml

Para o layout do dialog foi necessário criar dois outros drawables para modificar as cores dos EditTexts e Spinners, atualizar de branco para cinza.

O primeiro é para campos de texto, /res/drawable/field_border_and_background_dark:

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

<solid android:color="@android:color/transparent" />

<corners android:radius="2dp" />

<stroke
android:width="0.8dp"
android:color="@color/colorDarkDialog" />
</shape>

 

O segundo é para campos de seleção, /res/drawable/spinner_mini_border_and_background_dark.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="2dp" />
<stroke
android:width="0.8dp"
android:color="@color/colorDarkDialog" />
</shape>
</item>
<item android:right="0.5dp">
<bitmap
android:tint="@color/colorDarkDialog"
android:gravity="center_vertical|right"
android:src="@drawable/ic_keyboard_arrow_down_white_18dp" />
</item>
</layer-list>

 

No caso do Spinner também temos de ter um tema para a atualização da cor dos textos. Somente relembrando o que já mostramos como parte do arquivo /res/values/styles.xml:

...
<style name="SpinnerThemeDark">
<item name="android:textViewStyle">@style/TextViewStyleDark</item>
</style>
<style name="TextViewStyleDark" parent="android:Widget.TextView">
<item name="android:textColor">@color/colorDarkDialog</item>
</style>
...

 

Com isso o dialog fica como a seguir:

Dialog de pagamento

Ainda temos o XML do menu que permiti a colocação do ícone de "Adicionar ao carrinho". Segue /res/menu/sneaker_details.xml:

<?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/it_search"
android:icon="@drawable/ic_add_shopping_cart_white_24dp"
android:title="Adicionar ao carrinho"
app:showAsAction="ifRoom" />
</menu>

 

Por fim o código Kotlin de SneakerDetailsActivity:

class SneakerDetailsActivity : AppCompatActivity(),
View.OnClickListener,
AdapterView.OnItemSelectedListener {

var model : String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sneaker_details)
setSupportActionBar(toolbar)

fab.setOnClickListener { view ->
Snackbar
.make(view, "Conteúdo compartilhado", Snackbar.LENGTH_LONG)
.setAction("Compartilhar", null)
.show()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)

/*
* Foi prefirível utilizar sneaker como uma propriedade local,
* pois assim evitaremos a necessidade de sempre ter de verificar
* null.
* */
val sneaker = intent.getParcelableExtra<Sneaker>(Sneaker.KEY)

/* MODELO TÊNIS */
model = sneaker.model

/* MARCA */
iv_brand.setImageResource(sneaker.brand.imageResource)
iv_brand.contentDescription = sneaker.brand.name

/* GÊNERO */
Util.setGenre(sneaker, iv_genre_male, iv_genre_female)

/* GALERIA DE IMAGENS */
iv_sneaker_01.setImageResource( sneaker.imageResource )
iv_sneaker_01.contentDescription = "Tênis ${sneaker.model}"
Util.setImageViewBgColor(this, iv_sneaker_01)

iv_sneaker_02.setImageResource( sneaker.album[0] )
Util.setImageViewBgColor(this, iv_sneaker_02)
iv_sneaker_03.setImageResource( sneaker.album[1] )
Util.setImageViewBgColor(this, iv_sneaker_03)
iv_sneaker_04.setImageResource( sneaker.album[2] )
Util.setImageViewBgColor(this, iv_sneaker_04)

/* PREÇOS, QUANTIDADE EM ESTOQUE E BOTÃO COMPRAR */
tv_price.text = "${sneaker.getPriceAsString()} ou em"
tv_price_parcels.text = "até ${sneaker.getPriceParcelsAsString()}"
bt_buy.setOnClickListener(this)

sp_amount.setOnItemSelectedListener(this)

/* RATING, COLOCANDO AS ESTRELAS CORRETAS */
tv_rating_amount.text = "(${sneaker.rating.amount})"
Util.setStar(window.decorView, R.id.iv_rating_star_01, 1, sneaker.rating.stars)
Util.setStar(window.decorView, R.id.iv_rating_star_02, 2, sneaker.rating.stars)
Util.setStar(window.decorView, R.id.iv_rating_star_03, 3, sneaker.rating.stars)
Util.setStar(window.decorView, R.id.iv_rating_star_04, 4, sneaker.rating.stars)
Util.setStar(window.decorView, R.id.iv_rating_star_05, 5, sneaker.rating.stars)
}

/*
* Hackcode para que o título da Toolbar seja alterado de
* acordo com o tênis acionado em lista.
* */
override fun onResume() {
super.onResume()
toolbar.title = model
}

/*
* Somente para apresentarmos o menu item de "adicionar ao
* carrinho"
* */
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_sneaker_details, menu)
return true
}

override fun onClick(view: View?) {
callPaymentDialog()
}

/*
* Hackcode para centralizar o item selecionado no Spinner de
* quantidade a comprar de um mesmo modelo de tênis.
* */
override fun onItemSelected(
adapterView: AdapterView<*>,
view: View,
i: Int,
l: Long ) {

(adapterView.getChildAt(0) as TextView).gravity = Gravity.CENTER
}
override fun onNothingSelected(adapterView: AdapterView<*>?) {}

/*
* Abre o dialog de pagamento com cartão de crédito. AlertDialog
* está sendo utilizado por já ser suficiente a essa necessidade
* do aplicativo, mas um DialogFragment poderia ser utilizado
* também.
* */
fun callPaymentDialog() {
val sneaker = intent.getParcelableExtra<Sneaker>(Sneaker.KEY)
val builder = AlertDialog.Builder(this)
val dialogLayout = getLayoutInflater()
.inflate( R.layout.dialog_payment,null )

builder.setView(dialogLayout)

dialogLayout
.findViewById<TextView>(R.id.tv_total_price)
.text = sneaker.getPriceAsString()

dialogLayout
.findViewById<Button>(R.id.bt_finish_buy)
.setOnClickListener {
val intent = Intent(this, ThankYouActivity::class.java)
intent.putExtra(
Sneaker.KEY,
sneaker)
startActivity(intent)
finish()
}

builder.create().show()
}
}

 

Para simplificar o código, optei por não colocar imagens no Spinner de seleção de cor de tênis e optei também por somente apresentar o número de avaliações junto as estrelas de rating e um botão de carregamento de avaliações. Ao final ainda temos uma tela eficaz para nosso estudo do AppSee SDK:

Tela de detalhes de tênis

Atividade de "Obrigado pela compra"

Para a ThankYouActivity vamos iniciar com o layout de conteúdo, /res/layout/content_thank_you.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"
android:background="@color/colorPrimary"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="thiengo.com.br.tenisshop.ThankYouActivity"
tools:showIn="@layout/activity_sign_up">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">

<TextView
android:id="@+id/tv_excellent_buy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/excellent_buy"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_tracking_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_excellent_buy"
android:layout_marginTop="18dp"
android:textColor="@android:color/white" />

<TextView
android:id="@+id/tv_support_tel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_tracking_code"
android:layout_marginTop="4dp"
android:text="Tel. suporte: 0800 723 6659"
android:textColor="@android:color/white" />

<View
android:id="@+id/vv_horizontal_line_01"
android:layout_width="match_parent"
android:layout_height="1.5dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_support_tel"
android:layout_marginBottom="14dp"
android:layout_marginTop="14dp"
android:background="@android:color/white" />

<ImageView
android:id="@+id/iv_sneaker"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/vv_horizontal_line_01"
android:background="@drawable/image_view_sneaker"
android:scaleType="fitCenter"
android:src="@drawable/shoes_01_a" />

<TextView
android:id="@+id/tv_model"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_sneaker"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/iv_sneaker"
android:layout_toRightOf="@+id/iv_sneaker"
android:textColor="@android:color/white" />

<ImageView
android:id="@+id/iv_brand"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:layout_alignLeft="@+id/tv_model"
android:layout_alignStart="@+id/tv_model"
android:layout_below="@+id/tv_model"
android:layout_marginTop="2dp"
android:layout_toEndOf="@+id/iv_sneaker"
android:layout_toRightOf="@+id/iv_sneaker"
android:src="@drawable/ic_new_balance" />

<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/vv_horizontal_line_01"
android:textColor="@android:color/white"
android:textStyle="italic" />

<View
android:id="@+id/vv_horizontal_line_02"
android:layout_width="match_parent"
android:layout_height="1.5dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/iv_sneaker"
android:layout_marginBottom="14dp"
android:layout_marginTop="14dp"
android:background="@android:color/white" />

<Button
android:id="@+id/bt_keep_buying"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:background="@drawable/button_green_background"
android:onClick="backToTenisShop"
android:text="@string/keep_buying"
android:textAllCaps="false"
android:textColor="@android:color/white" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout content_thank_you.xml

Agora o XML do layout container do layout anterior, /res/layout/activity_thank_you.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"
tools:context="thiengo.com.br.tenisshop.ThankYouActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

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

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

<include layout="@layout/content_thank_you" />
</android.support.design.widget.CoordinatorLayout>

 

A seguir o diagrama do layout container:

Diagrama do layout activity_thank_you.xml

Por fim o código Kotlin de ThankYouActivity:

class ThankYouActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thank_you)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

val sneaker = intent.getParcelableExtra<Sneaker>(Sneaker.KEY)

/* CÓDIGO DE RASTREAMENTO DE COMPRA */
tv_tracking_code.text = "Código de rastreamento: ${codeBuyGenerator()}"

/* MODELO TÊNIS */
tv_model.text = sneaker.model

/* MARCA */
iv_brand.setImageResource(sneaker.brand.imageResource)
iv_brand.contentDescription = sneaker.brand.name

/* IMAGEM */
iv_sneaker.setImageResource( sneaker.imageResource )
iv_sneaker.contentDescription = "Tênis ${sneaker.model}"
Util.setImageViewBgColor(this, iv_sneaker)

/* PREÇO */
tv_price.text = sneaker.getPriceAsString()
}

/*
* Método que simula a criação de uma código de compra
* para rastreamento de mercadoria.
* */
fun codeBuyGenerator(): String {
val random = Random()
var code = ""

for( i in 0..18 ){
code += random.nextInt(10).toString()
}
return code
}

fun backToTenisShop(view: View){
val it = Intent(this, SneakersActivity::class.java)
/*
* Certificando de que não haverá nenhuma outra atividade
* na pilha de atividades quando o button de volta for
* acionado.
* */
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(it)
}
}

 

Com isso temos a seguinte tela de Obrigado:

Tela de Obrigado pela compra

Assim podemos partir para a atualização do app e estudo de caso com o AppSee SDK.

Integração com o AppSee SDK

Com o objetivo de entender o porquê de algumas desistências nos formulários do aplicativo Tênis Shop, vamos integrar o AppSee SDK a este e então estudar os dados e insights apresentados no dashboard.

Instalação do SDK via Gradle App Level

Depois de já ter o cadastro no AppSee site, abra o Gradle App Level, ou build.gradle (Module: app), e então adicione a seguinte referência em destaque:

...
dependencies {
...
/* APPSEE SDK */
implementation 'com.appsee:appsee-android:+'
}

 

Logo depois sincronize o projeto. Lembre que a permissão de Internet já está presente no AndroidManifest do aplicativo.

Atualizando as atividades de entrada

Para nosso aplicativo de exemplo há somente uma atividade de entrada, mesmo para quando o usuário está logado, a LoginActivity. No onCreate() dela adicione, antes do condicional de teste de login, a linha de código em destaque:

class LoginActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)

Appsee.start("Sua_chave_de_api_AppSee");
...
}
...
}

 

Aqui estaremos utilizando a chave de API de testes. Depois de algumas navegações de usuários pelo aplicativo, podemos acessar o dashboard e já ter alguns insights sobre o que pode ser melhorado.

Visão geral do dashboard AppSee

Acessando o dashboard temos a visão geral e logo abaixo, no quadro "UI Insights", há alguns insights de telas do app:

Visão geral do dashboard AppSee

Clicando na tela "Most Viewed Screen" teremos acesso ao heatmap dela.

Obtendo primeiros insights via heatmaps de toque

No início da seção de configuração do AppSee SDK ao aplicativo de exemplo nós estávamos preocupados com os dados de preenchimento de formulários, da desistência desses preenchimentos, certo?

Mas quando acessando a área de heatmaps, "UI Analysis âž™ Screens", é possível verificar que a tela de detalhes de tênis, que é também a tela de compra, é a tela mais acessada (SneakerDetails 83.3%) quando o usuário deixa a tela de listagem de tênis:

Heatmap da tela de listagem de calçados

Quando abrindo o heatmap desta tela, temos algo interessante:

Heatmap da tela de detalhes de tênis

Muitos usuários estão trocando o valor inicial do número do calçado a comprar, número inicial que até o momento é 36.

Isso pode estar diminuindo o número de vendas, pois segundo os testes de Tim Ash em Otimização da Página de Entrada:

"Qualquer passo a mais na jornada de algum funil / conversão é crucial para diminuir o número de conversões de usuários."

O que podemos fazer é verificar, em banco de dados, o tamanho de calçado mais comprado para cada tênis e assim apresentar, inicialmente, o número correto para cada tênis quando a página de detalhes for acionada.

Obviamente podemos fazer a mesma melhoria para o Spinner de "cor de tênis" e o de "quantidade a comprar".

Lembrando que esses são apenas insights, não há certeza de que os números de vendas aumentarão, mas os dados qualitativos de heatmap estão indicando que tendem a melhorar.

Engajamento dos usuários

Acessando os insights de UI Analysis, mais precisamente a seção "User Engagement", temos as principais telas para os usuários, as que mais eles interagem:

Engajamento de usuários AppSee

Fluxos e caminhos de navegação dos usuários

Acessando a opção "Users Flow" temos acesso aos fluxos dos usuários, em porcentagens:

Fluxos de usuários no AppSee

E na opção "Navigation Paths" conseguimos ainda mais organização dos caminhos percorridos, em porcentagens, do que somente com o "Users Flow":

Caminhos de navegação dos usuários no AppSee

Considerações finais de insights

Apesar de o dashboard ter muitas outras opções de análise, mesmo análises qualitativas, neste primeiro teste já obtivemos um excelente insight sobre os valores iniciais dos Spinners da tela de compra.

Caso o aplicativo fosse real, o próximo passo seria trabalhar essa melhoria no app para então aguardar o provável aumento nas vendas.

Assim ficamos por aqui com a apresentação de mais um SDK que auxilia consideravelmente no crescimento de nossos aplicativos Android.

Não deixe de se inscrever na 📩 lista de e-mails do Blog, logo acima ou ao lado, para receber em primeira mão os conteúdos exclusivos sobre o dev Android.

Se inscreva também no canal do Blog em: YouTube Thiengo.

Slides

Abaixo os slides com a apresentação completa da importância da coleta de dados e do AppSee SDK:

Vídeo

Abaixo o vídeo com a implementação passo a passo do AppSee SDK ao aplicativo de mobile-commerce:

Para acesso completo ao projeto Tênis Shop, entre no GitHub a seguir: https://github.com/viniciusthiengo/tenis-shop-app-see.

Conclusão

O AppSee SDK, ou qualquer outro SDK de coleta e análise de dados, tende a ajudar consideravelmente na evolução do aplicativo.

Veja como com apenas a funcionalidade de heatmap, no exemplo anterior, conseguimos um insight que pode aumentar o lucro de uma empresa sem necessidade de maiores investimentos em marketing ou em contratações.

A vantagem do AppSee vem com a possibilidade de podermos ter a análise quantitativa e qualitativa em um único SDK, isso em um dashboard de fácil leitura e com versão gratuita.

Note que mesmo eu indicando SDKs de análises somente para projetos com foco no médio / longo prazo, projetos que tendem a ter somente um aplicativo Android envolvido (e outros únicos de plataformas Web e iOS), você pode seguramente utilizar esse tipo de SDK em sua empresa de construção de aplicativos com foco no curto prazo, somente não esqueça de compartilhar depois como estão indo os resultados.

Não deixe de se inscrever na 📩 lista de e-mails e de deixar o seu comentário sobre o que achou do conteúdo.

Abraço.

Fontes

AppSee Analytics

Documentação Android Native AppSee

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

Iniciando com Anko Kotlin. Intenções no AndroidIniciando com Anko Kotlin. Intenções no AndroidAndroid
ViewModel Android, Como Utilizar Este Componente de ArquiteturaViewModel Android, Como Utilizar Este Componente de ArquiteturaAndroid
BottomNavigationView Android, Como e Quando UtilizarBottomNavigationView Android, Como e Quando UtilizarAndroid
Chips Android, Quando e Como UtilizarChips Android, Quando e Como UtilizarAndroid

Compartilhar

Comentários Facebook

Comentários Blog (3)

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...
Wagner Alves (1) (0)
02/06/2018
Fiz um teste rápido e achei muito interessante estas análises, mas a gravação das telas do app achei meio invasivo. Nos meus testes vi alguns dados que, pensando como usuário do app, não gostaria que fossem gravados e não encontrem como restringir isso. A listagem de dados por exemplo após um cadastro. Gostei bastante da parte de captura das falhas. Vou ver se consigo um tempo para analisar mais, mas a princípio acho que a gravação das telas pode causar algum desconforto para o usuário e problemas para o desenvolvedor. O que acha?
Responder
Vinícius Thiengo (0) (0)
06/06/2018
Wagner, tudo bem?

Na época que utilizei o AppSee SDK era colocada uma tarja preta em todos os campos que eram passíveis de receber dados do usuário (campos de formulário).

Acredito que isso ainda continue, resguardando o user, pois caso contrário o AppSee está infligindo os Termos e Condições de Uso do Google Android.

A única coisa que não "engoli" direito foi o "não pesar" ao aplicativo mesmo quando realizando, em background, o screencast da tela. De qualquer forma, em teste, não tive problemas também com o screencast.

Wagner, se encontrar algum problema, não deixe de compartilhar aqui, se possível.

Abraço.
Responder
wagner (1) (0)
07/06/2018
Blz Thiengo,

Sim, os campos são tarjados, mas uma listagem de dados que existe no meu aplicativo ficou totalmente visível.
Outra coisa é que realmente ele liga. O detalhe é que não tenho fluência no inglês. Aí você já imagina como foi. kkkkkkk

Vlw!
Responder