The Social Project: Configurando a Koin para DI

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último post integramos a métrica de cobertura de código ao nosso projeto. Hoje, vamos começar a mexer um pouco mais com o código da nossa aplicação, começando pela integração de uma biblioteca para nos fornecer um mecanismo de injeção de dependências (ou Dependency Injection – DI).

Quando falamos de DI no Android, logo nos vem a ideia de utilizar o Dagger para tal tarefa. Apesar de ser uma ferramenta muito poderosa, com estabilidade comprovada em produção, ela é um pouco verbosa e com uma quantidade relativamente grande de boilerplate para configurarmos – principalmente quando levamos em consideração um projeto 100% Kotlin como é o nosso caso.

Pensando, então, em um contexto puramente Kotlin, temos duas bibliotecas em evidência atualmente na comunidade, a Kodein e a Koin. A Kodein é uma solução mais robusta, focada em multi-plataforma (pensando nos diversos targets do Kotlin, como JVM, nativo e JavaScript), com uma recente refatoração na versão 5. Já a Koin tem uma abordagem mais minimalista e, ao meu ver, um ponto bem positivo: a integração com os Architecture Components (AC) do Android.

Sem pensar em otimizações prematuras, e imaginando a evolução do projeto, vamos fazer um setup inicial desse nosso mecanismo de fornecimento de dependências, com uma integração inicial com os AC. Eles vão nos ajudar a manter nossas Activities e Fragments mais limpos, isolar lógicas e facilitar testes.

O primeiro passo aqui, será adicionar as dependências da Koin e dos Architecture Components (no nosso caso, ViewModel e LiveData). Vamos adicionar as versões no arquivo build.gradle e as dependências no app/build.gradle.

// build.gradle
buildscript {
ext.versions = [
'kotlin': '1.2.41',
'supportLibrary': '27.1.1',
'jacoco': '0.8.1',
'archComponents': '1.1.1',
'koin': '0.9.3',
]
...
}
view raw snippet01.groovy hosted with ❤ by GitHub
// app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'org.jetbrains.kotlin.kapt'
...
dependencies {
...
implementation "android.arch.lifecycle:extensions:$versions.archComponents"
kapt "android.arch.lifecycle:compiler:$versions.archComponents"
implementation "org.koin:koin-android:$versions.koin"
implementation "org.koin:koin-android-architecture:$versions.koin"
}
view raw snippet02.groovy hosted with ❤ by GitHub

Como os AC necessitam de uma dependência de processador de anotações (annotationProcessor), precisamos aplicar o plugin do kapt (o processador de anotações do Kotlin), e adicionar a dependência do compiler com o escopo kapt.

Primeiramente vamos criar um ViewModel de exemplo, quase um placeholder. Como ainda não temos nenhuma feature no nosso app, faremos isso para validar em parte nossa arquitetura com o Koin. Sendo assim, criei um MainViewModel que será utilizado pela nossa MainActivity (que até então está vazia).

package net.rafaeltoledo.social
import android.arch.lifecycle.ViewModel
class MainViewModel(private val string: String) : ViewModel() {
fun getString() = string
}
view raw snippet03.kt hosted with ❤ by GitHub

Na nossa Activity, vamos pedir uma instância desse ViewModel através do Koin e (por enquanto), simplesmente exibir o conteúdo do método getString() em um TextView.

package net.rafaeltoledo.social
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.TextView
import org.koin.android.architecture.ext.viewModel
class MainActivity : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(TextView(this).apply {
id = R.id.content
text = mainViewModel.getString()
})
}
}
view raw snippet04.kt hosted with ❤ by GitHub

Para que essa “mágica” aconteça – e que o Koin seja capaz de nos fornecer uma instância de qualquer ViewModel -, precisamos configurar os módulos, e ensinar a biblioteca a construir esses objetos.

Primeiramente, perceba que o nosso ViewModel recebe uma String como parâmetro de seu construtor. Para que o Koin nos forneça um objeto do ViewModel, precisamos de alguma forma fornecer esse valor. Para isso, criarei um pacote chamado di, contendo dois arquivos: FirstModule (que fornecerá essa String) e ViewModelModule (que fornecerá a instância do ViewModel).

Para criar os módulos, faremos uso da função applicationContext do Koin:

// FirstModule.kt
package net.rafaeltoledo.social.di
import org.koin.dsl.module.applicationContext
val firstModule = applicationContext {
bean { "Social App" }
}
view raw snippet05.kt hosted with ❤ by GitHub
// ViewModelModule.kt
package net.rafaeltoledo.social.di
import net.rafaeltoledo.social.MainViewModel
import org.koin.android.architecture.ext.viewModel
import org.koin.dsl.module.applicationContext
val viewModelModule = applicationContext {
viewModel { MainViewModel(get()) }
}
view raw snippet06.kt hosted with ❤ by GitHub

Para o valor do tipo String, utilizamos bean, enquanto que, para o ViewModel, utilizamos viewModel. Para fornecer os parâmetros necessários a criação dos objetos (no caso aqui, do ViewModel), basta passarmos get() como parâmetro.

Por final, para amarrarmos todas as pontas, basta configurarmos o Koin na classe Application. Como ainda não temos uma implementação própria, vamos criar uma classe SocialApp para que possamos fazer essa inicialização.

package net.rafaeltoledo.social
import android.app.Application
import net.rafaeltoledo.social.di.firstModule
import net.rafaeltoledo.social.di.viewModelModule
import org.koin.android.ext.android.startKoin
class SocialApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin(listOf(
viewModelModule,
firstModule
))
}
}
view raw snippet07.kt hosted with ❤ by GitHub

A inicialização do Koin se dá por meio da extension function startKoin(), para a qual passamos a lista de módulos – no nosso caso, viewModelModulefirstModule. É importante lembrar de adicionar a nossa implementação da Application ao AndroidManifest.

O resultado disso é a nossa string exibida corretamente na MainActivity.

Tudo pronto? Ainda não! Cadê os testes?

Todo esse overhead de configuração é inútil se não estamos utilizando com algum propósito. Além de deixar nossas classes mais enxutas, a ideia é facilitar a troca de objetos por mocks ou mesmo por valores controlados para a execução dos testes.

Apesar de não ser o ideal, por enquanto vamos continuar com o Robolectric. Como nosso setup do Koin está localizado na Application, precisamos substituir ou alterar a sua implementação para que possamos preparar a execução dos testes. Nesse aspecto, o Robolectric possui uma facilidade muito bacana: basta criar uma classe com o mesmo nome da sua implementação de Application, utilizando o prefixo Test. Com isso, ele automaticamente utilizará a versão de testes da sua Application durante a execução.

Sendo assim, dentro do nosso source set de testes, criarei um arquivo chamado TestSetup.kt com o seguinte conteúdo:

// TestSetup.kt
package net.rafaeltoledo.social
import android.app.Application
import net.rafaeltoledo.social.di.viewModelModule
import org.koin.android.ext.android.startKoin
import org.koin.dsl.module.applicationContext
class TestSocialApp : SocialApp() {
fun overrideStringValue(newValue: String) {
// Override value
// This behavior should be explicit in a future version of Koin
// See: https://github.com/Ekito/koin/pull/123
loadKoinModules(listOf(
applicationContext { bean { newValue } }
))
}
}
view raw snippet08.kt hosted with ❤ by GitHub

Aqui, a ideia é deixar o valor a ser injetado configurável, para que possamos controlá-lo durante a execução dos testes. Assim, um teste que verifica se o valor inserido foi exibido corretamente na tela ficaria:

package net.rafaeltoledo.social
import android.widget.TextView
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
class MainActivityTest {
@Test
fun checkIfActivityIsSuccessfullyCreated() {
// Arrange
val newValue = "Test Social App"
val app = RuntimeEnvironment.application as TestSocialApp
app.overrideStringValue(newValue)
val activity = Robolectric.setupActivity(MainActivity::class.java)
// Act - nothing to do
// Assert
val text = activity.findViewById(R.id.content)
assertThat(text.text).isEqualTo(newValue)
}
}
view raw snippet09.kt hosted with ❤ by GitHub

Para deixar as asserções mais fluídas, estou utilizando o Truth.

Ao abrir um Pull Request para o repositório, temos, então, uma surpresa!

O Coveralls percebeu que tivemos uma queda na cobertura de código (de 100% para 65%) e nos avisou! Esse é exatamente o intuito de mapear essa métrica, para percebermos o quanto um determinado código está impactando na cobertura de testes. Por mais que ter uma cobertura de código alta não seja sinônimo de qualidade, ela pode indicar que estamos escrevendo código sem testar, o que definitivamente não é legal!

Nesse caso específico, tínhamos a utópica marca de 100% porque tínhamos apenas uma classe sem código algum, então a queda já era esperada.

Bom, por hoje é isso! O PR com essas modificações foi aberto, aprovado e mergeado. Vale salientar aqui que a implementação inicial teve uma crítica bem legal do Victor Nascimento. A ideia é exatamente essa, os PRs devem gerar discussões técnicas relevantes.

Até a próxima!

The Social App: Integrando dados de Cobertura de Código

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último post realizamos uma configuração inicial do tema do nosso aplicativo de forma a diferenciar as builds de debug e release. No post de hoje, vamos configurar uma métrica importante para o desenvolvimento de qualquer projeto de software, a cobertura de código.

Simplificando ao máximo o conceito (já que a explicação dele foge um pouco ao escopo deste post), a métrica diz respeito a quanto do seu código é “exercitado” durante a execução dos testes. Os relatórios de cobertura geralmente fornecem formas de mensurar não só percentualmente quanto do código foi coberto pelos testes, mas também pode dar insights sobre quais partes do código poderiam receber mais testes.

O primeiro passo é habilitarmos a geração desse dado no projeto. Porém, neste momento não é possível gerar este dado, já que nosso projeto não possui nenhum teste. Na verdade, ele praticamente não possui código, somente uma Activity vazia que ainda não faz nada. Poderíamos criar um teste simples de Espresso, para validar a inicialização da Activity, porém ainda não resolvemos o problema da execução de testes instrumentados no nosso CI (inclusive, se você quer ler mais sobre a diferença entre testes instrumentados e locais, tem um post excelente do Victor Nascimento no Medium). Dado esse cenário, vou utilizar o Robolectric para criar um teste que, apesar de utilizar classes do Android, roda localmente na JVM.

Para integrarmos o Robolectric em nosso projeto, é necessário adicionar a sua dependência, dentro do escopo de testes (testImplementation). Além disso, como o Robolectric utiliza resources do Android, precisamos habilitar a disponibilização dos resources para os testes unitários. Caso contrário, nossos testes falharão por não encontrar qualquer referência a resources adicionados por nós no projeto (como layouts, strings, drawables e outros).

android {
...
testOptions {
unitTests.includeAndroidResources true
}
}
dependencies {
...
testImplementation 'org.robolectric:robolectric:3.8'
}
view raw snippet01.groovy hosted with ❤ by GitHub

Dentro da pasta src/test/kotlin, vamos criar uma classe de teste bem simples, chamada MainActivityTest, que simplesmente fará uma validação se a Activity foi criada com sucesso pelo Robolectric. Apesar de ser um teste sem muito valor para o app (afinal, estamos testando se o Robolectric funciona), esse teste gerará alguma cobertura para o nosso projeto.

package net.rafaeltoledo.social
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class MainActivityTest {
@Test
fun checkIfActivityIsSuccessfullyCreated() {
assertNotNull(Robolectric.setupActivity(MainActivity::class.java))
}
}
view raw snippet02.kt hosted with ❤ by GitHub

Com o teste criado (e passando!), vamos configurar a geração dos dados de cobertura. Eu já escrevi dois posts sobre o assunto, então não vou me alongar muito sobre os detalhes de implementação aqui para não deixar o este post muito longo 🙂

Criei um script chamado coverage.gradle, que coloquei na pasta gradle/ do projeto (inicialmente, tinha colocado numa pasta chamada tools/, mas após algumas discussões a respeito, pareceu fazer mais sentido a primeira opção).

apply plugin: 'jacoco'
jacoco.toolVersion versions.jacoco
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
}
def classes = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug")
def sources = files("$projectDir/src/main/kotlin")
def report = "$buildDir/reports/jacoco/report.xml"
task createCombinedCoverageReport(type: JacocoReport,
dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
sourceDirectories = sources
classDirectories = files(classes)
executionData = fileTree(dir: buildDir, includes: [
'jacoco/testDebugUnitTest.exec',
'outputs/code-coverage/connected/*coverage.ec'
])
reports {
xml.enabled = true
xml.destination file(report)
html.enabled = true
}
}
view raw snippet03.groovy hosted with ❤ by GitHub

Bom, a parte de configuração do relatório em si é muito próxima do que expliquei nos dois posts sobre o assunto. Algumas coisas estão isoladas em variáveis (sourcesreport), pois vamos reutilizá-las já já.

Com isso, vamos incluir o arquivo no build.gradle do módulo app e habilitar a geração de cobertura de código para os testes instrumentados:

apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply from: "$rootDir/gradle/coverage.gradle"
android {
...
buildTypes {
debug {
testCoverageEnabled true
...
}
...
}
}
...
view raw snippet04.groovy hosted with ❤ by GitHub

Para finalizar, vamos setar a versão do jacoco no classpath para a última versão, modificando o build.gradle na raiz do nosso projeto:

buildscript {
ext.versions = [
'kotlin': '1.2.31',
'supportLibrary': '27.1.1',
'googleServices': '12.0.1',
'jacoco': '0.8.1'
]
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
classpath 'com.google.gms:google-services:3.2.1'
classpath "org.jacoco:org.jacoco.core:$versions.jacoco"
}
}
view raw snippet05.groovy hosted with ❤ by GitHub

Feito isso, já podemos executar a task createCombinedCoverageReport e deveremos obter o relatório com incríveis 100% de cobertura, disponível dentro da pasta app/build/reports/jacoco

Perceba que ele foi gerado pela versão correta do Jacoco que configuramos, 0.8.1.

Com a métrica sendo gerada, é importante que ela esteja visível dentro do nosso fluxo de trabalho, para que possamos acompanhar a sua evolução ao longo do desenvolvimento do projeto. Para isso, precisamos “publicar” essa informação em algum lugar.

O Jenkins, por exemplo, oferece formas de acompanhar isso, caso que não ocorre com o CircleCI. Para isso, vamos utilizar um serviço externo, no caso o Coveralls. Durante o desenvolvimento, validei também o Codecov, porém ele não se comportou muito bem com o Kotlin. O Codecov irá se plugar ao nosso fluxo, validando a cobertura atual em cada Pull Request, e exibirá um histórico de como essa métrica evolui a cada commit.

Para que essa integração funcione, precisamos de enviar essa métrica para o Coveralls. Faremos isso através de um plugin. A configuração dele é bem simples:

// build.gradle
buildscript {
...
repositories {
google()
jcenter()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
classpath 'com.google.gms:google-services:3.2.1'
classpath "org.jacoco:org.jacoco.core:$versions.jacoco"
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2'
}
}
view raw snippet06.groovy hosted with ❤ by GitHub

No arquivo coverage.gradle, faremos a aplicação do plugin, bem como a sua configuração:

// coverage.gradle
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'
...
def classes = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug")
def sources = files("$projectDir/src/main/kotlin")
def report = "$buildDir/reports/jacoco/report.xml"
...
coveralls {
sourceDirs = sources.flatten()
jacocoReportPath = report
}
view raw snippet07.groovy hosted with ❤ by GitHub

Com isso, o último passo é configurar a chave de upload dos relatórios no CI. Para isso, basta criarmos uma variável de ambiente chamada COVERALLS_REPO_TOKEN e colocar o valor fornecido no painel do Coveralls.

Feito isso, o último passo é editar o nosso arquivo de configuração do CI para incluir algumas coisas:

version: 2
jobs:
build:
docker:
- image: circleci/android:api-27-alpha
working_directory: ~/social-app
environment:
JVM_OPTS: -Xmx3200m
CIRCLE_JDK_VERSION: oraclejdk8
steps:
- checkout
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Accept licenses
command: yes | sdkmanager --licenses || true
- run:
name: Decrypt release key
command: openssl aes-256-cbc -d -in distribution/release.keystore-cipher -out distribution/release.keystore -md sha256 -k $CIPHER_DECRYPT_KEY
- run:
name: Setup Google Services JSON
command: |
mkdir -p app/src/debug/ && touch app/src/debug/google-services.json
echo "${JSON_FIREBASE_DEVELOPMENT}" >> "app/src/debug/google-services.json"
mkdir -p app/src/release/ && touch app/src/release/google-services.json
echo "${JSON_FIREBASE_RELEASE}" >> "app/src/release/google-services.json"
- run:
name: Run Linters
command: ./gradlew check
- run:
name: Run Tests and generate Code Coverage
command: ./gradlew createCombinedCoverageReport
- run:
name: Upload code coverage data
command: ./gradlew coveralls
- run:
name: Build
command: ./gradlew assemble assembleAndroidTest
- store_artifacts:
path: app/build/reports/jacoco/createCombinedCoverageReport
destination: coverage-report
- store_artifacts:
path: app/build/reports/tests/testDebugUnitTest
destination: local-test-report
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
view raw snippet08.yaml hosted with ❤ by GitHub

Aqui eu fiz algumas modificações. Primeiramente, separei as tasks de linters, testes e build (a partir da linha 35), adicionando a task que vai enviar os dados para o Coveralls (Upload code coverage data). Além disso, comecei a disponibilizar no CircleCI os relatórios e de cobertura e execução de testes (utilizando a configuração store_artifacts). Com isso, após a execução de uma build com sucesso, é possível navegar por esses artefatos, desde que esteja logado.

Ufa! É isso! Você pode conferir no repositório o resultado dessas mudanças. A partir de agora, a cobertura de código é exibida também no README do projeto e como uma etapa de verificação nos PRs abertos!

The Social Project: Configurando o Tema e Identificando suas Builds

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último post configuramos o Firebase no projeto, ao mesmo tempo em que mantivemos os JSONs de configuração seguros no nosso CI. Hoje vamos começar a preparar nosso app para soltar as primeiras releases.

Trazendo para o projeto algumas experiẽncias que tive em projetos que participei, posso dizer que a diferenciação entre o app em tempo de desenvolvimento e as releases é um problema bastante menosprezado quando estamos trabalhando em um projeto. É muito comum chegarmos ao ponto de não saber ao certo qual é a versão que estamos utilizando, e a inconsistência de comportamento acaba por nos colocar em situações complicadas (como testar o app errado, inconsistência de dados, dentre vários outros).

O primeiro passo, a viabilidade de que tanto as builds de release quanto as de debug possam conviver harmônicamente no mesmo device já foi resolvido no post anterior, através do applicationIdSuffix para o build type de debug.

O que faremos agora é criar uma identidade visual diferente para cada um dos build types.

Aqui vai um aviso: como ainda não temos um design definitivo, ou uma identidade visual para o nosso aplicativo, vai ser algo meio grotesco mesmo, mais como um placeholder do que algo definitivo, ou apropriado para um produto final 🙂

Como ícone do aplicativo, utilizei um dos ícones do banco de ícones do Material Design. São ícones para os mais diversos propósitos, divididos em várias categorias, otimizados tanto para mobile quanto para web.

Sim. Esse é o ícone do nosso app! 😂

Para prosseguirmos, também utilizei a ferramenta de cores do site do Material Design, que me ajudou a encontrar uma combinação de cores com bom contraste e gerar as suas variações mais clara e mais escura. Assim, os nosso ícones ficaram:

Sendo o ícone da esquerda a versão do aplicativo de release e a versão da direita a versão do app de debug.

Aqui, basicamente a ideia foi inverter as cores primary e accent para cada um dos build types. Dessa forma, o aplicativo final terá o verde como primary e o laranja como accent, enquanto a versão de debug terá o laranja como primary e o verde como accent. (vamos ver como isso vai ficar ao longo do projeto – designers, me salvem aqui! 😂)

Para que essa façanha funcione no aplicativo, precisamos fazer uso das diferentes pastas de source. Como pode ser visto no repositório, basicamente eu dupliquei os resources relacionados ao tema e ao ícone, separando suas respectivas versões em src/debug/ressrc/release/res. É importante salientar que os resources devem sempre ter o mesmo nome e os mesmos atributos nos XMLs para as duas pastas, caso contrário teremos problemas ao compilar o aplicativo.

O Android Gradle Plugin (AGP) tem esse mecanismo bacana. Com o mecanismo de build types (e flavors), conseguimos criar variações de um mesmo app facilmente. Da forma como fizemos, o aplicativo de debug será criado mesclando o conteúdo de src/mainsrc/debug, enquanto o de release será criado com o conteúdo de src/mainsrc/release.

Bom, aproveitando, também atualizei algumas coisas no repositório, como o Kotlin para a versão 1.2.31, e o Google Play Services (e Firebase) para a versão 12.0.1. Também atualizei o AGP para a versão 3.1.0, dando suporte ao Android Studio 3.1.

Com isso, tanto nosso app em debug e em release podem conviver em harmonia dentro do mesmo dispositivo.

E é isso! Lembrando que o código já foi mergeado e está disponível na branch develop do projeto.

Até a próxima!

The Social Project: Configurando o Firebase no Projeto

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último post configuramos as chaves de assinatura do nosso app de maneira segura, garantindo que os dados não estejam expostos no repositório e viabilizando que o CI consiga buildar e assinar os APKs. No post de hoje, vamos fazer a configuração do projeto no Firebase.

O primeiro passo é criarmos os projetos em nossa conta Google. Por que escrevi no plural? Porque faz todo sentido separarmos os ambientes, tendo um projeto para desenvolvimento (onde poderemos testar das mais diversas formas desde formato de dados, notificações, configurações e outros) e outro para produção, podendo assim realizar diversos experimentos sem impactar o app já publicado.

No meu caso, criei dois projetos no painel do Firebase, dando o nome de Social App DevelopmentSocial App Production.

Com os dois projetos criados, vamos configurar o nosso app. Primeiramente o build.gradle principal do projeto:

buildscript {

  ext.versions = [
      'kotlin': '1.2.30',
      'supportLibrary': '27.1.0',
      'googleServices': '12.0.0'
  ]
    
  repositories {
    google()
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:3.0.1'
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
    classpath 'com.google.gms:google-services:3.2.0'
  }
}

allprojects {
  repositories {
    google()
    jcenter()
  }

  configurations.all {
    resolutionStrategy {
      eachDependency { details ->
        if (details.requested.group == 'com.android.support'
            && details.requested.name != 'multidex'
            && details.requested.name != 'multidex-instrumentation') {
          details.useVersion versions.supportLibrary
        }
      }
    }
  }
}

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

Fizemos algumas boas mudanças no arquivo. Primeiramente criei um bloco para padronizar a configuração de versões que se repetem em múltiplos locais do projeto – a versão do Kotlin, da Support Library e das bibliotecas Google (Firebase e Play Services). Em seguida, adicionamos o plugin google-services em sua última versão, que se encarregará de configurar o nosso projeto.

Nesse arquivo, também, coloquei uma pequena configuração para forçar todas as Support Libraries a compartilharem da mesma versão. Por quê? Muitas vezes, ao adicionar uma outra dependência – por exemplo, o próprio Firebase – ele depende de uma versão de alguma Support Library mais antiga que a utilizada no projeto. Uma das formas de se resolver isso é dando um exclude na dependência e adicionando manualmente a versão mais recente. Particularmente não gosto dessa abordagem por poluir muito o bloco de dependências. Dessa forma, temos esse bloco de configuração que vai garantir que as Support Libraries estejam todas na versão 27.1.0 (exceto as bibliotecas de Multidex, que possuem um versionamento próprio).

Em seguida, vamos configurar o build.gradle do projeto app.

android {
  ...
  buildTypes {
    debug {
      applicationIdSuffix '.dev'
      signingConfig signingConfigs.debug
    }
    ...
  }
}

dependencies {
  implementation "com.android.support:appcompat-v7:$versions.supportLibrary"

  implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$versions.kotlin"

  implementation "com.google.firebase:firebase-core:$versions.googleServices"
  ...
}

apply plugin: 'com.google.gms.google-services'

Aqui, primeiramente, adicionamos um sufixo ao applicationId para o build type debug. Isso vai facilitar mantermos tanto a versão de produção quanto a de desenvolvimento no mesmo dispositivo. Em seguida, configurei as dependências para utilizar as versões definidas globalmente e adicionei a dependência firebase-core, que já nos adiciona o analytics, por exemplo. Por fim, aplicamos o plugin google-services no final do arquivo, conforme a documentação.

Bom, feito isso, vamos configurar os aplicativos no painel do Firebase.

Aqui, vamos inserir o nome do pacote (net.rafaeltoledo.social para o app de produção, net.rafaeltoledo.social.dev para o app de desenvolvimento), opcionalmente um apelido e o hash SHA-1 de assinatura. Podemos facilmente conseguir esse hash através da task signingReport do Gradle. Para executá-la, certifique-se de que a chave de produção esteja descriptografada e que as variáveis de ambiente com as senhas estejam exportadas.

Com o resultado dessa task, copie o hash SHA-1 de debug e release e coloque em cada um dos projetos. Com isso, teremos dois arquivos google-services.json, um para cada projeto.

Pronto, agora é só commitar e seguir o jogo? Não necessariamente…

Quando você está desenvolvendo um projeto, idealmente você não deveria deixar estes arquivos no seu controle de versão. Primeiramente, porque alguém poderia criar um app com o mesmo applicationId e utilizá-lo para acessar o seu projeto. Segundo, que poderia com isso também comprometer a sua cota de serviços e, caso você utilize um dos serviços pagos do Firebase, ocasionar em gastos para você. Portanto, trate o arquivo google-services.json também como uma chave do app.

Para resolver esse problema, primeiramente coloquei o arquivo no .gitignore, para que ele não seja adicionado ao Git. Em seguida, vamos colocar o conteúdo do arquivo no CI, para que sejam colocados no projeto na hora do build. Farei isso colocando o conteúdo em uma variável de ambiente e, através do comando echo, mandando o conteúdo pra um arquivo.

O conteúdo desse arquivo é simplesmente um JSON, então a minha recomendação é, antes de criar as variáveis, utilizar algum serviço de minificação (ou minify) para remover espaços e quebras de linha desnecessárias.

Por fim, basta adicionar uma etapa no arquivo de configuração do CI para obter o valor da variável e jogar no arquivo corretamente dentro de cada uma das pastas (app/src/release app/src/debug).

- run:
    name: Setup Google Services JSON
    command: |
      mkdir -p app/src/debug/ && touch app/src/debug/google-services.json
      echo "${JSON_FIREBASE_DEVELOPMENT}" >> "app/src/debug/google-services.json"
      mkdir -p app/src/release/ && touch app/src/release/google-services.json
      echo "${JSON_FIREBASE_RELEASE}" >> "app/src/release/google-services.json"

E é isso. Configuramos corretamente o projeto e mantivemos nossos segredos em segurança 🙂

O código já foi mergeado no repositório após um PR e a build no CI 🙂

The Social Project: Configurando a Assinatura do App no CI

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último artigo, configuramos o nosso build script para suportar o Kotlin. No post de hoje, vamos configurar a assinatura do app e levantar um ponto importante: como guardar seus segredos, principalmente quando o app é open source?

Com certeza, se você procurar um pouco, você encontrará diversos repositório com todo tipo de informação privada, desde chaves de APIs a ferramentas pagas. Todas, commitadas de forma acidental (ou mesmo proposital, mas de forma inadvertida) no repositório público do projeto.

No Android, um dos maiores “segredos” que mantemos é a chave de assinatura do app. Ela, juntamente com o Application ID, fazem um app se tornar único. Como o identificador é público, o vazamento de uma chave pode gerar um seríssimo risco de segurança, possibilitando que versões modificadas do seu app sejam passíveis de instalação sobre o app original, não só possibilitando comportamentos potencialmente perigosos para o usuário, como o acesso a informações internas e privadas que eventualmente o seu app tenha salvo localmente (como dados de autenticação na sua API, por exemplo).

A perda dessa chave também pode ser um problemão. Sem ela, é impossível enviar atualizações do seu app para o Google Play, por exemplo. Hoje felizmente já temos formas de minimizar esse risco, utilizando o Google Play App Signing (assunto que com certeza será abordado nessa série de posts).

Falando especificamente do nosso app, temos então um problema com relação a isso. Para a assinatura, precisamos de um arquivo (geralmente com a extensão .keystore ou .jks), duas senhas e um alias. O primeiro questionamento é sobre o arquivo em si. Colocaremos ele no repositório? Por mais que a chave necessite das senhas, ter o arquivo exposto envolve algum grau de risco.

Para solucionar esse problema temos basicamente duas abordagens: a primeira delas seria colocar o arquivo de assinatura em algum serviço externo, como Google Drive ou Dropbox, e baixá-lo no momento da build utilizando curl. Nesse cenário, teríamos uma chave de API exposta via variável de ambiente. Apesar de viável, esse cenário tem o problema de estarmos acoplando uma dependência externa ao simples processo de build do APK. Além de oscilações de rede que podem causar falha da build, também temos que manter essa request em dia, tendo que ajustar a chamada de download no caso de eventuais mudanças nas APIs desses serviços.

A segunda abordagem, e é a que vou utilizar aqui, será criptografar o arquivo e colocá-lo no repositório junto com o projeto. No CI, teremos exportado como variável de ambiente a chave que consegue descriptografar o arquivo, bem como as outras chaves necessárias para assinar o APK.

A maneira mais simples de gerarmos uma chave é através do próprio Android Studio.

Com a chave gerada, utilizaremos o openssl para criptografar o arquivo. Podemos fazer isso utilizando o comando:

openssl aes-256-cbc -e -in release.keystore -out release.keystore-cipher -md sha256 -k $CIPHER_DECRYPT_KEY

Perceba que no comando temos uma variável chamada CIPHER_DECRYPT_KEY, que é a chave que irá descriptografar o nosso arquivo lá no CI.

Com o arquivo criptografado gerado, vamos criar uma pasta no projeto para guardar as chaves. Além do arquivo criptografado release.keystore-cipher, também estou colocando o arquivo que assina as releases de debug. Isso é particularmente útil quando existe mais de uma pessoa trabalhando no projeto ou quando eventualmente há a necessidade de se trocar ou formatar a máquina, já que essa chave é gerada sempre que uma nova instalação da SDK do Android é realizada.

Com os arquivos no projeto, vamos primeiramente dizer ao CI para descriptografar o arquivo antes de executar a build. Perceba que adicionamos um passo chamado Decrypt release key ao nosso arquivo config.yml. Também modifiquei o passo de build para chamar a task assemble em vez da build (para validar a parte de assinatura de ambos os build variants):

version: 2
jobs:
  build:
    docker:
      - image: circleci/android:api-27-alpha

    working_directory: ~/social-app

    environment:
      JVM_OPTS: -Xmx3200m
      CIRCLE_JDK_VERSION: oraclejdk8

    steps:
      - checkout

      - restore_cache:
          key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}

      - run:
          name: Accept licenses
          command: yes | sdkmanager --licenses || true

      - run:
          name: Decrypt release key
          command: openssl aes-256-cbc -d -in distribution/release.keystore-cipher -out distribution/release.keystore -md sha256 -k $CIPHER_DECRYPT_KEY

      - run:
          name: Build
          command: ./gradlew clean check assemble assembleAndroidTest

      - save_cache:
          paths:
            - ~/.gradle
          key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}

Feito isso, vamos ao nosso build script configurar a assinatura e atribui-las para cada um dos build variants:

...

android {
  ...
  signingConfigs {
    debug {
      storeFile file("$rootDir/distribution/debug.keystore")
      storePassword 'android'
      keyAlias 'androiddebugkey'
      keyPassword 'android'
    }

    release {
      storeFile file("$rootDir/distribution/release.keystore")
      storePassword System.env.RELEASE_STORE_PASSWORD
      keyAlias System.env.RELEASE_KEY_ALIAS
      keyPassword System.env.RELEASE_KEY_PASSWORD
    }
  }

  buildTypes {
    debug {
      signingConfig signingConfigs.debug
    }

    release {
      signingConfig signingConfigs.release
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
}

...

Perceba que a parte da assinatura de release vai pegar também os dados através de variáveis de ambiente, que serão adicionadas ao CircleCI:

E é isso! O Pull Request foi aberto e aprovado para este caso, tendo passado no CI e integrado na branch develop do repositório.

No próximo post vamos fazer a configuração do Firebase no projeto 🙂

The Social Project: Configurando Kotlin no projeto

Olá pessoal!

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

No último post, configuramos o CircleCI para observar as branches do nosso projeto, bem como a abertura de pull requests no repositório. Hoje, vamos olhar um pouco mais o projeto e começar a estruturá-lo para posteriormente iniciarmos a implementação de fato.

O projeto em si foi criado a partir do template padrão do Android Studio 3.0.1, sem qualquer modificação na sua estrutura, e sem a inclusão do Kotlin por padrão. Meu intuito é entendermos o que é necessário para transformarmos um projeto Android com Java em um projeto Android com Kotlin.

O primeiro passo aqui é ajustar o arquivo build.gradle principal, aquele localizado na raiz do projeto. Inicialmente, vamos remover os comentários vindos do template. Eles são informativos sobre como configurar as dependências do projeto, porém podemos removê-los tranquilamente, já que sabemos onde vamos colocar cada coisa.

Em seguida, vamos criar uma variável, chamada de kotlinVersion, para guardar a versão do Kotlin que estaremos utilizando no projeto. É importante que essa versão esteja separada, pois ela deve ser a mesma em todas as dependências relacionadas ao Kotlin no projeto. Aqui fica uma ressalva: perceba que utilizamos a nomenclatura com Camel Case (enquanto o template do AS para Kotlin utiliza Snake Case). O Groovy (linguagem utilizada nos scripts do Gradle) tem convenções de código muito semelhantes ao Java. Assim, utilizamos Snake Case apenas nos casos onde o valor é uma constante, e sempre em caixa alta.

Após isso, adicionamos o plugin de Kotlin para Gradle, fazendo uso da interpolação de strings para inserir a versão. Assim, nosso arquivo build.gradle ficará assim:

buildscript {

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

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

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

Vamos agora editar então o arquivo de build do módulo app (localizado em app/build.gradle). Nele, primeiramente aplicamos o plugin do Kotlin, seguindo a nomenclatura mais recomendada pelo Gradle – utilizando o namespace. Dessa forma, aplicamos o plugin org.jetbrains.kotlin.android. Em seguida, vamos atualizar o tooling para a última versão do Android (atualmente a API 27). Como o Android Studio 3.0 já foi lançado a algum tempo, o seu template padrão ainda aponta para a API 26. Assim, apontamos as versões de compileSdkVersiontargetSdkVersion para 27, além de atualizarmos a versão da Support Library para 27.1.0, a versão mais recente neste momento.

O próximo passo para completar a integração do Kotlin no projeto é a adição da standard library do Kotlin. Essa dependência possui 3 versões: kotlin-stdlib (Java 6), kotlin-stdlib-jre7 (Java 7) e kotlin-stdlib-jre8 (Java 8). Particularmente eu adiciono a versão jre7, primeiro pelo fato da nossa API mínima ser KitKat (que já suporta as features do Java 7). Poderíamos utilizar a jre8, porém isso faria com que o processo de desugaring ocorresse durante a compilação, o que pode impactar no nosso tempo de build.

Por fim, dois pontos de ajuste que particularmente recomendo por questões de organização. O primeiro é a adição de source sets específicos para Kotlin. Quando temos um projeto Java, os arquivos fonte geralmente ficam na pasta src/main/java. Assim, adicionamos aos source sets src/main/kotlin. Ao meu ver, essa organização é particularmente útil quando o projeto acaba tendo arquivos de ambas as linguagens (o que não deve ocorrer no nosso caso) e para que a estrutura de diretórios fique mais semântica. O segundo ponto de ajuste, mais estético, é a padronização do uso de aspas simples e duplas no arquivo. Aspas duplas, somente quando houver interpolação na string.

Nosso arquivo, então, fica dessa forma:

apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'

android {
    compileSdkVersion 27

    defaultConfig {
        applicationId 'net.rafaeltoledo.social'
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName '1.0'

        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        test.java.srcDirs += 'src/test/kotlin'
        androidTest.java.srcDirs += 'src/androidTest/kotlin'
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation 'com.android.support:appcompat-v7:27.1.0'

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlinVersion"

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

Aproveitando que estamos modificando as configurações de build do nosso projeto, recomendo atualizarmos também a versão do Gradle que está sendo utilizada. Para isso, basta gerarmos o wrapper apontando para a versão mais recente (4.6 no momento em que escrevo este post). A não ser quando há imcompatibilidades entre o plugin do Android e o Gradle, sempre recomendo a utilização da versão mais recente do Gradle, já que ela costuma trazer melhorias importantes de performance (o que é sempre bem-vindo). Para atualizar o wrapper, basta executar o seguinte comando:

./gradlew wrapper --gradle-version 4.6 --distribution-type all

Para que nosso aplicativo já tenha uma Activity inicial, também adicionei uma MainActivity vazia e adicionei ao Android Manifest. Também aproveitei e exclui os testes que vem por padrão no template do Android Studio – assim que começarmos a entrar nas features do app, escreveremos os nossos 🙂

Seguindo o nosso Git Flow, criei um Pull Request para a branch develop, que, assim que passou no CI, foi mergeado 🙂

Por hoje é isso. Apesar deste post não mostrar nada muito novo, acho importante configurarmos o Kotlin por nós mesmos e saber o que muda no projeto. Afinal, os build scripts também são código, e mantê-los organizados faz parte da saúde do projeto. Aguardem que no próximo post teremos mais coisas interessantes.

Até lá!

Layouts e Resources – Tutorial Android 4

Olá pessoal! No último post criamos a nossa primeira Activity, e entendemos um pouco sobre Intents! No post de hoje vamos entender um pouco mais sobre resources no Android e criar um layout para a nossa Activity. Preparados?

Para o tutorial de hoje, vamos trabalhar majoritariamente com a pasta res, que fica localizada no caminho app/src/main/res. É nela que ficam todos os recursos do seu projeto, que não são necessariamente código Java. O projeto, no momento, deve ter algumas pastas, como drawable (vazia), mipmap (em algumas versões com sufixos estranhos) e values.

Um projeto Android padrão costuma ter várias outras, que entraremos em mais detalhes a medida que forem necessárias. Essas pastas separam os tipos de recursos que temos em nosso projeto. Por exemplo, drawable, conterá qualquer tipo de coisa desenhável, desde formas, a imagens e seletores. A mipmap contém somente o ícone do aplicativo. A pasta values contém valores em gerais, desde inteiros, até strings, cores, dimensões, estilos e etc.

Se você abrir cada uma das pastas (mipmap-hdpimipmap-mdpimipmap-xhdpi, etc), você verá que temos o ícone do aplicativo em diferentes tamanhos. Isso ocorre porque os dispositivos Android possuem diversas configurações. Configuração diz respeito a tudo no dispositivo, desde idioma, até o que chamamos de densidade da tela. Uma maior densidade, quer dizer que aquele dispositivo tem mais pixels em uma menor área física. Exemplo: um dispositivo que tem uma tela de 4,7″ e uma resolução de 480×800 pixels tem uma densidade mais baixa (hdpi) do que um device que tem a mesma tela de 4,7″, porém uma resolução de 1080×1920 (xxhdpi). Exatamente por suportar uma diversidade grande de tamanhos de tela, é que no Android não trabalhamos com pixels, e sim com density pixels. Assim, um botão tende a ter sempre “mais ou menos o mesmo tamanho”, independente da resolução da tela. Para aprender mais sobre isso, dê uma olhada na documentação do Android (que agora também possui versão em português!).

E como essa mágica é feita? Não é mágica. Ao colocar um qualificador na pasta, o Android, na hora que estiver executando, conhece a configuração do dispositivo e usa o recurso específico para a sua configuração. Assim, não precisamos fazer várias condições em nosso código. É importante lembrar que podemos ter vários tipos de qualificadores ou mesmo adicionar pastas com mais de um qualificador. Veremos alguns casos em outros posts ?

Assim, resumindo, Supondo que estou em dispositivo xhdpi e preciso do resource mipmap/ic_launcher, o Android automaticamente buscará em mipmap-xhdpi/ic_launcher. Caso não exista uma versão específica para a configuração, ele busca a opção mais genérica, sem nenhum sufixo.

Bom, agora que já estamos mais familiarizados com o mecanismo de resources e pastas no Android, vamos criar uma nova pasta dentro de res, chamada layout. Essa pasta, como o próprio nome sugere, guardará arquivos que representam os layouts das nossas telas. Vamos criar um arquivo chamado activity_main.xml. A grande maioria dos resources no Android utilizam a notação de XML.

Neste arquivo, primeiramente colocaremos um container, o LinearLayout. Os containers servem para organizar os elementos na tela. No caso do LinearLayout, ele organiza os elementos alinhados, um após o outro, utilizando a orientação vertical ou horizontal. Adicionando o LinearLayout, o arquivo activity_main.xml fica assim:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

Você pode perceber que, além da orientação, também configuramos a largura (width) e a altura (height) do elemento. Para essas dimensões, podemos utilizar um valor ou as constantes match_parentwrap_contentmatch_parent indica que o elemento adotará para essa dimensão, a mesma dimensão que seu elemento pai. No caso, como o LinearLayout é o primeiro elemento da nossa hierarquia de views, ele adotará o tamanho de seu pai (algum container do Android que representa a tela). Assim, temos um LinearLayout do tamanho da tela ?

Vamos agora adicionar dois campos de texto e um botão ao nosso layout.

Os campos de entrada de texto no Android são conhecidos como EditText. Além da altura e da largura, também especificamos o id e a propriedade hint. Ids no Android são identificadores, o que nos permitirá referenciar este elemento em outros lugares (seja no XML, seja nas nossas classes Java). A notação @+id indica que estamos criando este recurso caso ele não exista. Sempre que você ver a notação @ em um XML, significa que estamos referenciando um recurso. O + indica a criação e id indica que é um resource do tipo id. Já o hint indica um texto de sugestão que será exibido quando o campo estiver vazio, indicando ao usuário o que deve ser inserido. Também estamos utilizando a notação de recurso, dessa vez referenciando um recurso do tipo string (que terá um valor correspondente dentro de res/values/strings.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/name" />

    <EditText
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/address" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/save" />

</LinearLayout>
<!-- Arquivo strings.xml -->
<resources>
    <string name="app_name">Tutorial</string>
    <string name="save">Save</string>
    <string name="address">Address</string>
    <string name="name">Name</string>
</resources>

No caso do Button, temos atributos semelhantes, com a diferença de que utilizamos a propriedade text em vez de hint. Com a propriedade text, teremos um texto no botão.

Com o arquivo de layout criado, vamos voltar a nossa classe MainActivity e definir este arquivo de layout, vinculando-o a Activity. Para isso, vamos utilizar ainda o método onCreate() que é chamado sempre que a Activity é criada. Nele, vincularemos o nosso layout XML a Activity. Fazemos isso chamando o método setContentView(). Este método pode receber um objeto do tipo View ou uma referência a um resource de layout. Note o uso da classe R. Esta classe é gerada automaticamente a cada build do projeto, e é uma referência a todos os resources que temos no projeto, para uso no nosso código Java. Vamos também, remover os Logs que adicionamos anteriormente. O método fica assim:

public class MainActivity extends AppCompatActivity {

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

Com esse XML, caso execute o aplicativo, nossa tela fica com o seguinte layout:

Para uma experiência mais agradável, poderíamos centralizar os elementos verticalmente. Para isso, podemos adicionar a propriedade gravity com o valor center_vertical. Essa propriedade indica como os filhos do container vão se comportar, conforme forem adicionados. Assim, temos a tela final:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/name" />

    <EditText
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/address" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/save" />

</LinearLayout>

E é isso pessoal! No próximo post veremos o mecanismo de binding de views e callbacks no Android! Caso deseje baixar o projeto que desenvolvemos até agora, você pode encontrar no Github.

Até o próximo post!

Entendendo a Activities e Intents – Tutorial Android 3

Olá pessoal! Dando prosseguimento a nossa série de tutoriais, hoje vamos criar a nossa primeira Activity, entender o que ela é e como funciona, além de uma noção geral sobre Intents! Vamos lá?

No último post criamos um projeto no famoso estilo Hello World. Para este, vamos criar um projeto que será evoluído ao longo de toda a série. Para isso, crie um novo projeto seguindo o tutorial anterior, com a diferença de que, em vez de selecionar Empty Activity, selecione a opção Add No Activity. Assim, podemos controlar melhor o que é criado e entender o que está acontecendo.

Algumas configurações iniciais serão realizadas e um modelo básico de projeto será criado. A princípio não se preocupe, vamos focar nos pontos um a um ao longo desta série. Assim que o projeto é criado, você verá à esquerda da IDE, na árvore do projeto, as opções appGradle Scripts. Isso ocorre porque estamos utilizando a visão Android, que agrupa muitas coisas e mostra os arquivos de uma forma mais significativa para Android. Ok, um pouco de mágica e esses “açúcares” são bons, mas no início, quando estamos entendendo o que de fato está acontecendo, é melhor ver as coisas na sua forma mais crua, para que a mágica deixe de ser mágica. Sempre que tiver algo “mágico”, vamos trabalhar pra entender como a mágica é feita. Quebra um pouco o encanto, mas te possibilita de fazer suas próprias mágicas depois ?

Vamos então mudar a visão de Android para Project, que exibirá a estrutura real de pastas do projeto.

Vamos navegar então até a pasta java, no caminho app/src/main/java. O caminho para a pasta tem este formato devido a estrutura de um projeto Gradle. O Gradle uma ferramenta bastante popular que nos ajuda a gerenciar todo o ciclo de build do projeto, incluindo suas dependências e a orquestração das ferramentas de compilação e tooling em geral. Em um momento oportuno analisaremos o Gradle mais a fundo. Nesta pasta você pode ver, por enquanto, apenas a declaração do pacote do aplicativo. Vamos criar então, a nossa primeira Activity. Clique com o botão direito sobre o pacote (no meu caso, net.rafaeltoledo.tutorial) e selecionar a opção New -> Java Class e nomear a classe a ser criada de MainActivity.

Activities no Android são a representação de uma tela. É através dela que conteúdo das telas é exibido e as interações de interface são executadas. Dentre os principais componentes do Android, é o que corresponde a UI dos aplicativos.

Para que esta classe se torne uma Activity, ela deve estender da classe Activity do Android. Porém, para o nosso caso, vamos fazer a nossa classe estender AppCompatActivity. Por quê? A AppCompatActivity é uma versão da Activity que garante um comportamento uniforme a partir da versão 7 (2.1) do Android. Essa classe faz parte das bibliotecas de suporte, que conheceremos e entenderemos mais a fundo em um post futuro.

Para que a nossa Activity esteja disponível no launcher do dispositivo, na forma de um ícone na lista de aplicativos, precisamos configurar o nosso Android Manifest. O Android Manifest é um arquivo que corresponde a uma espécie de índice do nosso aplicativo. Nele é que serão descritas, dentre outras informações, quais Activities nosso aplicativo possui e quais permissões ele necessita para ser executado.

No caso, como não selecionamos a criação automática da Activity quando criamos o projeto, temos apenas a descrição básica do nosso aplicativo no arquivo AndroidManifest.xml, localizado na pasta app/src/main (podem haver pequenas diferenças, dependendo da versão do Android Studio que você estiver usando):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.rafaeltoledo.tutorial">

    <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">

    </application>

</manifest>

Basicamente, temos aqui duas informações importantes:

  • A declaração do nó principal manifest com o atributo package. Esse atributo é importante, pois ele indica em qual pacote Java está a classe R. Esta classe, gerada automaticamente a cada compilação, é um índice com todos os recursos do nosso aplicativo que não são código Java. Veremos recursos muito em breve.
  • A declaração do nó application, que configura e descreve o nosso aplicativo. Dentre as informações principais, temos aqui o ícone, o label (que será o nome do aplicativo no launcher do sistema) e o tema.

Para adicionarmos a nossa Activity, vamos adicionar um novo nó dentro do nó application, dessa forma:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.rafaeltoledo.tutorial">

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

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

    </application>

</manifest>

Neste nó, estamos adicionando uma Activity com o nome .MainActivity. Este caminho pode ser absoluto (net.rafaeltoledo.tutorial.MainActivity) ou, como é o caso, relativo ao package que temos na raiz do manifesto. Perceba que temos também uma configuração dentro do nó activity, chamada de intent-filter.

O Android possui um mecanismo de troca de mensagens conhecido como Intents. Uma intent indica a intenção de se fazer alguma coisa, seja enviar uma mensagem para um componente, seja iniciar um componente. Essa intent pode conter diversas informações, como o destinatário, uma ação (action), uma categoria (category) e outras várias informações extras.

Lembra que quase todo aplicação que a gente escreve em qualquer linguagem sempre começa com um método main()? A ideia é parecida aqui: quando o ícone do aplicativo no launcher é tocado, é disparada uma Intent para a Activity configurada, com a categoria android.intent.category.LAUNCHER e a ação android.intent.action.MAIN, indicando que o aplicativo deve foi iniciado a partir do launcher do sistema. Existem, inclusive, aplicativos que possuem mais de um ícone na lista de apps do launcher.

Para vermos isso acontecendo na prática, vamos ver esse Intent chegando para a nossa Activity. Para isso, vamos sobrescrever o método onCreate() da nossa Activity MainActivity. Como estendemos de AppCompatActivity, herdamos vários métodos que nos permitem fazer diversas coisas no Android. O método onCreate() é um dos callbacks do ciclo de vida de uma Activity. Como você pode perceber, não chamamos o construtor da Activity diretamente, quem faz esse gerenciamento é o próprio Android. Então, somos apenas notificados quando cada coisa acontece. Você pode ver o ciclo de vida da Activity completo aqui, mas não se preocupe muito com isso por enquanto. Cada um desses estados vai fazer sentido mais adiante 🙂

No método onCreate(), vamos então pegar as informações que vieram da Intent que dispararam a nossa Activity. Para isso, vamos escrever no log do Android (conhecido como LogCat) algumas informações da Intent, como a ação, a categoria e o componente que foi lançado. O código fica assim:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        Log.d("MA::Action", intent.getAction());
        for (String category : intent.getCategories()) {
            Log.d("MA::Category", category);
        }
        Log.d("MA::Component", intent.getComponent().getClassName());
    }
}

Repare que, para exibir informação no Logcat, em vez do padrão System.out do Java, utilizamos a classe Log do Android. Com essa classe, temos melhores mecanismos para controlar o nível do log (error, warning, debug, etc) e podemos adicionar uma tag (primeiro parâmetro) para que fique mais fácil de encontrarmos o que queremos 🙂

Agora, se você executar o aplicativo e clicar na aba Android Monitor (na parte inferior do Android Studio), poderá conferir o log do seu aplicativo, exibindo as informações da Intent:

Bom, por hoje é só isso pessoal! No próximo post vamos entender melhor os resources do Android e criar nosso primeiro layout para a MainActivity!

Ah, sim, o projeto completo desenvolvido até aqui pode ser encontrado no Github! 🙂

Criando um Novo Projeto e Criando um Emulador no Android Studio – Tutorial Android 2

Olá pessoal! No último post sobre Android, vimos como configurar o ambiente para programarmos, utilizando o Android Studio. Neste post, vamos ver como criar um projeto no famoso estilo Hello World, e um dispositivo virtual para a executarmos o aplicativo. Assim, você não precisa necessariamente de um celular com Android para começar a desenvolver para a plataforma, apesar de ser bastante recomendado 🙂

Ao abrir o Android Studio, vamos selecionar a opção Start a new Android Studio project. O guia que será aberto em seguida, nos auxiliará a criar um projeto sem muito esforço. Logo na primeira tela, vamos informar alguns dados básicos, como o nome do aplicativo (que utilizaremos Hello World) e o Company Domain, que nada mais é que um domínio que você tenha. Isso ajudará a criar um identificador único para seu aplicativo. No meu caso, vou colocar rafaeltoledo.net, mas você pode, por exemplo, utilizar seu nome no Github e criar o seu próprio domínio (por exemplo, rafaeltoledo.github.io). Para este projeto, não selecionaremos o suporte a C++. Vamos guardar isso para um tutorial mais avançado 🙂

Na tela seguinte, vamos configurar os módulos que nosso projeto terá, além das versões suportadas do Android. No momento que escrevo este tutorial, a minha recomendação é utilizar a API 16 como Minimum SDK. Assim, nosso aplicativo executará em mais de 97% dos dispositivos Android atualmente ativos. Você pode acompanhar a evolução das versões neste site, atualizado mensalmente.

Neste primeiro momento, vamos selecionar apenas a opção Phone and Tablet. Também vamos deixar os outros módulos, como Wear, TV e Auto para outras oportunidades e tutoriais futuros.

Na tela seguinte, vamos selecionar a opção Basic Activity. Você perceberá que muita coisa será gerada e criada, mas não se preocupe, vamos entender o que é cada coisa ao seu tempo. Avance utilizando a opção Next.

Por fim, vamos manter o nome da Activity como MainActivity, deixando as opções padrão como estão. Clique em Finish para finalizarmos o wizard.

Aguarde alguns momentos até que as dependências do projeto sejam iniciadas e a primeira compilação seja realizada.

Para executarmos este projeto, vamos criar um emulador, conhecido como AVD (Android Virtual Device). Para isso, clique no botão AVD Manager na barra de ferramentas do Android Studio.

Na janela que aparecer, selecione a opção Create Virtual Device…, que iniciará o wizard para a criação de um novo emulador.

Na tela seguinte, será exibida uma série de modelos de celular e tablet, que servirá como base para o nosso emulador. Escolha qualquer um que desejar, como por exemplo o Nexus 5. Clique em Next para avançar.

Na tela seguinte, escolheremos a imagem que utilizaremos em nosso dispositivo. A imagem contém o sistema operacional em si. A minha recomendação é escolher uma versão mais recente, e, caso seu processador e sistema operacional seja 64 bits (provavelmente é), escolha a opção x86_64 e com as APIs do Google (with Google APIs). Para este caso, estou selecionando a versão 7.1.1. Caso a imagem ainda não tenha sido baixada, clique na opção Download.

Na última tela, temos algumas opções do dispositivo, como câmera, memória e armazenamento. Por enquanto, mantenha as opções padrão e finalize.

Feche a janela do AVD Manager e clique para executar o projeto, no botão com o formato de um play na barra de ferramentas do Android Studio.

Feito isso, aguarde alguns instantes até que o emulador seja carregado e o aplicativo compilado, e então você verá seu primeiro aplicativo Android sendo executado! 🙂

E é isso pessoal! No próximo post vamos entender melhor a estrutura de um projeto Android e começar, de fato, a colocar a mão na massa! Até lá!

 

Configurando o Ambiente de Desenvolvimento – Tutorial Android 1

Desde o último post que explicava a configuração do ambiente de desenvolvimento para Android, tivemos pelo menos uma mudança bem drástica. Se antes utilizávamos o Eclipse, hoje utilizamos o Android Studio, uma versão da poderosa IDE IntelliJ IDEA, desenvolvida pela JetBrains.

Configuração no Windows

O primeiro passo para a instalação do ambiente no Windows, é o download da JDK (Java Development Kit). Ela nos fornecerá a base de ferramentas para o desenvolvimento Java, que será utilizada também para o desenvolvimento dos apps Android também. Você pode baixar a JDK no site da Oracle. A instalação deve ocorrer sem problemas.

O próximo passo consiste em baixarmos o Android Studio. Após baixar o .exe bundle (que já contém os itens básicos de SDK e emulador), basta seguir o procedimento de instalação normalmente como qualquer outro aplicativo.

O último passo é exportarmos as variáveis de ambiente JAVA_HOME ANDROID_HOME, para que possamos, quando necessário, também executar builds e comandos a partir do console. Primeiramente vamos exportar a ANDROID_HOME. Caso você não tenha alterado o local durante a instalação do Android Studio, o valor deve ser:

%LOCALAPPDATA%\Android\sdk
view raw snippet01 hosted with ❤ by GitHub

Em seguida, vamos exportar a JAVA_HOME. Novamente, se você não alterou o local da instalação do Java, o valor deve ser:

%ProgramFiles%\Java\jdk1.8.0_111
view raw snippet02 hosted with ❤ by GitHub

No momento que escrevo este post, a última versão do Java é a 8, revisão 111. Caso a versão que você instale seja outra, ajuste o caminho de acordo.

Por fim, vamos adicionar os binários ao Path do sistema, para que sejam acessíveis através do console. Adicione ao Path as entradas %ANDROID_HOME%\tools%ANDROID_HOME\%platform-tools%JAVA_HOME\%bin.

Para testar, valide que os comandos adbjavac são acessíveis no console.

Durante a instalação do Android Studio, também deve ter sido instalado o HAXM (caso o seu processador seja compatível). O HAXM permitirá que você execute o emulador do Android com excelente desempenho. Outra boa opção de emulador é o Genymotion, porém para usos gerais, o emulador que acompanha a SDK do Android é suficiente.

Por fim, opcional mas recomendado, instale o Git, que é hoje a ferramenta de versão mais utilizada.

Configuração no Linux

No Linux, por possuirmos um gerenciador de pacotes, a coisa fica um pouco mais fácil. Caso você esteja utilizando o Ubuntu / Linux Mint, para preparar nosso ambiente, basta instalar os seguintes pacotes:

sudo apt-get install lib32z1 lib32ncurses5 lib32stdc++6 openjdk-8-jdk qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
view raw snippet03 hosted with ❤ by GitHub

Caso esteja utilizando o Fedora, os pacotes são:

sudo dnf install glibc.i686 glibc-devel.i686 libstdc++.i686 zlib-devel.i686 ncurses-devel.i686 libX11-devel.i686 libXrender.i686 libXrandr.i686 java-1.8.0-openjdk-devel qemu-kvm
view raw snippet04 hosted with ❤ by GitHub

Outras distribuições podem ter estes pacotes com outros nomes, mas uma rápida busca pode trazer os correspondentes. O Linux não utiliza o HAXM para aceleração dos emuladores x86 – para isso, temos o KVM, um mecanismo de virtualização ligado ao kernel (caso o seu processador suporte).

Em seguida, vamos baixar o Android Studio. Para Linux, o arquivo tem extensão .tar.gz e deve ser extraído para alguma pasta (como por exemplo, ~/Android). Feito isso, basta executar o arquivo studio.sh que encontra-se dentro da pasta bin. Na primeira execução, serão baixados os pacotes necessários para a SDK. Para encerrar, selecione no menu Configure a opção Create Desktop Entry para facilitar a execução do Android Studio a partir do seu menu de aplicativos (seja UnityGnomeKDE, etc.)

O último passo é exportarmos a variável ANDROID_HOME e colocar os binários no Path. Para isso abra o arquivo ~/.bashrc ou, caso esteja utilizando o zshell, ~/.zshrc, e adicione as seguintes linhas. Caso tenha modificado o local da instalação da SDK do Android, ajuste o caminho:

export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools"
view raw snippet05 hosted with ❤ by GitHub

Por fim, é opcional porém recomendada, a instalação do Git para controle de versão dos nossos projetos.

Configuração no OSX

No OSX (Mac), a instalação é semelhante a que fizemos para o Windows. Primeiramente, baixe a JDK do site da Oracle. A instalação deve ocorrer sem maiores problemas, bastando seguir o wizard com o passo a passo da instalação.

Em seguida, baixe o Android Studio. A partir do arquivo .dmg, a instalação é tranquila, seguindo o padrão para outros aplicativos do Mac. Ao executar pela primeira vez, as ferramentas serão baixadas e o HAXM será instalado.

O último passo é a configuração das variáveis de ambiente. Contando que os caminhos padrão tanto do Java quanto da SDK do Android não foram alterados durante a instalação, adicione o seguinte conteúdo ao arquivo ~/.bash_profile (caso o arquivo não exista, crie-o).

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
view raw snippet06 hosted with ❤ by GitHub

Por último, opcionalmente mas fortemente recomendado, instale o Git (opte por utilizar o Homebrew, para instalar a versão mais recente).


E é isso! Aguardem os próximos posts, pois começaremos a colocar a mão na massa 🙂