Home Anotações sobre Kotlin
Post
Cancelar

Anotações sobre Kotlin

Introdução

  • Open-source software (OSS)
  • Orientada a objetos e funcional
  • 100% compatível com Java
  • Statically Typed
  • Concise
  • Safe
  • Interoperable (pode trabalhar em conjunto com código Java)
  • Kotlin roda na JVM: Kotlin code (.kt) -> Kotlinc (compilador) -> Byte code (.class) -> JVM
  • Não utiliza ponto e vírgula
  • Não possui operador ternário tradicional
  • Type safe e null safe
  • Inferência de tipo
  • Smart cast

Configuração do Android Studio

#Kotlin

  • Em Android SDK, além de baixar as versões que você quer utilizar, marque “show package details” e baixe as “Google APIs Intel x86 Atom System Image” e “Intel x86 Atom System Image” respectivas de cada versão do Android, para um emulador mais eficiente.
  • Depois, na aba SDK Tools, marque “show package details” e selecione:
    • a versão mais recente do “Android SDK Build-Tools”,
    • “Android Emulator”,
    • “Android SDK Platform-Tools”,
    • “Android SDK Tools”
    • “Google Play Services”,
    • “Google USB Driver”,
    • “Intel x86 Emulator Accelerator (HAXM installer)”.
  • Em “Support Repository”, selecione a versão mais recente do “ConstraintLayout for Android” e do “Solver for Constraint Layout”, e marque também “Android Support Repository” e “Google Repository”. (não disponível em versão mais recente do Android Studio)
  • Ativar a virtualização: Nos processadores AMD é chamado AMD-V (sempre habilitado). Nos processadores Intel é chamado VT-X (pode estar desabilitado). Nos Recursos do Windows, ativar Hyper-V ou Plataforma do Hipervisor do Windows.
  • Na pasta do projeto “Gradle Scripts”, no arquivo Build.gradle (Module: …), adicionar a plugins:
    • id ‘kotlin-android-extensions’, para recursos estendidos do Kotlin

Padrões de projeto e de código

#Kotlin

Motivação

Convenções de código são um conjunto de recomendações para definir o estilo de codificação usado em métodos, variáveis, nome de classes, organização e nomenclatura de arquivos, comentários e outros recursos da linguagem. Convenciona-se para que a leitura seja mais fácil ao se deparar com um código desconhecido e também para que não haja diferentes estilos de programação dentro de um projeto, uma vez que frequentemente projetos são executados por diversas pessoas. As convenções descritas aqui estão no site oficial da linguagem Kotlin.

Ponto e vírgula

A linguagem deixa como opcional o uso do ponto e vírgula e dessa maneira, como Kotlin preza pela limpeza do código, o uso do ponto e vírgula é desencorajado. Caso seja declarado na IDE, não haverá problema de compilação, mas haverá o aviso constante de que não é necessário.

Nomenclaturas de código

● Uso de camelCase começando com letra minúscula e evitando underline para: ○ variáveis ○ métodos ○ atributos de classe ● Identação com espaços com o auxílio da própria IntelliJ IDEA ou Android Studio. ● Variáveis privadas da classe se iniciam com m minúsculo seguido do nome da variável usando camelCase. Para constantes, marcados com a palavra const ou declarações val fora de classes e funções, devem usar todas as letras maiúsculas separados com underline. Métodos que façam sobrecarga são próximos uns dos outros.

Classes

Se um arquivo Kotlin possuir uma classe somente, o nome do arquivo deve ser o mesmo nome da classe com a extensão .kt. Se um arquivo contiver múltiplas classes, o ideal é que o nome do arquivo seja algo que descreva a responsabilidade da classe da melhor maneira e mais sucinta possível. Para a nomenclatura, usar o padrão CammelCase com a primeira letra sempre maiúscula. Já para os parâmetros definidos dentro das classes, caso sejam poucos, podem ser escritas em uma única linha. Classes com construtores mais longos devem ser formatadas para que cada propriedade fique em uma linha. Além disso, o parênteses para fechar o construtor deve ficar em uma nova linha. Objects, companion objects, métodos que façam a sobrescrita da classe pai e construtores secundários ficam no início da classe.

Chaves - {}

Em if/else, while, for, do..while, try..catch, funções e classes sempre utilizar as chaves para abertura e fechamento do corpo das instruções.

Unit (Void)

O tipo Unit corresponde a nulo. Em funções que não há retorno, não há necessidade de declarar o retorno como Unit, basta deixar em branco. Caso a função possua retorno, então é necessário declarar o tipo.

Activities e Layout

Nomes de arquivos de layout devem ser prefixados com os elementos que representam:

ComponenteClasseNome do Layout
ActivityUserProfileActivityactivity_user_profile.xml
FragmentSignUpFragmentfragment_sign_up.xml
DialogChagePasswordDialogdialog_change_password.xml

Arquivos XML

Quando um elemento não tiver nenhum conteúdo dentro das tags, deve-se usar “self closing” tags.

Identificadores de elementos

Identificadores de elementos são escritos em minuscula_com_underline.

ElementoPrefixo
TextViewtext_
EditViewedit_
Buttonbutton_
ImageViewimage_
Menumenu_

Estrutura principal

1
2
3
fun main() {
  
}

Variáveis

1
2
3
4
5
6
7
8
9
10
11
var nome //variável mutável
lateinit var nome //lateinit indica que a variável será inicializada tardiamente
val pi = 3.14 //variável imutável
const val PI = 3.14 //constante cujo valor é atribuído durante a compilação
var caractere: Char = 'a'
var nome: String = "João"
var idade: Int = 10
var preco: Double = 10.50 //padrão da linguagem para números. Para float ou long adicione letra 3.5f 600L
var booleano: Bool = true
var nomes = arrayOf("nome1", "nome2", "nome3", 520)
var numeros = intArrayOf(10, 20, 30)

Tipos

.MAX_VALUE

IntBoolean
LongChar
FloatByte
DoubleShort
ArrayNull

Atribuição

1
val MaxValue = if (a>b) a else if (a<b) b else b

Formatação de strings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
    val frase = "Kotlin é uma linguagem"
    val caracteristica = "show"
    println(frase+caracteristica)
    println("Kotlin é uma linguagem" + caracteristica)
    println("Kotlin é uma linguagem $caracteristica")
    println("Kotlin é uma linguagem ${caracteristica.length}")
 
    val stg = "lskadlsadfj" +
            "alskdfjiweasdf" +
            "kldfgjsldkfgjlk"
    
    val stg = """lskadlsadfj
            alskdfjiweasdf
            kldfgjsldkfgjlk"""
    
}
Capitalização.capitalize(), .toUpperCase(), .toLowerCase(), .decapitalize()
Remoção de espaços ou caracteres inadequados.trimEnd(), .trimStart(), .trim()
Substituição de caracteres.replace(x, y)
Formatação.format(valor)

Empty x Blank

  • Métodos de comparação;

  • .isEmpty(), .isBlank(), .isNullOrBlank(), .isNullOrEmpty

  • Se o tamanho da string for 0 está empty e blank

  • Se o tamanho for >0 mas todos os caracteres são espaços em branco, está blank mas não empty.

Funções

1
2
3
4
5
fun calculaBonus (a: Int, b: Int, c: String = valor default) : Int { 
//quando a função tem retorno, o tipo é indicado depois dos parâmetros. Quando há um valor default ele não precisa ser passado obrigatoriamente
    println("O bônus é: ${a+b}")
    return a+b
}

N parâmetros

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun media (vararg notas: Float) { 
//vararg permite receber um numero variável de parâmetros
    if (notas.isNotEmpty()){
        var soma = 0f
        for (nota in notas) {
            soma += nota
        }
        println("A média é ${soma/notas.size}")
    }
}
 
fun <T> diversos (vararg valores: T) { //define um tipo genérico que aceita parâmetros de tipos diversos
    
}

Funções de uma única linha

  • Infere o tipo de retorno
1
fun calculaBonus (a: Int, b: Int, c: Int) = println("O bônus é: ${a+b*c}")

Funções/extensões

  • Prefixo fun Tipo.nomeDaFunção()

  • Cria uma função que só pode ser chamada por um tipo específico, cujo valor pode ser referenciado dentro da função como this:

1
fun String.randomCapitalizedLetter() = this[(0..this.lenght-1).random()].toUpperCase()

Função local

Função interna a outra função, não acessivel diretamente, quase como se fosse private. Acessivel pela função pai, que pode ser chamada.

Funções infix

1
2
3
4
5
6
infix fun Int.isHalfOf (value: Int) = value / 2 == this

fun main (args: Array<Strings>) {
	10.isHalfOf(40)
	10 isHalfOf 30 //Infix só pode ser usada com 1 parâmetro
}

Higher Order Functions

1
2
3
4
5
6
7
8
9
10
11
12
fun operator (x: Int, y: Int, op: (Int, Int) -> Int): Int {
	val ret = op(x,y)
	return ret
}

fun sum (x: Int, y:Int) = x+y

fun main (args: Array<String>) {
	val list = listOf(1, 2, 3, 4)
	list.forEach( {println(it)} ) //funções que recebem outras funções como parâmetro
	operator(1, 2, ::sum) //:: para indicar a função pelo nome sem assinatura
}

Lambdas

1
2
3
4
5
6
7
8
9
10
11
12
13
fun operator (x: Int, y: Int, op: (Int, Int) -> Int): Int {
	val ret = op(x,y)
	return ret
}

fun main (args: Array<String>) {
	operator(10, 20, {a, b -> a+b})
	
	val l1 = {a: Int, b: Int -> a+b}//os lambdas conseguem executar apenas uma instrução
	val l2: (Int, Int) -> Int = {u, i -> u+i} //para executar mais de uma instrução é possível utilizar funções anonimas fun() {}
	operator(10, 20, l1)
	operator(10, 20, l2)
}

Operadores Aritméticos

unários

+=. ++, –, -=

a++ (postfix) / –a(prefix)

infix binários

Soma(+)a.plus(b)
Subtrair(-)a.minus(b)
Multiplicar(*)a.times(b)
dividir(/)a.div(b)
%(resto da divisão)a.mod(b)

Operadores relacionais

Binários/infix

Resultado sempre booleano

== (igual a)a.equals(b)
!= (diferente)!(a.equals(b))
> (Maior que)a.compareTo(b)>0
< (Menor que)a.compareTo(b)<0
>= (Maior ou igual)a.compareTo(b)>=0
<= (Menor ou igual)a.compareTo(b)<=0

Operadores lógicos

&& (e)andbinário/infix
|| (Ou)orbinário/infix
^ (ou exclusivo)xorbinário/infix
! (negação)notunário/prefix

Operadores in e range

in (contém) !in (não contém)Se o valor está presente em uma lista ou uma faixa de valores
range(int.int)Range cria um intervalo de valores

Operator Overloading

a + ba.plus(b)operator fun plus (b: TYPE): TYPE
a - ba. minus(b)operator fun minus (b: TYPE): TYPE
a * ba.times(b)operator fun times (b: TYPE): TYPE
a / ba.div(b)operator fun div (b: TYPE): TYPE
a..ba.rangeTo(b)operator fun rangeTo (b: TYPE): TYPE
a in bb.contains(a)operator fun contains (b: TYPE): Boolean
a += ba.plusAssign(b)operator fun plusAssign (b:TYPE): Unit
a - = ba.minusAssign(b)operator fun minusAssign (b:TYPE): Unit
a *= ba.timesAssign(b)operator fun timesAssign (b:TYPE): Unit
a/= ba.divAssign(b)operator fun divAssign (b:TYPE): Unit
a>ba.compareTo(b) >0operator fun compareTo (b:Type): Int
a<ba.compareTo(b)<0operator fun compareTo(b:TYPE): Int
a>=ba.compareTo(b) >=0operator fun compareTo (b: Type): Int
a<=ba.compareTo(b)<=0operator fun compareTo (b: Type): Int

Operador Elvis (?)

1
2
val test: Int? = null //permite atribulir valor null a variável
val num: Int = test ?: 100 //se test for null, atribua 100, para evitar erros

Controle de fluxo if else

1
2
3
4
5
6
7
8
9
10
  if( idade <= 9 ){
  }
  else if( idade >= 10 && idade <= 18 ){
  }
  else{
  }
fun calculaIdade (idade: Int) : Boolean { //depois dos : indica o tipo de retorno da função
    return idade >=18
}
val str = if(valor==10) "sim" else "não" //é possível utilizar if else diretamente na atribuição de uma variável

Controle de fluxo – When

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
when (c) {
  "soma" -> {
    return a+b
  }
  "subtração" -> {
    return a-b
  }
  else -> {
    println("Operação incorreta")
    return 0
  }
}
 
when (a) {
  in 0..100 -> { // o in e os dois pontos permitem criar um range, um intervalo de valores
    println("o número está entre 0 e 100")
  }
}

Loops for e while

  • Aceita os comandos in, range, until, downTo e step. forEach
1
2
3
4
5
6
7
8
9
10
11
12
13
  while( numero <= 5 ){
    
  }
  do{
    
  }while(var palavra in palavras);
  
for (i in 1..100 step 2) {
    print("$i ")
}
for (j in 100 downTo 0) {
    print("$j ")
}

Conversão de valores

  • A operação entre valores de tipo diferente gera um resultado do tipo de maior tamanho. A conversão de valores deve ser explicita.
Double64.toDouble()
Float32.toFloat()
Long64.toLong()
Int32.toInt()
Short16.toShort()
Byte8.toByte()
Char .toChar()

Exceções

  • Tratamento para evitar a quebra da execução
1
2
3
4
5
6
7
8
9
10
11
12
try {
    println("joão".toInt())
}
catch(e: NumberFormatException) { //exceção específica
    println("Erro na conversão de string para inteiro")
}
catch(e: Exception) { //coloque a exceção mais genérica por último, do contrátio o erro sempre cairá nela e não na específica
    println("Erro genérico")
}
finally { //sempre é executado, independentemente de erro
    //aqui poder incluir uma verificação, caso tenha havido erro, para encerrar o processo
}

Null Safe

  • O operador Elvis em Kotlin permite que variáveis assumam valor nulo, dando mais segurança ao evitar erros relacionados a null.
1
2
3
val str: String? = null
println(str?.length) //o operador elvis previne o erro
println(str!!.lenght) // o operador !! permite que o erro ocorra

Pacotes

  • Pacotes servem para separar códigos/usos, manter pacotes concisos e coesos
1
import basic.* //o asterisco indica que todos os recursos do pacote serão importados

Classes e Objetos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Casa { //padrão de nomenclatura, classes com primeira letra maiúscula
    var cor: String = ""
    fun abrirJanela(qtdJanelas: Int){ //padrão de nomenclatura, métodos e propriedades em camelCase
        println("Abrir janela total: $qtdJanelas ")
    }
    fun abrirPorta(){
        println("Abrir porta")
    }
    fun abrirCasa(){
        this.abrirJanela()
        this.abrirPorta()
    }
}
fun main(args: Array<String>) {
    val casa = Casa() //criação do objeto
    casa.cor = "Amarela"
    casa.abrirJanela( 10 )
    val casa2 = Casa()
    casa2.cor = "Vermelha"
    println(casa.cor)
    println(casa2.cor)
}

Construtores

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Casa( var cor: String) { //construtor primário, definido na declaração da classe
    var cor: String
    var vagasGaragem: Int
    constructor( vagasGaragem: Int  ){ //construtor secundário, evitar utilizar ambos pedindo o mesmo parâmetro. Pode ser usado para pedir parâmetros adicionais, diferentes do construtor primário
        this.cor = cor
        this.vagasGaragem = vagasGaragem
    }
    init{ //inicializa as propriedades passadas para o construtor primário
        this.cor = cor
        this.vagasGaragem = vagasGaragem
    }
    fun detalhesCasa(){
        println("A casa tem a cor: $cor, vagas: $vagasGaragem ")
    }
}
fun main(args: Array<String>) {
    val casa = Casa("Amarela", 2)
    casa.detalhesCasa()
}

Herança

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
open class Animal{ //no kotlin, por padrão, as classes não podem ser herdadas. Para fazer isso utilizar open
    open fun dormir(){ //o open aqui permite que o método seja sobrescrito pelas classes filhas
        println("Dormir")
    }
    fun correr(){
        println("Correr")
    }
}
class Cao : Animal() { //notação para indicar herança
    override fun dormir(){ //sobrescrita de método
        super.dormir() //chama o método original
        println("como um cao") //acrescenta a alteração sobrescrita
    }
    fun latir(){
        println("Latir")
    }
    fun latir(i: String){ //sobrecarga de método (overload). Método com mesmo nome, mas com parâmetros e implementação diferentes
        println("Latir como um $i")
    }
 
}
class Passaro: Animal() {}
fun main(args: Array<String>) {
    val cao = Cao()
    cao.dormir()
    cao.correr()
    cao.latir()
    val passaro = Passaro()
}

Modificadores Static e Final

  • Ao tornar o atributo estático, ele fica acessível sem a necessidade de instanciar a classe em um objeto.

  • O objeto marcado como final não pode ser alterado/sobrescrito.

Constantes e Variáveis estáticas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Constantes {
    companion object NOME { //companion object armazena constantes, funções e variáveis estáticas. O nome é opcional
        val TABLE = "mesa" //notação de constantes em maiúsculas
        fun teste() [
            println("teste")
        ]
    }
    object VENDAS { //nome obrigatório. É possível criar objects dentro de objectes, criando hierarquias
        var OBJETO = "cadeira"
    }
}
fun main() {
    Constantes.TABLE
    Constantes.NOME.TABLE
    Constantes.VENDAS.OBJETO
}

Getters e Setters

  • O Kotlin cria getters e setters automaticamente, sem a necessidade de criação explicita (exceto quando você quer criar validações próprias, aí utilizar Field).

Field

1
2
3
4
5
6
7
8
9
10
11
class Maquina (var marca: String) {
    var nucleos: Int =0
        get() {
            println("get for chamado")
            return field //a palavra field é utilizada para getters e setters para evitar chamadas recursivas
        }
        set(value) {
            println("set foi chamado")
            field = value
    	}
}

Classes Enum

A clase enum pode ser utilizada como uma representação dos próprios objetos, sem instâncias.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum class Prioridade1 { //As classes enum servem para listar possibilidades 
    BAIXA, MEDIA, ALTA
}
enum class Prioridade2 (val id: Int) { //classes enum também podem ser utilizadas como parâmetros de outras classes
    BAIXA(id:1) {
        override fun toString(): String{
            return "string de prioridade baixa"
        }
    }, MEDIA(id:5), ALTA(id:10)
    //é possível fazer override dos atributos de um enum para, por exemplo, convertê-los em tipos primitivos para permitir comparações e verificações.
}
fun main(args: Array<String>) {
    println(Prioridade1.BAIXA)
    println(Prioridade2.BAIXA.id)
    for (p in Prioridade2.values()) {
        println("$p - ${p.id} - ${p.ordinal}") //o atributo ordinal indica a posição do atributo no enum
    }        
}

Data Class

Armazenar um estado imutavel em um objeto.

1
2
3
4
5
data class Pessoa (var nome: String, var telefone: Int) { //data class é utilizada para agregar dados/atributos, que podem ser passados para outras funções e classes com mais facilidade
}
fun cadastro (var pessoa: Pessoa) {
}
//ao comparar 2 objetos em uma expressão lógica, são comparadas as posições de memória, mas quando utilizamos data class, são utilizados os atributos para comparação. Permite verificar a função .hashcode() para comparar e também copiar todos os valores para uma nova instância (.copy()).

Classes Abstratas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Presidenciavel { //interface marca uma classe como uma interface
//interface não pode ter valores, estados, variáveis
  abstract fun participarEleicao() //metodo abstrato sem corpo sem as chaves. As classes filhas são obrigadas a sobrescrever o método
}
abstract class Cidadao { //classe abstrata não pode ser instanciada, usada apenas para herança
  init{ //a funçõa init é sempre executada, independente de quantos objetos da classe são instanciados
    var cpf: String = 000.000.000-00
  }
  fun direitosDeveres(){ 
    println("Todo cidadão tem direitos e deveres");
  }
}
class Obama : Cidadao(), Presidenciavel() { //classe concreta instanciável
  @override
  fun participarEleicao(){
    println("Participar eleicão Estados Unidos");
  }
}
//a herança em kotlin é sempre simples. Ou seja, uma classe só pode herdar de outra, mas pode implementar diversas interfaces

Interface

  • Pode-se dizer, grosso modo, que uma interface é um contrato que quando assumido por uma classe deve ser implementado. Interface é utilizada pois podemos ter muitos objetos (classes) que podem possuir a mesma ação (métodos), porém, podem executá-las de maneiras diferentes.

  • A interface também é utilizada pois o Kotlin não permite herança múltipla (extends).

  • Veja no exemplo de código anterior.

With

1
2
3
4
5
6
7
8
9
//Para chamar muitas funções de uma mesma classe ao mesmo tempo, pode-se usar with
fun main () {
    var m = Maquina(marca: "spto")
    with(m) {
        ligar()
        processar()
        desligar()
    }
}

Nested Class

Classe interna

1
2
3
4
5
6
7
class Computer (val processador: String) {
	inner class Memory () { //classe interna
	}
}
fun main (args: Array<String>) {
	val m1: Computer.Memory = Computer().Memory()
}

Sealed Class

Sealed Class limita o acesso da classe para herança ao mesmo aquivo onde ela está.

1
2
3
sealed class Computer (val processador: String) {
	
	}

Modificadores de Acesso

  • private -> Visível apenas dentro da classe
  • protected -> Funciona como o “private”, mas é visível em subclasses
  • open -> Visível em todo lugar, caso não seja definido é o padrão.
  • internal -> Lembra o Public, mas sua visibilidade é restringida ao módulo, sendo então visível dentro de um mesmo módulo.

#Kotlin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class Animal {
    internal var nome = "marley"
    fun dormir(){
        println("Dormir")
    }
}
class Cao : Animal() {
    fun exibeNome(){
        println( "Método exibe nome: $nome" )
    }
}
fun main(args: Array<String>) {
    val cao = Cao()
    cao.exibeNome()
    println( animal.nome )
}

Coleções

O Kotlin não possui coleções próprias, ele utiliza as mesmas do Java.

1
2
3
4
5
6
7
8
9
10
11
12
package collections
fun main (args: Array<Strings>){
	//List, array, set, hashmap
	val l1 = listOf("Madrid", "São Paulo", "Berlin") //imutável
	val l2 = mutableListOf("Madrid", "São Paulo", "Berlin") //mutável
	val array = arrayListOf("Madrid", "São Paulo", "Berlin") //utiliza os métodos do Java
	val s1 = setOf("Madrid", "São Paulo", "Berlin") //não aceita elementos repetidos
	val s2 = mutableSetOf("Madrid", "São Paulo", "Berlin") //não aceita elementos repetidos
	val h1 = hashMapOf<String, String>(Pair("key", "value"), Pair("França", "Paris")) //hashmap
	val h2 = mapOf<String, String>(Pair("key", "value"), Pair("França", "Paris")) //igual hashmap
	val h3 = mutableMapOf<String, String>(Pair("key", "value"), Pair("França", "Paris")) //igual hashmap
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package XIIIColecoes

data class Receita(val nome: String, val calorias: Int, val ingredientes: List<Ingredientes> = listOf())
data class Ingredientes(val nome: String, val quantidade: Int)

fun main() {

    // Cria lista de dados
    val data = listOf(
        Receita(
            "Lasanha", 1200,
            listOf(
                Ingredientes("Farinha", 1),
                Ingredientes("Presunto", 5),
                Ingredientes("Queijo", 10),
                Ingredientes("Molho de tomate", 2),
                Ingredientes("Manjerição", 3)
            )
        ),
        Receita("Panqueca", 500),
        Receita("Omelete", 200),
        Receita("Parmegiana", 700),
        Receita("Sopa de feijão", 300),
        Receita(
            "Hamburguer", 2000,
            listOf(
                Ingredientes("Pão", 1),
                Ingredientes("Hamburguer", 3),
                Ingredientes("Queijo", 1),
                Ingredientes("Catupiry", 1),
                Ingredientes("Bacon", 3),
                Ingredientes("Alface", 1),
                Ingredientes("Tomate", 1)
            )
        )
    )

    // Tenho receitas na lista?
    println("Tenho receitas? ${if (data.any()) "sim" else "não"}.")

    // Quantas receitas tenho na coleção?
    println("Tenho ${data.count()} receitas.")

    // Tenho alguma receita de Lasanha?
    println("Tenho receitas de Lasanha? ${if (data.any { it.nome.contains("Lasanha") }) "sim" else "não"}.")

    // Quantas receitas de Lasanha?
    println("Tenho ${data.count { it.nome.contains("Lasanha") }} receitas de Lasanha.")

    // Qual a primeira e última receita?
    println("A primeira receita é: ${data.first().nome}.")
    println("A última receita é: ${data.last().nome}.")

    // Qual a soma de calorias?
    val sumCalories = data.sumBy { it.calorias }
    println("A soma de calorias é: $sumCalories")

    // Me dê as duas primeiras receitas
    val firstTwo = data.take(2)
    for (x in firstTwo.withIndex()) {
        println("${x.index + 1} - ${x.value.nome}")
    }

    // Sei como fazer panqueca? E sushi?
    val knowPanqueke = data.filter { it.nome == "Panqueca" }.any()
    println("Sei fazer panqueca? ${if (knowPanqueke) "sim" else "não"}")

    val knowSushi = data.filter { it.nome == "Sushi" }.any()
    println("Sei fazer sushi? ${if (knowSushi) "sim" else "não"}")

    // Quais são as comidas com mais de 500 calorias?
    data.filter { it.calorias > 500 }.forEach { println(it.nome) }

    // Qual a receita mais calórica? E a menos calórica?
    val maisCal = data.maxBy { it.calorias }
    println("Mais calórica: ${maisCal?.nome}")

    val menosCal = data.minBy { it.calorias }
    println("Menos calórica: ${menosCal?.nome}")

    // Faça uma lista com o nome dos pratos
    data.map { it.nome }

    // Qual a média de calor de todas as receitas?
    val media = data.map { it.calorias }.average()
    println("A média de calorias é: $media.")

    // Lista de dados simples
    val listaInteiros = listOf(1, 2, 6, 67, 7, 3, 34, 56, 3, 3, 2, 5, 34, 2)
    println("Lista distinta: ${listaInteiros.distinct()}.")
    println("Máximo: ${listaInteiros.max()}.")
    println("Mínimo: ${listaInteiros.min()}.")

    // Eliminar receitas com mesmo nome
    println(data.distinctBy { it.nome })

    // Ordenar uma lista
    listaInteiros.sorted()
    listaInteiros.sortedDescending()

    // Inverter uma lista
    listaInteiros.reversed()

}

Imagens

  • Adicionar arquivos de imagem na pasta “res/drawable”.

  • A classe R. armazena o id dos elementos de interface.

  • Utilizar como unidade de tamanho “dp” para imagens e “sp” para textos, para melhor ajuste a diferentes telas.

Botão

  • Ao criar uma função que será ativada por botão na interface (onClick), essa função deverá receber como parâmetro uma view:

  • Recuperar view por id:

  • findViewbyID(R.id.nome_da_id) as tipodowidget

  • ou

  • Com o plugin Kotlin Android Extensions, você pode referenciar o id diretamente.

1
2
3
4
5
fun gerarFrase(view: View){
    val totalIntensArray = frases.size
    val numeroAleatorio = Random().nextInt( totalIntensArray )
    texto.setText( frases[ numeroAleatorio ] )
}

Orientação padrão do app (em caso de rotação)

  • Para definir uma orientação fixa acesse o arquivo AndroidManifest.xml na pasta manifests e adicione, após
1
2
<activity android:name=".MainActivity">
	<android:screenOrientation=”portrait”>

Banco de dados (SQLite)

Armazenamento de dados Banco de dados relacional ○ Estrutura de tabelas ● Tipos de dados permitidos ○ TEXT, REAL, INTEGER, NULL, BLOB

O SQLite é uma pequena biblioteca, implementada em C que oferece acesso a uma base de dados relacional SQL e suportando até 2 TB de dados. Seu armazenamento é feito em um arquivo de texto dentro do dispositivo, e não existe a necessidade de se estabelecer qualquer tipo de conexão JDBC ou ODBC por exemplo.

#Kotlin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package com.example.convidados.service.repository

import android.content.ContentValues
import android.content.Context
import android.text.Selection
import com.example.convidados.service.constants.DataBaseConstants
import com.example.convidados.service.model.GuestModel
import java.lang.Exception
import java.util.ArrayList

class GuestRepository private constructor(context: Context) {

    // Acesso ao banco de dados
    private var mGuestDataBaseHelper: GuestDataBaseHelper = GuestDataBaseHelper(context)

    /**
     * Singleton
     */
    companion object {
        private lateinit var repository: GuestRepository

        fun getInstance(context: Context): GuestRepository {
            if (!::repository.isInitialized) {
                repository = GuestRepository(context)
            }
            return repository
        }
    }

    /**
     * Carrega convidado
     */
    fun get(id: Int): GuestModel? {

        var guest: GuestModel? = null
        return try {
            val db = mGuestDataBaseHelper.readableDatabase

            // Colunas que serão retornadas
            val projection = arrayOf(
                DataBaseConstants.GUEST.COLUMNS.NAME,
                DataBaseConstants.GUEST.COLUMNS.PRESENCE
            )

            // Filtro
            val selection = DataBaseConstants.GUEST.COLUMNS.ID + " = ?"
            val args = arrayOf(id.toString())

            val cursor = db.query(
                DataBaseConstants.GUEST.TABLE_NAME,
                projection,
                selection,
                args,
                null,
                null,
                null
            )

            // Verifica se existem dados no cursor
            if (cursor != null && cursor.count > 0) {
                cursor.moveToFirst()

                val name = cursor.getString(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.NAME))
                val presence = (cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.PRESENCE)) == 1)

                guest = GuestModel(id, name, presence)
            }

            cursor?.close()
            guest
        } catch (e: Exception) {
            guest
        }
    }

    /**
     * Insere convidado
     */
    fun save(guest: GuestModel): Boolean {
        return try {

            // writableDatabase - Para fazer escrita de dados
            val db = mGuestDataBaseHelper.writableDatabase

            val contentValues = ContentValues()
            contentValues.put(DataBaseConstants.GUEST.COLUMNS.NAME, guest.name)
            contentValues.put(DataBaseConstants.GUEST.COLUMNS.PRESENCE, guest.presence)
            db.insert(DataBaseConstants.GUEST.TABLE_NAME, null, contentValues)
            true
        } catch (e: Exception) {
            false
        }
    }

    /**
     * Faz a listagem de todos os convidados
     */
    fun getAll(): List<GuestModel> {
        val list: MutableList<GuestModel> = ArrayList()
        return try {
            val db = mGuestDataBaseHelper.readableDatabase

            // Colunas que serão retornadas
            val projection = arrayOf(
                DataBaseConstants.GUEST.COLUMNS.ID,
                DataBaseConstants.GUEST.COLUMNS.NAME,
                DataBaseConstants.GUEST.COLUMNS.PRESENCE
            )

            // Linha única
            // Cursor cursor = db.rawQuery("select * from Guest", null);

            // Faz a seleção
            val cursor = db.query(
                DataBaseConstants.GUEST.TABLE_NAME,
                projection,
                null,
                null,
                null,
                null,
                null
            )

            if (cursor != null && cursor.count > 0) {
                while (cursor.moveToNext()) {

                    val id = cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.ID))
                    val name = cursor.getString(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.NAME))
                    val presence = (cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.PRESENCE)) == 1)

                    val guest = GuestModel(id, name, presence)
                    list.add(guest)
                }

                // Como verificar se um valor é nulo
                // cursor.isNull(cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.PRESENCE))
            }

            cursor?.close()
            list
        } catch (e: Exception) {
            list
        }
    }

    /**
     * Faz a listagem de todos os convidados presentes
     */
    fun getPresent(): List<GuestModel> {
        val list: MutableList<GuestModel> = ArrayList()
        return try {
            val db = mGuestDataBaseHelper.readableDatabase

            val cursor = db.rawQuery("SELECT id, name, presence FROM Guest WHERE presence = 1", null)

            if (cursor != null && cursor.count > 0) {
                while (cursor.moveToNext()) {

                    val id = cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.ID))
                    val name = cursor.getString(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.NAME))
                    val presence = (cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.PRESENCE)) == 1)

                    val guest = GuestModel(id, name, presence)
                    list.add(guest)
                }
            }

            cursor?.close()
            list
        } catch (e: Exception) {
            list
        }
    }

    /**
     * Faz a listagem de todos os convidados presentes
     */
    fun getAbsent(): List<GuestModel> {
        val list: MutableList<GuestModel> = ArrayList()
        return try {
            val db = mGuestDataBaseHelper.readableDatabase

            val cursor = db.rawQuery("SELECT id, name, presence FROM Guest WHERE presence = 0", null)

            if (cursor != null && cursor.count > 0) {
                while (cursor.moveToNext()) {

                    val id = cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.ID))
                    val name = cursor.getString(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.NAME))
                    val presence = (cursor.getInt(cursor.getColumnIndex(DataBaseConstants.GUEST.COLUMNS.PRESENCE)) == 1)

                    val guest = GuestModel(id, name, presence)
                    list.add(guest)
                }
            }

            cursor?.close()
            list
        } catch (e: Exception) {
            list
        }
    }

    /**
     * Atualiza convidado
     */
    fun update(guest: GuestModel): Boolean {
        return try {
            val db = mGuestDataBaseHelper.writableDatabase

            val contentValues = ContentValues()
            contentValues.put(DataBaseConstants.GUEST.COLUMNS.NAME, guest.name)
            contentValues.put(DataBaseConstants.GUEST.COLUMNS.PRESENCE, guest.presence)

            // Critério de seleção
            val selection = DataBaseConstants.GUEST.COLUMNS.ID + " = ?"
            val args = arrayOf(guest.id.toString())

            db.update(DataBaseConstants.GUEST.TABLE_NAME, contentValues, selection, args)
            true
        } catch (e: Exception) {
            false
        }
    }

    /**
     * Remove convidado
     */
    fun delete(id: Int): Boolean {
        return try {
            val db = mGuestDataBaseHelper.writableDatabase
            val selection = DataBaseConstants.GUEST.COLUMNS.ID + " = ?"
            val args = arrayOf(id.toString())

            db.delete(DataBaseConstants.GUEST.TABLE_NAME, selection, args)

            true
        } catch (e: Exception) {
            false
        }
    }

}

Explorar banco de dados físico da aplicação: programa DB Browser for SQLite No Device File Explorer do emulador que estiver rodando a aplicação, você encontra o DB em data\data\pasta do app\databases

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.example.convidados.service.repository

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.example.convidados.service.constants.DataBaseConstants

class GuestDataBaseHelper(context: Context) : SQLiteOpenHelper(context, NAME, null, VERSION) {

    /**
     * Método executado somente uma vez quando o acesso ao banco de dados é feito pela primeira vez
     */
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(CREATE_TABLE_GUEST)
    }

    /**
     * Método executado quando a versão do DATABASE_VERSION é alterada
     * Dessa maneira, a aplicação sabe que o banco de dados foi alterado e é necessário rodar o script de update
     */
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}

    companion object {
        private const val VERSION = 1
        private const val NAME = "Convidados.db"

        private const val CREATE_TABLE_GUEST =
            ("create table " + DataBaseConstants.GUEST.TABLE_NAME + " ("
                    + DataBaseConstants.GUEST.COLUMNS.ID + " integer primary key autoincrement, "
                    + DataBaseConstants.GUEST.COLUMNS.NAME + " text, "
                    + DataBaseConstants.GUEST.COLUMNS.PRESENCE + " integer);")
    }

}

Adapters

Um adaptador é um objeto que cria uma ponte entre uma AdapterView e os dados para aquela View. O adaptador provê acesso aos dados e também é responsável por criar uma View para cada registro presente no conjunto de dados.

Layout e Navegação

Interfaces são criadas em XML View: Geralmente representam elementos na interface que o usuário possa interagir. Ex: Button, EditText, Switch, ImageView, CheckBox. ViewGroup: Elementos de layout que definem o comportamento de outros elementos. Um agrupador de elementos de interface. Ex: LinearLayout, RelativeLayout, ConstraintLayout, RecyclerView. A quantidade de elementos influencia no processamento e tempo de carregamento. Quanto mais elementos e mais níveis, mais custo para processamento. Usar as medidas recomendadas para largura e altura dos elementos. Tamanho de fonte deve usar a medida “sp” (scale independent pixels) Tamanho de elementos, margins, paddings, etc devem usar “dp” (Density Independent pixels) Largura e altura também podem ser definidas relativas ao elemento pai ou ao próprio elemento match_parent - Quando um elemento possui este valor no atributo largura ou altura (width ou height), seu tamanho será igual o tamanho do elemento pai (elemento exterior, nível acima) wrap_content - Quando um elemento possui este valor no atributo largura ou altura (width ou height), seu tamanho será o suficiente para acomodar seu conteúdo

Atributos de Layout

Padding e Margin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<TextView
android:layout_marginStart= "8dp"
android:layout_marginEnd= "8dp"
android:padding="8dp"
android:paddingBottom= "8dp"
android:paddingTop="8dp"
android:paddingLeft="8dp"
android:paddingRight= "8dp"
android:layout_margin= "8dp"
android:layout_marginLeft= "8dp"
android:layout_marginRight= "8dp"
android:layout_marginTop= "16dp"
android:layout_marginBottom= "16dp" 
/>

Gravity e LayoutGravity

Gravity: Alinhamento dentro do elemento. android:gravity=”center” Opções: Center, right, left, bottom, top, center_horizontal, center_vertical LayoutGravity: Alinhamento do elemento com relação ao pai. android:layout_gravity= “center” Opções: Center, right, left, bottom, top, center_horizontal, center_vertical

Background e textColor

Background android:background=”#fff” Aplicável para View e ViewGroup textColor android:textColor=”#0AA37D”

Estrutura de um projeto Android

Manifests: Configurações globais do app (icone do app, nome, tema, tela inicial activity main, permissões). Java: classes e recursos Java. res: Resources. Drawable para recursos gráficos; Layout; Mipmap para imagens e icones; Values (cores, strings e estilos).

Constraint layout

Layouts ajustáveis a diferentes telas. ScroolView permite criar um componente que cria uma barra de scrool se o componente ocupe uma região maior que a tela. ScrollView e HorizontalScrollView Você pode criar guidelines para ajustar os elementos no layout. Chain: você pode selecionar vários elementos e criar uma corrente entre eles (create … chain). Primeira versão liberada juntamente com Android Studio 2.3 (meio 2018) Criado com a intenção de deixar a hierarquia plana e melhorar a performance da aplicação Melhorias significativas para que pudesse ser construído usando o Design do Android Studio além do XML Disponível a partir da API 9

Melhores práticas

Ao criar um novo elemento somente com width e height, o Android Studio notifica sobre as “constraints” do elemento This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you add constraints. Significa que o elemento não sabe como se comportar na tela. Assim, ao criar um elemento, é necessário dizer quais suas “constraints” que também pode ser chamada de “restrições” ou “constantes” match_parent não é uma boa prática Mesmo que muitas vezes tenha o funcionamento esperado, deve-se usar constraints left e right e atribuir 0dp

Equivalência RelativeLayout e ConstraintLayout

Equivalência RelativeLayout e ConstraintLayout

RelativeLayoutConstraintLayout
android:layout_alignParentLeft=”true”app:layout_constraintLeft_toLeftOf=”parent”
android:layout_alignParentStart=”true”app:layout_constraintStart_toStartOf=”parent”
android:layout_alignParentRight=”true”app:layout_constraintRight_toRightOf=”parent”
android:layout_alignParentEnd=”true”app:layout_constraintEnd_toEndOf=”parent”
android:layout_alignParentTop=”true”app:layout_constraintTop_toTopOf=”parent”
android:layout_alignParentBottom=”true”app:layout_constraintBottom_toBottomOf=”parent”
android:layout_alignStart=”@id/view”app:layout_constraintStart_toStartOf=”@id/view”
android:layout_alignLeft=”@id/view”app:layout_constraintLeft_toLeftOf=”@id/view”
android:layout_alignEnd=”@id/view”app:layout_constraintEnd_toEndOf=”@id/view”
android:layout_alignRight=”@id/view”app:layout_constraintRight_toRightOf=”@id/view”
android:layout_alignTop=”@id/view”app:layout_constraintTop_toTopOf=”@id/view”
android:layout_alignBottom=”@id/view”app:layout_constraintBottom_toBottomOf=”@id/view”
android:layout_toStartOf=”@id/view”app:layout_constraintEnd_toStartOf=”@id/view”
android:layout_toLeftOf=”@id/view”app:layout_constraintRight_toLeftOf=”@id/view”
android:layout_toEndOf=”@id/view”app:layout_constraintStart_toEndOf=”@id/view”
android:layout_toRightOf=”@id/view”app:layout_constraintLeft_toRightOf=”@id/view”
android:layout_above=”@id/view”app:layout_constraintBottom_toTopOf=”@id/view”
android:layout_below=”@id/view”app:layout_constraintTop_toBottomOf=”@id/view”

Layout Relativo e Layout Linear

Para substituir o constraint layout (padrão atual), editar o xml.

RelativeLayout

Posições das Views relativas à elas ou relativas ao pai (VIewGroup) Criado com a intenção de deixar a hierarquia mais simples e melhorar a performance da aplicação Orientação da esquerda/topo para direita/inferior layout_alignParentTop Se marcado como “true”, o topo da View será alinhado ao topo do elemento pai layout_alignParentBottom Se marcado como “true”, o inferior da View será alinhado ao inferior do elemento pai layout_alignParentLeft Se marcado como “true”, a esquerda da View será alinhada à esquerda do elemento pai layout_alignParentRight Se marcado como “true”, a direita da View será alinhada à direita do elemento pai layout_centerInParent Se marcado como “true”, a View será horizontalmente alinhada com o pai layout_centerVertical Se marcado como “true”, a View será verticalmente alinhada com o pai layout_above Coloca o elemento acima de outra View layout_below Coloca o elemento abaixo de outra View layout_toLeftOf Coloca o elemento à esquerda de outro elemento referenciado pelo ID layout_toRightOf Coloca o elemento à direita de outro elemento referenciado pelo ID layout_toStartOf Coloca o elemento à esquerda de outro elemento referenciado pelo ID layout_toEndOf Coloca o elemento à direita de outro elemento referenciado pelo ID

LinearLayout

Organiza os elementos de forma linear - Horizontal ou vertical. Ambas orientações não adequam os elementos para que caibam na tela. Caso sejam adicionados mais elementos do que a tela comporta, eles ficarão sobrepostos. É possível dividir a tela em partes atribuindo peso aos elementos: android:layout_weight Não é obrigatório que os pesos sejam os mesmo. É possível dividir a tela em partes com valores diferentes.

Itent: transição para novas telas (activity)

Como criar uma nova Activity a partir de uma já existente? Realizamos essa operação criando um objeto de mensagem que será utilizado para solicitar uma ação a outro componente de aplicativo. Este objeto de mensagem é conhecido como Intent, e trabalha com três dos componentes de aplicativo.

graph LR
    A[Activity A] -->|"startActivity()"| B(Intent)
    B --> C{Android System}
    C -->D(Intent)
    D -->|"onCreate()"| E[Activity B]

Iniciamos uma nova Activity passando sua referência a uma Intent e então passando esta Intent ao método startActivity(). Nesse caso a Intent descreverá a atividade a iniciar e carregará todos os dados necessários. Caso você necessite obter um resultado quando a nova Activity finalizar, basta passar a Intent ao método startActivityForResult(). Assim, sua Activity estará apta a receber o resultado como um objeto Intent separado no retorno de chamada onActivityResult().

1
2
3
4
button_contato.setOnClickListener {
	val intent = Intent(this, DetalheContatoActivity::class.java)
	startActivity(intent)
}

Passando dados entre Activities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
button_passar_dados.setOnClickListener {
	val intent = Intent(applicationContext, SegundaActivity::class.java)
	//Passar dados
	intent.putExtra(name:"nome", value:"João")
	intent.putExtra(name:"idade", value:"28")
	startActivity(intent)
}

//no activity que vai receber
val dados: Bundle = intent.extras
val nome = dados.getString(key:"nome")
val idade = dados.getInt(key:"idade")
text_nome.text = nome
text_idade.text = idade.toString()

Destruir uma Activity que não será mais utilizada

1
finish()

SharedPreferences

Usado para dados pequenos de uso rápido, não usar como banco de dados.

SharedPreferences e permite que tipos primitivos sejam salvos no formato “chave,valor”. Uma vez salvos, estes dados podem ser acessados de qualquer lugar de dentro de seu aplicativo, tanto para leitura quanto para escrita. No entanto, estes dados não poderão ser acessados a partir de outros aplicativos.

Seu uso é comum em situações onde você deve guardar alguma informação primitiva (ou seja, tipos simples) entre sessões de utilização de seu aplicativo. Por exemplo, você poderia guardar o nome do usuário, suas configurações desejadas ou até mesmo a maior pontuação atingida em um jogo.

Suas preferências podem ser salvas em um ou mais arquivos, dependendo de sua necessidade. Para preferências em nível de Activity, utiliza-se a chamada getPreferences(). Para preferências em nível de aplicação (ou seja, que podem ser acessadas de qualquer parte da aplicação utiliza-se a chamada getSharedPreferences().

O acesso aos dados armazenados é feito através da instância de um objeto SharedPreferences, e a partir daí utilizamos métodos get para recuperação do valor.

1
2
val shared = this.getSharedPreferences(name"motivation", Context.MODE_PRIVATE)
shared.edit().putString("key", "value").apply()

Internacionalização

Na pasta res, criar um xml values-** com o código relativo a língua. ISO 639-1 Se o sistema estiver nessa língua, o idioma será setado automaticamente. Para mudar o idioma na aplicação:

TextView

Elemento de layout que possibilita mostrar um texto ao usuário android:text Texto usado no elemento android:textColor Cor do texto usado no elemento - Pode ser usado em hexadecimal android:textSize Tamanho do texto - Medida recomendada em “sp” (Scale independent pixels - Escala independente de pixels) Medida usada em “sp” permite o recurso de acessibilidade do celular alterar o tamanho da fonte android:textStyle bold / italic / normal android:fontFamily Fonte usada no texto android:textAllCaps Define todo o texto maiúsculo ou não

EditText

Elemento de layout que possibilita o usuário fornecer informações à aplicação android:inputType=”OPÇÃO” text: Texto livre phone: Faz a troca do teclado para números textPassword: Esconde o texto informado pelo usuário textAutoCorrect: Faz sugestões de texto ao usuário textCapSentences: Torna o texto informado maiúsculo number: Faz a troca do teclado para números textEmailAddress: Faz a troca do teclado com @ e caracteres usados no email textMultiLine: Permite que usuário digite diversas linhas android:imeOptions=”OPÇÃO” actionSearch: Troca o botão inferior direito para uma lupa actionNext: Troca o botão inferior direito para navegar para o próximo campo android:hint=”SEU TEXTO” Deixa um texto que funciona como sugestão para o usuário É possível combinar opções. Não é necessário escolher somente uma entre as diversas disponíveis android:inputType=”textAutoCorrect|textCapSentences

ImageView

Checkbox

Arquitetura MVVM (Model-View-ViewModel)

Padrão arquitetural focado em separação de responsabilidades ● Padrão adotado pela Google ○ Recomendação de arquitetura para desenvolvimento de aplicação ● Uso do Android Architecture Components ○ Componentes desenvolvidos pela Google para ajudar a construir aplicações robustas ○ LiveData e ViewModel ■ Respeitam o ciclo de vida da aplicação

graph TD
    A(Activity/Fragment) -->B
    subgraph ViewModel
        B[[LiveData 3]]
    end
    B --> C(Repository)
    subgraph Model
        D(Room)
    end
    subgraph Remote Data Source
        E(Retrofit)
    end
    C -->D-->F[(SQLite)]
    C -->E-->G

Activity (View+ViewModel)

graph LR
    subgraph  
        B-->A
        A[View]-->B[ViewModel]
    end
    C-->B
    B --> C[Model]

Ciclo de vida de uma activity

Uma Activity representa uma tela com interface gráfica capaz de promover algum tipo de interação com o usuário. Uma aplicação Android pode ser composta de diversas activities para fornecer um conjunto de funcionalidades para o usuário. Compreender o funcionamento e as transições é importante para o desenvolvimento e implementação da lógica da aplicação. Por exemplo, ao trabalhar com um aplicativo de vídeo, pode ser um requisito pausar o vídeo se o dispositivo receber uma ligação ou se outro aplicativo for aberto simultaneamente. Dentro do ciclo de vida da Activity, é possível declarar o comportamento quando o usuário deixa ou retorna a activity, de maneira a atingir esses requisitos. A activity possui um ciclo de vida específico. Quando um usuário abre o aplicativo, troca de tela ou deixa o aplicativo em segundo plano, existe uma transição entre estados descritos abaixo. Em outras palavras, as transições no ciclo de vida permitem a execução de códigos e funcionalidades apropriados para cada estado da activity

graph TD
    A([Activity launched]):::azul-->B["onCreate()"]-->C["onStart()"]-->D["onResume()"]-->E([Activity running]):::verde
    G & F-->|Apps with higher priority need memory|J([App process killed]):::laranja-->|User navigates to the activity|B
    E -->|Another activity comes into the foreground| F["onPause()"]-->|The activity is no longer visible|G["onStop()"]-->|The activity is finishing or being destroyed by the system|H["onDestroy()"]-->I([Activity shut down]):::laranja
    F-->|User returns to the activity|D
    G-->|User navigates to the activity|K["onRestart()"]-->C
    classDef azul fill:#0E4357, color: #EDF4F5;
    classDef verde fill:#ADEEE3, color:#000;
    classDef laranja fill:#F5B841, color:#0E4357;

Callbacks no ciclo de vida

Sempre que uma activity muda de estado, o Android faz a chamada de um método (callback) correspondente.

onCreate()

Quando um aplicativo é aberto, o Android sabe qual a página inicial que deve ser criada, uma vez que está definida no AndroidManifest.xml. Com isso, o método onCreate() da activity é invocado. O método onCreate, embora não seja obrigatório, é sempre implementado por padrão na criação do projeto ou na criação de outras Activities. É criado por padrão por ser o melhor local para definição do layout, identificação dos elementos e inicialização de variáveis que serão usadas durante para implementação. Além disso, popular uma lista, fazer uma chamada à API ou acesso ao banco de dados são exemplos do que pode ser feito dentro deste método.

onStart()

Esse método faz com que a activity seja visível para o usuário, enquanto o aplicativo prepara para a activity entrar em funcionamento e se tornar interativa. O método onStart() é executado rapidamente e não é recomendado para processamentos pesados. Assim que esse callback é terminado, a activity entra no modo resumido.

onResume()

Quando a activity entrar no estado resumido, ela entra em funcionamento e está pronta para interação com o usuário. Assim que o aplicativo entra nesse estado, permanecerá assim até que algo aconteça e tire o foco do aplicativo. Por exemplo, uma chamada telefônica, um alarme ou a tela do aplicativo sendo bloqueada ou desligada. Se a activity retorna ao estado Resumido, o sistema chama onResume() novamente e não onStart(), uma vez que ela já foi iniciada e já está em memória. Por esse motivo, é necessário implementar onResume() para reinicializar componentes descartados no onPause(). Este método é chamado todas as vezes que uma activity se torna visível para o usuário, inclusive a primeira vez após a chamada do método onCreate() e onStart()

onPause()

Quando qualquer um dos eventos descritos no método onResume ocorre, o Android faz a chamada do callback onPause(). Esse método é chamado como o primeiro indicativo de que o usuário está saindo da activity. Este método deve ser usado para pausar animações e músicas que não devem continuar quando a tela está pausada. Por exemplo, se o aplicativo utiliza a câmera, o método onPause() é um ótimo local para desalocar o recurso da câmera para ser alocada posteriormente no método onResume(). A execução desse método é muito breve e não necessariamente dispõe de muito tempo para performar ações de salvar dados. Por esse motivo, esse método não deve ser usado para salvar dados da aplicação, chamadas a APIs ou banco de dados. Tais ações devem ser feitas no método onStop() descrito abaixo.

onStop()

Quando a activity não está mais visível para o usuário, porém ainda permanece alocada na memória o sistema invoca o callback onStop(). É neste método que todos os recursos alocados na criação devem ser descartados. O método também é recomendado quando se deseja realizar alguma operação que use relativamente a CPU, como por exemplo, salvar dados no banco de dados ou uma chamada a uma API. A partir deste estado, a activity volta a interagir com o usuário ou é finalizada e removida da memória. Se a activity voltar, o sistema invoca onRestart(). Se a activity terminar de ser executada, o sistema chamará onDestroy().

onDestroy()

Método chamado antes da activity ser destruída. Esta é a chamada final que a Activity recebe. Este método libera todos os recursos alocados que não tenham sido descartados previamente durante os outros métodos.

Room

Camada de abstração do banco de dados. Parte do Architecture Components

graph TD
    subgraph Room
        A[Entity]
        B["DAO (Data Access Objects)"]
        C[Database]
    end

Adicionar a dependência no gradle: apply plugin: 'kotlin-kapt' implementation 'androidx.room:room-runtime:2.2.5' kapt 'androidx.room:room-compiler:2.2.5' GuestModel.kt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.convidados.service.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "Guest")
class GuestModel {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Int = 0

    @ColumnInfo(name = "name")
    var name: String = ""

    @ColumnInfo(name = "presence")
    var presence: Boolean = false

}

GuestDAO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.convidados.service.repository

import androidx.room.*
import com.example.convidados.service.model.GuestModel

@Dao
interface GuestDAO {

    @Insert
    fun save(guest: GuestModel): Long

    @Update
    fun update(guest: GuestModel): Int

    @Delete
    fun delete(guest: GuestModel)

    @Query("SELECT * FROM Guest WHERE id = :id")
    fun load(id: Int): GuestModel

    @Query("SELECT * FROM Guest")
    fun getInvited(): List<GuestModel>

    @Query("SELECT * FROM Guest WHERE presence = 1")
    fun getPresent(): List<GuestModel>

    @Query("SELECT * FROM Guest WHERE presence = 0")
    fun getAbsent(): List<GuestModel>
}

GuestDatabase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.convidados.service.repository

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.convidados.service.model.GuestModel

@Database(entities = [GuestModel::class], version = 1)
abstract class GuestDatabase : RoomDatabase() {

    abstract fun guestDAO(): GuestDAO

    companion object {
        private lateinit var INSTANCE: GuestDatabase

        fun getDatabase(context: Context): GuestDatabase {
            if (!::INSTANCE.isInitialized) {
                synchronized(GuestDatabase::class) {
                    INSTANCE = Room.databaseBuilder(context, GuestDatabase::class.java, "guestDB")
                        .addMigrations(MIGRATION_1_2)
                        .allowMainThreadQueries()
                        .build()
                }
            }
            return INSTANCE
        }

        /**
         * Atualização de versão de banco de dados
         */
        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("DELETE FROM Guest")
            }
        }
    }
}

GuestRepository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.example.convidados.service.repository

import android.content.ContentValues
import android.content.Context
import android.text.Selection
import com.example.convidados.service.constants.DataBaseConstants
import com.example.convidados.service.model.GuestModel
import java.lang.Exception
import java.util.ArrayList

class GuestRepository(context: Context) {

    // Acesso ao banco de dados
    private val mDataBase = GuestDatabase.getDatabase(context).guestDAO()

    /**
     * Carrega convidado
     */
    fun get(id: Int): GuestModel {
        return mDataBase.load(id)
    }

    /**
     * Insere convidado
     */
    fun save(guest: GuestModel): Boolean {
        return mDataBase.save(guest) > 0
    }

    /**
     * Faz a listagem de todos os convidados
     */
    fun getAll(): List<GuestModel> {
        return mDataBase.getInvited()
    }

    /**
     * Faz a listagem de todos os convidados presentes
     */
    fun getPresent(): List<GuestModel> {
        return mDataBase.getPresent()
    }

    /**
     * Faz a listagem de todos os convidados presentes
     */
    fun getAbsent(): List<GuestModel> {
        return mDataBase.getAbsent()
    }

    /**
     * Atualiza convidado
     */
    fun update(guest: GuestModel): Boolean {
        return mDataBase.update(guest) > 0
    }

    /**
     * Remove convidado
     */
    fun delete(guest: GuestModel) {
        mDataBase.delete(guest)
    }
}

API - Conceitos teóricos e teste usando Postman

Application Programming Interface Não se refere a Interface tratada em orientação a objetos Fronteira que define a forma de comunicação entre duas partes Computação Funcionalidades de um software expostas Confiabilidade Praticidade Agilidade API Web Funcionalidades expostas através de conexões Web HTTP, HTTPS Respostas no formato XML ou JSON Exemplos Login com redes sociais - Facebook, Gmail, Instagram, Twitter Uso para construção de aplicações mobile, desktop, web RESTful X RPC

Requisições

Uso do protocolo HTTP / HTTPs Composta por 4 partes URL Method Body Header

URL

Endereço web acessível https://jsonplaceholder.typicode.com/posts É um endereço como um site comum, porém disponibiliza dados para consumo ao invés de páginas web de leitura.

Method

Tambem chamado de verbo. Diz qual a maneira de interação com a API. Quatro tipos mais utilizados ● POST (create) ● GET (read) ● PUT (update) ● DELETE (delete)

Contém detalhes a respeito da requisição. Geralmente configurações sobre a requisição. Por exemplo, cache, content-type, dispositivo. Em muitos casos, autenticação também é fornecida através do header.

Body

Parâmetros da requisição. Por exemplo, se for uma requisição de inserção, o body contém os valores que deverão ser inseridos. Pode-se se pensar como os parâmetros de um método na programação.

Resposta

Código HTTP

Descrevem o status da requisição. Status são padronizados como exemplo abaixo: ● 1xx: Informações gerais; ● 2xx: Sucesso na requisição e na resposta; ● 3xx: Redirecionamento para outra URL; ● 4xx: Erro (por parte do cliente); ● 5xx: Erro (por parte do servidor).

Header

Detalhes a respeito da requisição.

Body

Contém a resposta da requisição. Pode ser uma lista de valores, valores únicos, depende do tipo de requisição e da informação requisitada.

Retrofit

Abstração para chamadas a API Parte do Architecture Components

graph TD
    subgraph Retrofit
        A[Entity]
        B[Serviço]
        C[Retrofit]
    end

Fingerprint - autenticação biométrica

Gradle: implementation 'androidx.biometric:biometric:1.0.1'

Componentes

Toast notification

Uma pequena pop-up que exibe uma mensagem de texto para o usuário.

1
Toast.makeText(applicationContext, "texto", Toast.LENGTH_LONG).show()

Permite formação de layout.

Snackbar

Gradle: implementation 'com.google.android.meterial:material:1.1.0'

1
Snackbar.make(linear_root, "texto", Snackbar.LENGTH_LONG).show()

Permite receber ação de clique.

Spinner

Lista suspensa.

SeekBar

Barra deslizante para selecionar valores de um range.

Switch

Interruptor, on ou off.

Checkbox

RadioButton e RadioGroup

DatePicker

Pode soprepor como uma janela de diálogo, ou ter uma visão de tela toda.

Timepicker

Semelhante ao datepicker.

ProgressBar

AndroidX

Atualmente quando novas aplicações são criadas usando o Android Studio, ao entrar no arquivo gradle, percebe-se a presença de uma biblioteca chamada androidx. Em aplicações antigas (mas não tanto, 2018, 2019), ao entrar no arquivo gradle do projeto, é muito comum ver a seguinte dependência: implementation ‘com.android.support:appcompat-v7:28.0.0’ Atualmente, a biblioteca androidx aparece da seguinte maneira: implementation ‘androidx.appcompat:appcompat:1.1.0’ Em resumo, androidx é a mais nova atualização de funcionalidades Android que substitui completamente a antiga Support Library. Assim, em novos projetos, não existe mais referência a Support Library.

Por que houve a troca de nome?

A motivação por trás da troca de nome é a melhor separação de funcionalidades entre bibliotecas do sistema operacional Android e bibliotecas de desenvolvimento. Bibliotecas de desenvolvimento ficarão no pacote androidx. Uma motivação com um peso maior do que simplesmente organização é que como as bibliotecas de sistema e funcionalidade não estão mais juntas, elas podem ser atualizadas separadamente. Ou seja, seu projeto Android pode receber melhorias através da androidx sem a necessidade de atualizar as bibliotecas de sistema operacional. O pacote androidx traz dentro dele todas as bibliotecas de funcionalidades Android que são mais comumente chamadas de Android Jetpack.

Android Jetpack

Android Jetpack é o nome dado as funcionalidades que são usadas para o desenvolvimento de aplicações Android. Por exemplo, Navigation, Preferences, Fragment, Layout, Room, ViewModel, Lifecycles, Notifications, Permissions e mais. As funcionalidades acima são usadas para realizar tarefas e implementações de maneira mais fácil. Não é necessário, por exemplo, escrever o código completo dizendo como o sistema operacional trata uma notificação, é necessário usar a funcionalidade de notificação já disponível. Este é o conceito do Android Jetpack, disponibilizar implementações que facilitem a vida do desenvolvedor, de maneira que possa focar em criar as funcionalidades da aplicação que possuem valor para o usuário.

Androidx roda em sistemas operacionais antigos?

Sim. A nova atualização, assim como de costume, oferece retrocompatibilidade. Ou seja, é seguro usar a última versão da biblioteca, pois usuários de versões antigas não serão esquecidos

Esta postagem está licenciada sob CC BY 4.0 pelo autor.