O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Kotlin para Jetpack Compose

O Jetpack Compose foi criado com base em Kotlin. Em alguns casos, o Kotlin oferece expressões idiomáticas especiais que facilitam a criação de um bom código do Compose. Se você imaginar seu código em outra linguagem de programação e traduzir mentalmente essa linguagem para o Kotlin, provavelmente perderá algumas das vantagens do Compose e poderá achar difícil entender código Kotlin escrito idiomaticamente. Compreender melhor o estilo do Kotlin pode ajudar você a evitar esses problemas.

Argumentos padrão

Ao escrever uma função do Kotlin, você pode especificar valores padrão para argumentos de função, usados se o autor da chamada não transmitir esses valores explicitamente. Esse recurso reduz a necessidade de funções sobrecarregadas.

Por exemplo, suponha que você queira criar uma função que desenhe um quadrado. Essa função pode ter um único parâmetro obrigatório, side, especificando o comprimento de cada lado. Ela pode ter vários parâmetros opcionais, como thickness, edgeColor, fill e assim por diante. Se eles não forem especificados pelo autor da chamada, a função usará valores padrão. Em outras linguagens, seria esperado ter que criar várias funções:

// We don't need to do this in Kotlin!
void drawSquare(int side) {...}
void drawSquare(int side, int thickness) {...}
void drawSquare(int side, int thickness, Color edgeColor ) {...}
// …

Com o Kotlin, é possível escrever uma única função e especificar os valores padrão dos argumentos:

fun drawSquare(
    side: Int, thickness: Int = 2,
    edgeColor: Color = Color.Black,
    fill: Color = Color.White
) { ... }

Além de evitar que você precise escrever várias funções redundantes, esse recurso deixa a leitura do código muito mais clara. Caso o autor da chamada não especifique um valor para um argumento, isso indica que ele está disposto a usar o valor padrão. Além disso, os parâmetros nomeados facilitam muito a visualização do que está acontecendo. Se você observar o código e vir uma chamada de função como esta, talvez não saiba o que os parâmetros significam sem analisar o código drawSquare():

drawSquare(30, 5, Color.Red);

Por outro lado, este código é autodocumentado:

drawSquare(side = 30, thickness = 5, edgeColor = Color.Red)

A maioria das bibliotecas do Compose usa argumentos padrão, e é recomendável fazer o mesmo para as funções que podem ser compostas que você criar. Essa prática torna os elementos que podem ser compostos personalizáveis, mas ainda será simples invocar o comportamento padrão. Por exemplo, você pode criar um elemento de texto simples como este:

Text(text = "Hello, Android!")

Esse código tem o mesmo efeito que o seguinte código muito mais detalhado, em que mais parâmetros Text são definidos explicitamente:

Text(text = "Hello, Android!",
    color = Color.Unset,
    fontSize = TextUnit.Inherit,
    letterSpacing = TextUnit.Inherit,
    Overflow = TextOverflow.Clip)

Além do primeiro snippet de código ser muito mais simples e fácil de ler, ele também é autodocumentado. Ao especificar apenas o parâmetro text, você documenta que, para todos os outros parâmetros, você quer usar os valores padrão. Já o segundo snippet implica que você quer definir explicitamente os valores desses outros parâmetros, ainda que os valores definidos sejam os valores padrão da função.

Funções de ordem superior e expressões lambda

O Kotlin é compatível com funções de ordem superior, que recebem outras funções como parâmetros. O Compose amplia essa abordagem. Por exemplo, a função que pode ser composta Button fornece um parâmetro lambda onClick. O valor desse parâmetro é uma função, que o botão chama quando o usuário clica nele:

Button( // …
    onClick = myClickFunction)

As funções de ordem superior são pareadas naturalmente com expressões lambda, que são expressões que avaliam uma função. Se você precisar da função apenas uma vez, não precisará defini-la em outro lugar para transmiti-la à função de ordem superior. Em vez disso, você pode definir a função diretamente com uma expressão lambda. O exemplo anterior supõe que myClickFunction() foi definida em outro lugar. Mas, se você usar essa função apenas aqui, será mais simples definir a função in-line com uma expressão lambda:

Button( // ...
    onClick = {
       // do something
       // do something else
    }
)

Lambdas finais

O Kotlin oferece uma sintaxe especial para chamar funções de ordem superior cujo parâmetro last é um lambda. Se você quiser transmitir uma expressão lambda como esse parâmetro, poderá usar a sintaxe de lambdas finais. Em vez de colocar a expressão lambda entre parênteses, você a coloca em seguida. Essa é uma situação comum no Compose, então você precisa estar familiarizado com a aparência do código.

Por exemplo, o último parâmetro para todos os layouts, como a função que pode ser composta Column(), é content, uma função que emite os elementos da IU na coluna. Suponha que você queira criar uma coluna com três elementos de texto e aplicar uma formatação. Esse código funcionaria, mas seria muito complicado:

Column(
    modifier = Modifier.padding(16.dp), content = {
        Text("Some text")
        Text("Some more text")
        Text("Last text")
    }
)

Como o parâmetro children é o último na assinatura da função e estamos transmitindo o valor dele como uma expressão lambda, podemos retirá-lo dos parênteses:

Column(modifier = Modifier.padding(16.dp)) {
    Text("Some text")
    Text("Some more text")
    Text("Last text")
}

Os dois exemplos têm o mesmo significado. As chaves definem a expressão lambda transmitida para o parâmetro content.

Na verdade, se o único parâmetro que você está transmitindo for o lambda final, ou seja, se o parâmetro final for um lambda e você não estiver transmitindo outros parâmetros, você poderá omitir os parênteses. Então, por exemplo, suponha que você não tenha transmitido um modificador para Column. Você pode escrever o código assim:

Column {
    Text("Some text")
    Text("Some more text")
    Text("Last text")
}

Essa sintaxe é muito comum no Compose, especialmente para elementos de layout, como Column. O último parâmetro é uma expressão lambda que define os filhos do elemento, e eles são especificados em chaves após a chamada de função.

Escopos e receptores

Alguns métodos e propriedades só estão disponíveis em escopos específicos. O escopo limitado permite oferecer funcionalidades quando necessário e evitar a utilização acidental delas quando não for apropriado.

Considere um exemplo usado no Compose. Quando você chama o layout Row que pode ser composto, o lambda de conteúdo é automaticamente invocado em RowScope. Isso permite que Row exponha funcionalidades válidas somente dentro de um Row. O exemplo abaixo demonstra como Row expôs um valor específico de linha para o modificador gravity:

Row {
    Text(
        text = "Hello world",
        // This Text is inside a RowScope so it has access to
        // Alignment.CenterVertically but not to
        // Alignment.CenterHorizontally, which would be available
        // in a ColumnScope.
        modifier = Modifier.gravity(Alignment.CenterVertically)
    )
}

Algumas APIs aceitam lambdas chamados no escopo do receptor. Esses lambdas têm acesso a propriedades e funções que são definidas em outro lugar, com base na declaração de parâmetro:

Box(
    modifier = Modifier.drawBehind {
        // This method accepts a lambda of type DrawScope.() -> Unit
        // therefore in this lambda we can access properties and functions
        // available from DrawScope, such as the `drawRectangle` function.
        drawRectangle(...)
    }
)

Para ver mais informações, consulte literais de função com o receptor na documentação do Kotlin (link em inglês).

Propriedades delegadas

O Kotlin é compatível com propriedades delegadas. Essas propriedades são chamadas como se fossem campos, mas seu valor é determinado dinamicamente por meio da avaliação de uma expressão. É possível reconhecer essas propriedades pelo uso da sintaxe by:

class delegatingClass {
    var name: String by nameGetterFunction()
}

Outros códigos podem acessar a propriedade com um código como este:

myDC = delegatingClass()
println("The name property is: " + myDC.name)

Quando println() é executado, nameGetterFunction() é chamada para retornar o valor da string.

Essas propriedades delegadas são úteis principalmente quando você trabalha com propriedades com estado armazenado:

var showDialog by state { false }

// Updating the var automatically triggers a state change
showDialog = true

Como desestruturar classes de dados

Se você definir uma classe de dados, será possível acessar os dados facilmente com uma declaração de desestruturação. Por exemplo, suponha que você defina uma classe Person:

data class Person(val name: String, val age: Int)

Se você tiver um objeto desse tipo, poderá acessar os valores dele com um código como este:

val mary = Person(name = "Mary", age = 35)

// ...

val (name, age) = mary

Você geralmente verá esse tipo de código em funções do Compose:

ConstraintLayout {

    val (image, title, subtitle) = createRefs()

    // The `createRefs` function returns a data object;
    // the first three components are extracted into the
    // image, title, and subtitle variables.

    // ...

}

As classes de dados oferecem muitas outras funcionalidades úteis. Por exemplo, quando você define uma classe de dados, o compilador define automaticamente funções úteis, como equals() e copy(). Veja mais informações na documentação das classes de dados.

Objetos singleton

Com o Kotlin, é fácil declarar singletons, que sempre têm uma e apenas uma instância. Estes singletons são declarados com a palavra-chave object. O Compose usa esses objetos com frequência. Por exemplo, MaterialTheme é definido como um objeto singleton; as propriedades MaterialTheme.colors, shapes e typography contêm os valores do tema atual.