Você está na página 1de 35

Conheça a linguagem de programação Kotlin

Kotlin é uma linguagem de programação amplamente usada por desenvolvedores


Android em qualquer lugar. Este tópico funciona como um curso intensivo do Kotlin
para você começar a usar rapidamente.

Declaração de variável

O Kotlin usa duas palavras-chave diferentes para declarar variáveis: val e var.

 Use val para uma variável cujo valor nunca muda. Não é possível reatribuir um
valor a uma variável que tenha sido declarada usando val.
 Use var para uma variável cujo valor possa ser mudado.

No exemplo abaixo, count é uma variável do tipo Int que recebe um valor inicial de 10:

var count: Int = 10

Int é um tipo que representa um número inteiro, um dos muitos tipos numéricos que
podem ser representados em Kotlin. Assim como acontece com outras linguagens,
você também pode usar Byte, Short, Long, Float e Double, dependendo dos seus
dados numéricos.

A palavra-chave var significa que você pode reatribuir valores a count conforme


necessário. Por exemplo, você pode mudar o valor de count de 10 para 15:

var count: Int = 10


count = 15

No entanto, alguns valores não podem ser mudados. Considere


um String chamado languageName. Se você quiser garantir
que languageName sempre tenha o valor "Kotlin", poderá
declarar languageName usando a palavra-chave val:

val languageName: String = "Kotlin"

Essas palavras-chave permitem que você seja explícito sobre o que pode ser mudado.
Use-as em seu favor conforme necessário. Se uma referência de variável precisar ser
reatribuível, declare-a como var. Do contrário, use val.

Inferência de tipo

Continuando com o exemplo anterior, quando você atribui um valor inicial


a languageName, o compilador Kotlin pode inferir o tipo com base no tipo do valor
atribuído.

Como o valor de "Kotlin" é do tipo String, o compilador infere


que languageName também é um String. O Kotlin é uma linguagem estática. Isso
significa que o tipo é resolvido no momento da compilação e nunca muda.

1
No exemplo a seguir, languageName é inferido como String. Portanto, não é possível
chamar nenhuma função que não faça parte da classe String:

val languageName = "Kotlin"


val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() é uma função que só pode ser chamada em variáveis do tipo String.


Como o compilador Kotlin inferiu languageName como String, você pode
chamar toUpperCase() com segurança. inc(), entretanto, é uma função de
operador Int, por isso não pode ser chamada em String. A abordagem do Kotlin para a
inferência de tipos oferece concisão e segurança de tipos.

Segurança nula

Em algumas linguagens, uma variável de tipo de referência pode ser declarada sem
fornecer um valor explícito inicial. Nesses casos, as variáveis geralmente contêm um
valor nulo. Por padrão, as variáveis do Kotlin não podem reter valores nulos. Isso
significa que o snippet a seguir é inválido.

// Fails to compile
val languageName: String = null

Para que uma variável mantenha um valor nulo, ela precisa ser do tipo anulável. Você
pode especificar uma variável como sendo anulável, usando um sufixo do tipo com ?,
conforme mostrado neste exemplo a seguir.

val languageName: String? = null

Com um tipo String?, você pode atribuir um valor String ou null a languageName.

Você precisa lidar com variáveis anuláveis com cuidado ou corre o risco de ter
um NullPointerException. Em Java, por exemplo, se você tentar invocar um método
em um valor nulo, seu programa falhará.

O Kotlin fornece uma série de mecanismos para trabalhar com segurança com
variáveis anuláveis. Para ver mais informações, consulte Padrões comuns do Kotlin no
Android: anulação (link em inglês).

Condicionais

O Kotlin apresenta vários mecanismos para implementar a lógica condicional. O mais


comum deles é uma instrução if-else. Se uma expressão entre parênteses ao lado de
uma palavra-chave if for avaliada como true, o código dentro dessa ramificação (ou
seja, o código imediatamente seguinte que é encapsulado entre chaves) será
executado. Caso contrário, será executado o código dentro da ramificação else.

2
if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

Você pode representar várias condições usando else if. Isso permite representar uma
lógica mais granular e complexa em uma única instrução condicional, conforme
mostrado neste exemplo:

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

As instruções condicionais são úteis para representar a lógica com estado, mas você
pode se repetir ao gravá-las. No exemplo acima, você simplesmente imprime
um String em cada ramificação. Para evitar essa repetição, o Kotlin
oferece expressões condicionais. O último exemplo pode ser regravado da seguinte
forma:

val answerString: String = if (count == 42) {


    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

Implicitamente, cada ramificação condicional retorna o resultado da expressão na linha


final, de modo que não é necessário usar uma palavra-chave return. Como o resultado
das três ramificações é do tipo String, o resultado da expressão if-else também é do
tipo String. Neste exemplo, answerString recebe um valor inicial do resultado da
expressão if-else. A inferência de tipos pode ser usada para omitir a declaração de tipo
explícito para answerString, mas geralmente é uma boa ideia incluí-la para fins de
clareza.

Observação: o Kotlin não inclui um operador ternário tradicional, favorecendo o uso


de expressões condicionais.

Conforme a complexidade da instrução condicional aumenta, é recomendável


substituir a expressão if-else por uma expressão when, conforme mostrado neste
exemplo:

3
val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

Cada ramificação em uma expressão when é representada por uma condição, uma


seta (->) e um resultado. Se a condição no lado esquerdo da seta for avaliada como
verdadeira, o resultado da expressão no lado direito será retornado. Observe que a
execução não passa de uma ramificação para a próxima. O código no exemplo de
expressão when é funcionalmente equivalente ao do exemplo anterior, mas é mais
fácil de ler.

As condicionais do Kotlin destacam um dos recursos mais avançados, a transmissão


inteligente. Em vez de usar o operador de chamada segura ou o operador de
declaração não nulo para trabalhar com valores anuláveis, você pode verificar se uma
variável contém uma referência a um valor nulo usando uma instrução condicional,
conforme mostrado neste exemplo:

val languageName: String? = null


if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

Na ramificação condicional, languageName pode ser tratado como não anulável. O


Kotlin é inteligente o suficiente para reconhecer que a condição para executar a
ramificação é que languageName não contenha um valor nulo. Portanto, você não
precisa tratar languageName como anulável nessa ramificação. Essa transmissão
inteligente funciona para verificações nulas, verificações de tipo ou qualquer condição
que satisfaça a um contrato.

Funções

Você pode agrupar uma ou mais expressões em uma função. Em vez de repetir a


mesma série de expressões sempre que precisar de um resultado, você pode unir as
expressões em uma função e chamar essa função.

Para declarar uma função, use a palavra-chave fun seguida pelo nome da função. Em


seguida, defina os tipos de entrada que sua função assume, se houver, e declare o
tipo de saída retornada. No corpo de uma função, você define expressões que são
chamadas quando sua função é invocada.

Com base nos exemplos anteriores, veja uma função completa do Kotlin:

fun generateAnswerString(): String {


    val answerString = if (count == 42) {
        "I have the answer."

4
    } else {
        "The answer eludes me"
  }

    return answerString
}

A função no exemplo acima tem o nome generateAnswerString. Não é necessária


nenhuma entrada. Ela gera um resultado do tipo String. Para chamar uma função, use
o nome dela, seguido pelo operador de invocação (()). No exemplo abaixo, a
variável answerString é inicializada com o resultado de generateAnswerString().

val answerString = generateAnswerString()

As funções podem receber argumentos como entrada, conforme mostrado neste


exemplo:

fun generateAnswerString(countThreshold: Int): String {


    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
  }

    return answerString
}

Ao declarar uma função, você pode especificar qualquer número de argumentos e os


tipos. No exemplo acima, generateAnswerString() leva um argumento
chamado countThreshold do tipo Int. Dentro da função, você pode se referir ao
argumento usando o nome dele.

Ao chamar essa função, você precisa incluir um argumento nos parênteses da


chamada da função:

val answerString = generateAnswerString(42)

Como simplificar declarações de função

generateAnswerString() é uma função bastante simples. A função declara uma


variável e, em seguida, retorna imediatamente. Quando o resultado de uma única
expressão é retornado de uma função, você pode ignorar a declaração de uma
variável local retornando diretamente o resultado da expressão if-else contida na
função, conforme mostrado neste exemplo:

5
fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
  }
}

Você também pode substituir a palavra-chave de retorno pelo operador de atribuição:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {


        "I have the answer"
    } else {
        "The answer eludes me"
  }

Funções anônimas

Nem todas as funções precisam de um nome. Algumas funções são identificadas mais
diretamente por suas entradas e saídas. Essas funções são chamadas de funções
anônimas. Você pode manter uma referência a uma função anônima usando essa
referência para chamar a função anônima posteriormente. Você também pode passar
a referência no seu app, como acontece com outros tipos de referência.

val stringLengthFunc: (String) -> Int = { input ->


    input.length
}

Assim como as funções nomeadas, as funções anônimas podem conter qualquer


número de expressões. O valor retornado da função é o resultado da expressão final.

No exemplo acima, stringLengthFunc contém uma referência a uma função anônima


que usa um String como entrada e retorna o comprimento da entrada String como
saída do tipo Int. Por esse motivo, o tipo da função é denotado como (String) -> Int. No
entanto, esse código não invoca a função. Para recuperar o resultado da função, você
precisa invocá-lo como faria com uma função nomeada. Forneça um String ao
chamar stringLengthFunc, conforme mostrado neste exemplo:

val stringLengthFunc: (String) -> Int = { input ->


    input.length
}

val stringLength: Int = stringLengthFunc("Android")

Funções de ordem superior

6
Uma função pode usar outra função como um argumento. As funções que usam outras
funções como argumentos são chamadas de funções de ordem superior. Esse padrão
é útil para a comunicação entre componentes, da mesma forma que você pode usar
uma interface de callback em Java.

Veja um exemplo de uma função de ordem superior:

fun stringMapper(str: String, mapper: (String) -> Int): Int {


    // Invoke function
    return mapper(str)
}

A função stringMapper() usa um String junto com uma função que deriva um


valor Int de um String transmitido nela.

Você pode chamar stringMapper() passando um String e uma função que satisfaça o


outro parâmetro de entrada, ou seja, uma função que usa um String como entrada e
gera um Int, conforme mostrado neste exemplo:

stringMapper("Android", { input ->


    input.length
})

Se a função anônima for o último parâmetro definido em uma função, você poderá


passá-la para fora dos parênteses usados para chamar a função, conforme mostrado
neste exemplo:

stringMapper("Android") { input ->


    input.length
}

As funções anônimas podem ser encontradas em toda a biblioteca padrão do Kotlin.


Para mais informações, consulte Funções de ordem superior e lambdas (link em
inglês).

Classes

Todos os tipos mencionados até agora estão integrados à linguagem de programação


Kotlin. Se quiser adicionar um tipo personalizado, você poderá definir uma classe
usando a palavra-chave class, conforme mostrado neste exemplo:

class Car

Propriedades

As classes representam o estado usando propriedades. Uma propriedade é uma


variável de nível de classe que pode incluir um getter, um setter e um campo de

7
backup. Como um carro precisa de rodas para dirigir, você pode adicionar uma lista de
objetos Wheel como uma propriedade de Car, conforme mostrado neste exemplo:

class Car {
    val wheels = listOf<Wheel>()
}

Observe que wheels é um public val, o que significa que wheels pode ser acessado de


fora da classe Car e não pode ser reatribuído. Se você quiser ter uma instância
de Car, primeiro é necessário chamar seu construtor. A partir daí, você pode acessar
qualquer uma das propriedades acessíveis.

val car = Car() // construct a Car


val wheels = car.wheels // retrieve the wheels value from the Car

Se você quiser personalizar suas rodas, poderá definir um construtor personalizado


que especifica como as propriedades de classe são inicializadas:

class Car(val wheels: List<Wheel>)

No exemplo acima, o construtor de classe usa um List<Wheel> como argumento do


construtor e usa esse argumento para inicializar a propriedade wheels.

Funções de classe e encapsulamento

As classes usam funções para modelar o comportamento. As funções podem


modificar o estado, ajudando você a expor somente os dados que quer expor. Esse
controle de acesso faz parte de um conceito maior orientado a objetos, conhecido
como encapsulamento.

No exemplo a seguir, a propriedade doorLock é mantida privada em qualquer item fora


da classe Car. Para desbloquear o carro, você precisa chamar a
função unlockDoor() transmitindo uma chave válida, conforme mostrado neste
exemplo:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {


        // Return true if key is valid for door lock, false otherwise
  }
}

Se quiser personalizar a forma como uma propriedade é referenciada, você poderá


fornecer getter e setter personalizados. Por exemplo, se quiser expor o getter de uma

8
propriedade ao restringir o acesso a setter, você poderá designar esse setter
como private:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15


        private set

    fun unlockDoor(key: Key): Boolean {


        // Return true if key is valid for door lock, false otherwise
  }
}

Com uma combinação de propriedades e funções, você pode criar classes que
modelam todos os tipos de objeto.

Interoperabilidade

Uma das características mais importantes do Kotlin é a interoperabilidade fluida com


Java. Como o código Kotlin é compilado até o bytecode da JVM, seu código Kotlin
pode ser chamado diretamente no código Java e vice versa. Isso significa que você
pode aproveitar bibliotecas Java já existentes diretamente do Kotlin. Além disso, a
maioria das APIs do Android é gravada em Java, e você pode chamá-las diretamente
do Kotlin.

A seguir

Kotlin é uma linguagem flexível e pragmática com suporte e dinâmica crescentes.


Encorajamos você a testá-la se ainda não tiver feito isso. Para as próximas etapas,
confira a documentação oficial do Kotlin (link em inglês) com o guia sobre como
aplicar padrões comuns do Kotlin nos seus apps para Android.

Use padrões comuns do Kotlin com o Android

Este tópico se concentra em alguns dos aspectos mais úteis da linguagem Kotlin ao
desenvolver para o Android.

Trabalhar com fragmentos

As seções a seguir usam exemplos de Fragment para destacar alguns dos melhores


recursos do Kotlin.

9
Herança

Você pode declarar uma classe no Kotlin com a palavra-chave class. No exemplo a


seguir, LoginFragment é uma subclasse de Fragment. Você pode indicar a herança
usando o operador : entre a subclasse e o pai:

class LoginFragment : Fragment()

Nessa declaração de classe, LoginFragment é responsável por chamar o construtor da


superclasse, Fragment.

Dentro de LoginFragment, você pode modificar vários callbacks de ciclo de vida para
responder a mudanças de estado no Fragment. Para substituir uma função, use a
palavra-chave override, conforme mostrado no exemplo a seguir:

override fun onCreateView(


        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

Para fazer referência a uma função na classe pai, use a palavra-chave super,


conforme mostrado no exemplo a seguir:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


    super.onViewCreated(view, savedInstanceState)
}

Nulidade e inicialização

Nos exemplos anteriores, alguns dos parâmetros nos métodos modificados têm tipos
sufixados com um ponto de interrogação ?. Isso indica que os argumentos passados
para esses parâmetros podem ser nulos. Não deixe de usar a proteção contra valores
nulos (link em inglês).

No Kotlin, você precisa inicializar as propriedades ao declarar um objeto. Isso significa


que, quando você tem uma instância de uma classe, é possível referenciar qualquer
uma das propriedades acessíveis imediatamente. Os objetos View em um Fragment,
no entanto, não ficam prontos para ser inflados até que Fragment#onCreateView seja
chamado, então você precisa de uma maneira de adiar a inicialização de propriedade
para um View.

O lateinit permite que você adie a inicialização da propriedade. Ao usar lateinit, você


precisa inicializar sua propriedade assim que possível.

O exemplo a seguir demonstra o uso de lateinit para atribuir


objetos View em onViewCreated:

10
class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText


    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
  }

    ...
}

Observação: se você acessar uma propriedade antes que ela seja inicializada, o
Kotlin lançará um UninitializedPropertyAccessException.

Conversão de SAM

Você pode detectar eventos de cliques no Android implementando a


interface OnClickListener. Os objetos Button contêm uma
função setOnClickListener() que recebe uma implementação de OnClickListener.

OnClickListener tem um único método abstrato, onClick(), que você precisa


implementar. Como setOnClickListener() sempre usa um OnClickListener como
argumento e, como OnClickListener sempre tem o mesmo método abstrato, essa
implementação pode ser representada usando uma função anônima no Kotlin. Esse
processo é conhecido como conversão de método abstrato único (link em inglês)
ou conversão de SAM.

A conversão de SAM pode tornar seu código consideravelmente mais limpo. O


exemplo a seguir mostra como usar a conversão de SAM para implementar
um OnClickListener para um Button:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
  )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
  }

11
}

O código dentro da função anônima passada para setOnClickListener() é executado


quando um usuário clica em loginButton.

Objetos complementares

Os objetos complementares fornecem um mecanismo para definir variáveis ou funções


que são vinculadas conceitualmente a um tipo, mas não a um objeto específico (link
em inglês). Os objetos complementares são similares a usar a palavra-chave static do
Java para variáveis e métodos.

No exemplo a seguir, TAG é uma constante de String. Não é necessária uma instância


única de String para cada instância de LoginFragment, então você precisa defini-la em
um objeto complementar:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
  }
}

Você pode definir TAG no nível superior do arquivo, mas ele também pode ter um
grande número de variáveis, funções e classes que também são definidas no nível
superior. Os objetos complementares ajudam a conectar variáveis, funções e a
definição de classe sem fazer referência a nenhuma instância específica dessa classe.

Delegação de propriedade

Ao inicializar propriedades, você pode repetir alguns dos padrões mais comuns do
Android, como acessar um ViewModel em um Fragment. Para evitar excesso de
código duplicado, você pode usar a sintaxe de delegação de propriedade do Kotlin.

private val viewModel: LoginViewModel by viewModels()

A delegação de propriedade fornece uma implementação comum que você pode


reutilizar em todo o app. O Android KTX fornece alguns representantes de propriedade
para você. viewModels, por exemplo, recupera um ViewModel que está no escopo
do Fragment.

A delegação de propriedade usa reflexão, o que adiciona alguma sobrecarga no


desempenho. A vantagem é uma sintaxe concisa que economiza tempo de
desenvolvimento.

Nulidade

12
O Kotlin fornece regras de nulidade rigorosas que mantêm a segurança de tipo em
todo o app. No Kotlin, as referências a objetos não podem conter valores nulos por
padrão. Para atribuir um valor nulo a uma variável, você precisa declarar um tipo de
variável anulável adicionando ? ao final do tipo base.

Por exemplo, a expressão a seguir é ilegal em Kotlin. name é do tipo String e não é


anulável:

val name: String = null

Para permitir um valor nulo, você precisa usar um tipo String anulável, String?,


conforme mostrado no exemplo a seguir:

val name: String? = null

Interoperabilidade

As regras rígidas do Kotlin tornam seu código mais seguro e conciso. Essas regras
diminuem as chances de haver um NullPointerException que cause falhas no app.
Além disso, elas reduzem o número de verificações de nulos que você precisa fazer
no código.

Muitas vezes, você também precisa chamar um código que não é do Kotlin ao criar um
app para Android, porque a maioria das APIs do Android é criada na linguagem de
programação Java.

Nulidade é uma área-chave em que Java e Kotlin diferem no comportamento. O Java


é menos rigoroso com a sintaxe de nulidade.

Como exemplo, a classe Account tem algumas propriedades, incluindo uma


propriedade String chamada name. O Java não tem as regras de nulidade do Kotlin,
contando com anotações de nulidade opcionais para declarar explicitamente se é
possível atribuir um valor nulo.

Como o framework do Android é criado principalmente em Java, você pode se deparar


com esse cenário ao chamar APIs sem anotações de nulidade.

Tipos de plataforma

Se você usar Kotlin para referenciar um membro name não anotado definido em uma


classe Java Account, o compilador não saberá se String mapeia
para String ou String? em Kotlin. Essa ambiguidade é representada por um tipo de
plataforma, String!.

String! não tem um significado especial para o compilador Kotlin. String! pode


representar String ou String?, e o compilador permite atribuir um valor de qualquer
tipo. Você corre o risco de receber um NullPointerException se representar o tipo
como um String e atribuir um valor nulo.

Para resolver esse problema, use anotações de nulidade sempre que escrever um
código em Java. Essas anotações ajudam desenvolvedores Java e Kotlin.
13
Por exemplo, veja a classe Account como ela é definida em Java:

public class Account implements Parcelable {


    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

Uma das variáveis de membro, accessId, é anotada com @Nullable, indicando que ela


pode conter um valor nulo. O Kotlin então trataria accessId como um String?.

Para indicar que uma variável nunca pode ser nula, use a anotação @NonNull:

public class Account implements Parcelable {


    public final @NonNull String name;
    ...
}

Nesse cenário, name é considerado String não anulável no Kotlin.

As anotações de nulidade são incluídas em todas as novas APIs do Android e em


muitas APIs existentes do Android. Muitas bibliotecas Java adicionaram anotações de
nulidade para oferecer melhor compatibilidade com desenvolvedores Kotlin e Java.

Como lidar com a nulidade

Se não tiver certeza sobre um tipo de Java, considere que ele é anulável. Como
exemplo, o membro name da classe Account não está anotado, então você pode
presumir que ele é String anulável.

Se você quiser cortar name para que o valor não inclua espaços em branco à


esquerda ou à direita, poderá usar a função trim do Kotlin. Você pode cortar com
segurança um String? de algumas maneiras diferentes. Uma dessas formas é usar
o operador de asserção não nulo, !!, conforme mostrado no exemplo a seguir:

val account = Account("name", "type")


val accountName = account.name!!.trim()

O operador !! trata tudo no lado esquerdo como não nulo, portanto, neste caso, você
está tratando name como um String não nulo. Se o resultado da expressão à esquerda
for nulo, seu app lançará um NullPointerException. Esse operador é rápido e fácil, mas
é preciso usá-lo com moderação, porque ele pode reintroduzir instâncias
de NullPointerException no seu código.

Uma opção mais segura é usar o operador safe-call, ?., conforme mostrado no


exemplo a seguir:

14
val account = Account("name", "type")
val accountName = account.name?.trim()

Usando o operador safe-call, se name não for nulo, o resultado de name?.trim() será


um valor de nome sem espaços em branco à esquerda ou à direita. Se name for nulo,
o resultado de name?.trim() será null. Isso significa que o app nunca poderá lançar
um NullPointerException ao executar esta instrução.

Enquanto o operador safe-call salva você de um potencial NullPointerException, ele


passa um valor nulo para a próxima instrução. Em vez disso, você pode gerenciar
casos nulos imediatamente usando um operador Elvis (?:), conforme mostrado no
exemplo a seguir:

val account = Account("name", "type")


val accountName = account.name?.trim() ?: "Default name"

Se o resultado da expressão no lado esquerdo do operador Elvis for nulo, o valor no


lado direito será atribuído a accountName. Essa técnica é útil para fornecer um valor
padrão que seria nulo.

Você também pode usar o operador Elvis para retornar de uma função
antecipadamente, como mostrado no exemplo a seguir:

fun validateAccount(account: Account?) {


    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point


    account ?: return

    ...
}

Mudanças na API do Android

As APIs do Android estão se tornando cada vez mais compatíveis com o Kotlin. Muitas
das APIs mais comuns do Android, incluindo AppCompatActivity e Fragment, contêm
anotações de nulidade, e algumas chamadas como Fragment#getContext têm mais
alternativas otimizadas para Kotlin.

Por exemplo, acessar Context de Fragment quase sempre não é nulo, porque a


maioria das chamadas feitas em Fragment ocorre enquanto Fragment é anexado a
um Activity (uma subclasse de Context). Dito isso, Fragment#getContext não retorna
sempre um valor não nulo, porque há cenários em que um Fragment não é anexado a
um Activity. Assim, o tipo de retorno de Fragment#getContext é anulável.

Como o Context retornado de Fragment#getContext é anulável (e está anotado como


@Nullable), é preciso tratá-lo como um Context? no código Kotlin. Isso significa aplicar
um dos operadores mencionados anteriormente para resolver a nulidade antes de

15
acessar as propriedades e funções. Para alguns desses cenários, o Android contém
APIs alternativas que oferecem essa conveniência. Fragment#requireContext, por
exemplo, retorna um Context não nulo e lança um IllegalStateException se chamado
quando um Context seria nulo. Dessa forma, é possível tratar o Context resultante
como não nulo sem a necessidade de operadores safe-call ou soluções alternativas.

Inicialização de propriedades

Propriedades em Kotlin não são inicializadas por padrão. Elas precisam ser
inicializadas quando a classe envolvida for inicializada.

Você pode inicializar propriedades de algumas maneiras diferentes. O exemplo a


seguir mostra como inicializar uma variável index atribuindo um valor a ela na
declaração de classe:

class LoginFragment : Fragment() {


    val index: Int = 12
}

Essa inicialização também pode ser definida em um bloco inicializador:

class LoginFragment : Fragment() {


    val index: Int

    init {
        index = 12
  }
}

Nos exemplos acima, index é inicializado quando um LoginFragment é construído.

No entanto, você pode ter algumas propriedades que não podem ser inicializadas
durante a construção do objeto. Por exemplo, talvez você queira referenciar um View a
partir de um Fragment, o que significa que o layout precisa ser inflado primeiro. A
inflação não ocorre quando um Fragment é construído. Em vez disso, ele é inflado ao
chamar Fragment#onCreateView.

Uma maneira de lidar com esse cenário é declarar a visualização como anulável e
inicializá-la o mais rápido possível, conforme mostrado no exemplo a seguir:

class LoginFragment : Fragment() {


    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
  }
16
}

Embora funcione como esperado, é preciso gerenciar a nulidade de View sempre que


fizer referência a ela. Uma solução melhor é usar lateinit para a inicialização de View,
como mostrado no exemplo a seguir:

class LoginFragment : Fragment() {


    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
  }
}

A palavra-chave lateinit permite que você evite inicializar uma propriedade quando um


objeto for construído. Se a propriedade for referenciada antes de ser inicializada, o
Kotlin emitirá um UninitializedPropertyAccessException, portanto, inicialize a
propriedade assim que possível.

Observação: soluções de vinculação de visualizações, como a Vinculação de dados,


que removem chamadas manuais para findViewById podem ajudar a reduzir o
número de problemas com a proteção contra valores nulos que você precisa
considerar.

Guia de estilo do Kotlin

Este documento serve como a definição completa dos padrões de codificação do


Google Android para o código-fonte na linguagem de programação Kotlin. Um arquivo
de origem Kotlin é descrito como estando no Estilo do Google Android somente se ele
segue as regras contidas aqui.

Assim como outros guias de estilo de programação, os problemas abordados incluem


não apenas questões estéticas de formatação, mas também outros tipos de
convenções ou padrões de codificação. No entanto, este documento se concentra
principalmente nas regras rígidas que seguimos universalmente e evita dar conselhos
que não sejam claramente aplicáveis (seja por humanos ou ferramentas).

Última atualização: 10/06/2020

Arquivos de origem

Todos os arquivos de origem devem ser codificados como UTF-8.

Nomeação

17
Se um arquivo de origem contiver somente uma única classe de nível superior, o nome
do arquivo precisará refletir o nome que diferencia maiúsculas de minúsculas e a
extensão .kt. Caso contrário, se um arquivo de origem contiver várias declarações de
nível superior, escolha um nome que descreva o conteúdo do arquivo, aplique o
PascalCase e anexe a extensão .kt.

// MyClass.kt
class MyClass { }

// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …

// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …

Caracteres especiais

Caracteres de espaço em branco

Além da sequência de terminador de linha, o caractere de espaço horizontal ASCII


(0x20) é o único caractere de espaço em branco que aparece em qualquer lugar em
um arquivo de origem. Isso significa que:

 todos os outros caracteres de espaços em branco em literais de string e de


caracteres têm escape;
 os caracteres de tabulação não são usados para recuo.

Sequências especiais de escape

Para qualquer caractere que tenha uma sequência de escape especial


(\b, \n, \r, \t, \', \", \\ e \$), essa sequência é usada em vez do escape Unicode
correspondente (por exemplo, \u000a).

Caracteres não ASCII

Para os caracteres não ASCII restantes, será usado o caractere Unicode real (por
exemplo, ∞) ou o escape Unicode equivalente (por exemplo, \u221e). Será escolhido
aquele que torna o código mais fácil de ler e entender. Escapes Unicode não são
recomendados para caracteres de impressão em nenhum local e fora de literais de
string e comentários.

18
Exemplo Discussão

val unitAbbrev = "μs" Melhor: perfeitamente claro, mesmo sem um comentário.

val unitAbbrev = Ruim: não há motivo para usar um escape com um caractere de
"\u03bcs" // μs impressão.

val unitAbbrev = "\u03bcs"` Ruim: o leitor não tem ideia do que seja isso.

return "\ufeff" + content Bom: use escapes para caracteres não imprimíveis e comente,
se necessário.

Estrutura

Um arquivo .kt compreende o seguinte, em ordem:

 Cabeçalho de direitos autorais e/ou licença (opcional)


 Anotações no nível do arquivo
 Declaração do pacote
 Declarações de importação
 Declarações de nível superior

Exatamente uma linha em branco separa cada uma dessas seções.

Direitos autorais/licença

Se um cabeçalho de direitos autorais ou licença pertencer ao arquivo, ele precisará ser


colocado na parte superior imediata de um comentário de várias linhas.

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
 

Não use um estilo KDoc ou um comentário em estilo de linha única.

/**
 * Copyright 2017 Google, Inc.
 *
 * ...

19
 */

// Copyright 2017 Google, Inc.


//
// ...

Anotações no nível do arquivo

As anotações com o "arquivo" de destino de uso do site são colocadas entre qualquer


comentário de cabeçalho e a declaração do pacote.

Declaração do pacote

A declaração do pacote não está sujeita a nenhum limite de colunas e nunca é


preenchida com linhas.

Declarações de importação

As declarações de importação para classes, funções e propriedades são agrupadas


em uma única lista e são classificadas como ASCII.

Importações de caracteres curinga (de qualquer tipo) não são permitidas.

Assim como a declaração do pacote, as declarações de importação não estão sujeitas


a um limite de colunas e nunca são preenchidas com linhas.

Declarações de nível superior

Um arquivo .kt pode declarar um ou mais tipos, funções, propriedades ou aliases de


tipo no nível superior.

O conteúdo de um arquivo precisa ser focado em um único tema. Exemplos disso


seriam um único tipo público ou um conjunto de funções de extensão realizando a
mesma operação em vários tipos de receptor. Declarações não relacionadas precisam
ser separadas nos próprios arquivos, e declarações públicas em um único arquivo
precisam ser minimizadas.

Nenhuma restrição explícita é imposta em relação ao número ou à ordem do conteúdo


de um arquivo.

Os arquivos de origem geralmente são lidos de cima para baixo, o que significa que a
ordem, em geral, precisa refletir que as declarações mais acima informarão a
compreensão das que estão mais abaixo. Arquivos diferentes podem optar por
ordenar os conteúdos de forma diferente. Da mesma forma, um arquivo pode conter
100 propriedades, outras 10 funções e ainda uma única classe.

O importante é que cada classe use alguma ordem lógica, que o mantenedor poderá


explicar, se solicitado. Por exemplo, novas funções não são adicionadas

20
habitualmente ao fim da classe, já que resultariam em ordenação "cronológica por
data de adição", o que não é uma ordem lógica.

Ordenação de membros da classe

A ordem dos membros em uma classe segue as mesmas regras das declarações de
nível superior.

Formatação

Chaves

As chaves não são obrigatórias para ramificações when e corpos de instruções if que


não possuem ramificações else if/else e que se encaixam em uma única linha.

if (string.isEmpty()) return

when (value) {
    0 -> return
    // …
}

As chaves são obrigatórias para qualquer declaração if, for,


ramificação when, do e while, mesmo quando o corpo está vazio ou contém apenas
uma declaração.

if (string.isEmpty())
    return  // WRONG!

if (string.isEmpty()) {
    return  // Okay
}

Blocos não vazios

As chaves seguem o estilo de Kernighan e Ritchie (colchetes egípcios) para blocos


não vazios e construções semelhantes a blocos:

 Nenhuma quebra de linha antes da chave de abertura.


 Quebra de linha após a chave de abertura.
 Quebra de linha antes da chave de fechamento.
 Quebra de linha após a chave de fechamento, somente se essa chave terminar
uma declaração ou encerrar o corpo de uma função, construtor ou classe nomeada.
Por exemplo, não haverá nenhuma quebra de linha depois da chave, se ela for
seguida por else ou por uma vírgula.

21
return Runnable {
    while (condition()) {
        foo()
  }
}

return object : MyClass() {


    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
      }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
    }
  }
}

Algumas exceções para classes de enumeração são fornecidas abaixo.

Blocos vazios

Um bloco vazio ou uma construção parecida com um bloco precisa estar no estilo
K&R.

try {
    doSomething()
} catch (e: Exception) {} // WRONG!

try {
    doSomething()
} catch (e: Exception) {
} // Okay

22
Expressões

Uma condicional if/else que é usada como uma expressão poderá omitir


chaves somente se toda a expressão couber em uma linha.

val value = if (string.isEmpty()) 0 else 1  // Okay

val value = if (string.isEmpty())  // WRONG!


  0
else
  1

val value = if (string.isEmpty()) { // Okay


  0
} else {
  1
}

Recuo

Sempre que um novo bloco ou construção semelhante a um bloco é aberto, o recuo


aumenta em quatro espaços. Quando o bloco termina, o recuo retorna ao nível de
recuo anterior. O nível do recuo se aplica ao código e aos comentários em todo o
bloco.

Uma declaração por linha

Cada declaração é seguida por uma quebra de linha. Pontos e vírgulas não são
usados.

Quebra de linha

O código tem um limite de colunas de 100 caracteres. Exceto conforme indicado


abaixo, qualquer linha que exceda a esse limite precisa ter quebra de linha, conforme
explicado abaixo.

Exceções:

 Não é possível usar linhas que obedecem ao limite de colunas (por exemplo,
um URL longo no KDoc)
 Declarações package e import
 Linhas de comando em um comentário que pode ser recortado e colado em um
shell

23
Onde quebrar

A principal diretiva de quebra de linha é: prefira quebrar em um nível sintático superior.


Além disso:

 Quando uma linha é quebrada em um nome de operador ou de função de


infixo, a quebra aparece depois do nome do operador ou da função de infixo.
 Quando uma linha é quebrada nos seguintes símbolos "semelhantes a
operadores", a quebra aparece antes do símbolo:
 O separador de pontos (., ?.).
 Os dois-pontos duplos de uma referência de membro (::).
 Um nome de método ou construtor permanece anexado ao parêntese de
abertura (() que o segue.
 Uma vírgula (,) permanece anexada ao token que a precede.
 Uma seta lambda (->) permanece anexada à lista de argumentos que a
precede.
Observação: o principal objetivo da quebra de linha é ter um código claro, não
necessariamente um código que se encaixe no menor número de linhas.

Funções

Quando uma assinatura de função não se encaixar em uma única linha, divida cada
declaração de parâmetro na própria linha. Os parâmetros definidos nesse formato
precisam usar um único recuo (+4). O parêntese de fechamento ()) e o tipo de retorno
são colocados na própria linha sem recuo adicional.

fun <T> Iterable<T>.joinToString(


    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    // …
}

Funções de expressão

Quando uma função contém apenas uma única expressão, ela pode ser representada
como uma função de expressão (link em inglês).

override fun toString(): String {


    return "Hey"
}

24
override fun toString(): String = "Hey"

O único momento em que uma função de expressão precisa se ajustar a várias linhas
é quando ela abre um bloco.

fun main() = runBlocking {


  // …
}

Caso contrário, se uma função de expressão aumentar e exigir quebra, use um corpo
de função normal, uma declaração return e regras normais de quebra de expressão.

Propriedades

Quando um inicializador de propriedade não se encaixar em uma única linha, divida


após o sinal de igual (=) e use um recuo.

private val defaultCharset: Charset? =


        EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

As propriedades que declaram uma função get e/ou set precisam ser colocadas cada


uma na própria linha com um recuo normal (+4). Formate-as usando as mesmas
regras das funções.

var directory: File? = null


    set(value) {
        // …
  }

As propriedades somente leitura podem usar uma sintaxe mais curta que caiba em
uma única linha.

val defaultExtension: String get() = "kt"

Espaço em branco

Vertical

Uma única linha em branco é exibida:

 Entre membros consecutivos de uma classe: propriedades, construtores,


funções, classes aninhadas etc.

25
 Exceção: uma linha em branco entre duas propriedades consecutivas
(não tendo outro código entre elas) é opcional. Essas linhas em branco são usadas
conforme necessário para criar agrupamentos lógicos de propriedades e associar
propriedades com a propriedade de backup, se presente.
 Exceção: linhas em branco entre constantes de enumeração são
abordadas abaixo.
 Entre declarações, conforme necessário, para organizar o código em
subseções lógicas.
 Opcionalmente antes da primeira declaração em uma função, antes do primeiro
membro de uma classe ou depois do último membro de uma classe (não incentivado
nem desencorajado).
 Conforme exigido por outras seções deste documento (como a
seção Estrutura).

Várias linhas em branco consecutivas são permitidas, mas não são recomendadas
nem necessárias.

Horizontal

Além de onde exigido pela linguagem ou outras regras de estilo, além de literais,
comentários e KDoc, um único espaço ASCII também aparece somente nos seguintes
locais:

 Separando qualquer palavra reservada, como if, for ou catch de um parêntese


de abertura (() que a segue nessa linha.

// WRONG!
for(i in 0..1) {
}

// Okay
for (i in 0..1) {
}

 Separando qualquer palavra reservada, como else ou catch, de uma chave de


fechamento (}) que a preceda nessa linha.

// WRONG!
}else {
}

// Okay
} else {
}

 Antes de qualquer chave de abertura({).

26
// WRONG!
if (list.isEmpty()){
}

// Okay
if (list.isEmpty()) {
}

 Nos dois lados de qualquer operador binário.

// WRONG!
val two = 1+1

// Okay
val two = 1 + 1

Isso também se aplica aos seguintes símbolos "semelhantes a operadores":

 a seta em uma expressão lambda (->).

// WRONG!
ints.map { value->value.toString() }

// Okay
ints.map { value -> value.toString() }

Mas não:

 os dois pontos (::) de uma referência de membro.

// WRONG!
val toString = Any :: toString

// Okay
val toString = Any::toString

 o separador de pontos (.).

// WRONG
it . toString()

// Okay
it.toString()

 o operador de intervalo (..).

27
// WRONG
 for (i in 1 .. 4) print(i)
 
 // Okay
 for (i in 1..4) print(i)

 Antes de dois pontos (:) somente se usado em uma declaração de classe para
especificar uma classe base ou interfaces, ou quando usado em uma
cláusula where para restrições genéricas (link em inglês).

// WRONG!
class Foo: Runnable

// Okay
class Foo : Runnable

// WRONG
fun <T: Comparable> max(a: T, b: T)

// Okay
fun <T : Comparable> max(a: T, b: T)

// WRONG
fun <T> max(a: T, b: T) where T: Comparable<T>

// Okay
fun <T> max(a: T, b: T) where T : Comparable<T>

 Após uma vírgula (,) ou dois pontos (:).

// WRONG!
val oneAndTwo = listOf(1,2)

// Okay
val oneAndTwo = listOf(1, 2)

// WRONG!
class Foo :Runnable

// Okay
class Foo : Runnable

 Em ambos os lados da barra dupla (//) que começa um comentário de fim de


linha. Aqui, vários espaços são permitidos, mas não obrigatórios.

28
// WRONG!
var debugging = false//disabled by default

// Okay
var debugging = false // disabled by default

Essa regra nunca é interpretada como exigência ou proibição de espaço adicional no


início ou no final de uma linha; ela aborda apenas o espaço interior.

Construções específicas

Classes de enumeração

Uma enumeração sem funções e sem documentação sobre as constantes pode,


opcionalmente, ser formatada como uma única linha.

enum class Answer { YES, NO, MAYBE }

Quando as constantes em uma enumeração são colocadas em linhas separadas, uma


linha em branco não é obrigatória entre elas, exceto no caso em que elas definem um
corpo.

enum class Answer {


    YES,
    NO,

    MAYBE {
        override fun toString() = """¯\_(ツ)_/¯"""
  }
}

Como as classes de enumeração são classes, todas as outras regras para classes de
formatação se aplicam.

Anotações

Anotações de membros ou tipos são colocadas em linhas separadas imediatamente


antes da construção anotada.

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

29
Anotações sem argumentos podem ser colocadas em uma única linha.

@JvmField @Volatile
var disposable: Disposable? = null

Quando uma única anotação sem argumentos estiver presente, ela poderá ser
colocada na mesma linha que a declaração.

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {


    // …
}

A sintaxe @[...] só pode ser usada com um destino de site de uso explícito e somente
para combinar duas ou mais anotações sem argumentos em uma única linha.

@field:[JvmStatic Volatile]
var disposable: Disposable? = null

Tipos implícitos de retorno/propriedade

Se um corpo de função de expressão ou um inicializador de propriedade for um valor


escalar ou o tipo de retorno puder ser claramente inferido a partir do corpo, ele poderá
ser omitido.

override fun toString(): String = "Hey"


// becomes
override fun toString() = "Hey"

private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")


// becomes
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

Ao gravar uma biblioteca, retenha a declaração de tipo explícito quando ela fizer parte
da API public.

Nomeação

Os identificadores usam apenas letras e dígitos ASCII e, em um pequeno número de


casos indicados abaixo, são sublinhados. Assim, cada nome de identificador válido é
correspondido pela expressão regular \w+.

30
Prefixos ou sufixos especiais, como os exibidos nos
exemplos name_, mName, s_name e kName, não são usados, exceto no caso de
propriedades de backup (consulte Propriedades de backup).

Nomes de pacote

Nomes de pacotes são todos minúsculos, com palavras consecutivas simplesmente


concatenadas juntas (sem sublinhados).

// Okay
package com.example.deepspace
// WRONG!
package com.example.deepSpace
// WRONG!
package com.example.deep_space

Nomes de tipo

Nomes de classes são escritos em PascalCase e normalmente são substantivos ou


sintagmas nominais. Por exemplo, Character ou ImmutableList. Nomes de interface
também podem ser substantivos ou sintagmas nominais (por exemplo, List), mas às
vezes podem ser adjetivos ou frases adjetivas (por exemplo, Readable).

As classes de teste são nomeadas começando com o nome da classe que estão
testando e terminando com Test. Por exemplo, HashTest ou HashIntegrationTest.

Nomes de função

Nomes das funções são escritos em camelCase e normalmente são verbos ou frases
com verbo. Por exemplo, sendMessage ou stop.

Os sublinhados podem ser usados em nomes de funções de teste para separar


componentes lógicos do nome.

@Test fun pop_emptyStack() {


    // …
}

Nomes de constante

Nomes de constante usam UPPER_SNAKE_CASE: todas as letras maiúsculas, com


palavras separadas por sublinhados. Mas o que é uma constante, exatamente?

31
Constantes são propriedades val sem nenhuma função get personalizada, cujos
conteúdos são imutáveis e cujas funções não têm efeitos colaterais detectáveis. Isso
inclui tipos imutáveis e coleções imutáveis de tipos imutáveis, bem como escalares e
strings, se marcados como const. Se algum estado observável da instância puder ser
modificado, ele não será uma constante. Apenas tentar não mudar o objeto não é
suficiente.

const val NUMBER = 5


val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

Esses nomes normalmente são substantivos ou sintagmas nominais.

Os valores constantes só podem ser definidos dentro de object ou como uma


declaração de nível superior. Valores que atendam ao requisito de uma constante,
mas são definidos dentro de um class precisam usar um nome não constante.

Constantes que são valores escalares precisam usar o modificador const (link em


inglês).

Nomes não constantes

Nomes não constantes são escritos em camelCase. Eles se aplicam a propriedades


de instâncias, propriedades locais e nomes de parâmetros.

val variable = "var"


val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)
val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2)
val logger = Logger.getLogger(MyClass::class.java.name)
val nonEmptyArray = arrayOf("these", "can", "change")

Esses nomes normalmente são substantivos ou sintagmas nominais.

Propriedades de backup

Quando uma propriedade de backup é necessária, o nome tem que corresponder


exatamente ao da propriedade real, exceto o prefixo com um sublinhado.

private var _table: Map? = null

val table: Map

32
    get() {
        if (_table == null) {
            _table = HashMap()
    }
        return _table ?: throw AssertionError()
  }

Nomes de variáveis de tipo

Cada variável de tipo é nomeada em um de dois estilos:

 Uma única letra maiúscula, opcionalmente seguida por um único número


(como E, T, X, T2)
 Um nome no formulário usado para as classes, seguido pela letra
maiúscula T (como RequestT, FooBarT)

Letras concatenadas

Às vezes, há mais de uma maneira razoável de converter uma frase em inglês em


letras concatenadas, como quando acrônimos ou construções incomuns como "IPv6"
ou "iOS" estão presentes. Para melhorar a previsibilidade, use o seguinte esquema.

Começando com a forma em prosa do nome:

1. Converta a frase em ASCII simples e remova as apóstrofes. Por exemplo, o


"Müller’s algorithm" pode se tornar o "Muellers algorithm”".
2. Divida esse resultado em palavras, dividindo em espaços e qualquer
pontuação restante (normalmente hifens). Recomendado: se alguma palavra já tiver
uma aparência de concatenação convencional de uso comum, divida-a nas partes
constituintes (por exemplo, "AdWords" se torna "ad words"). Uma palavra como "iOS"
não é realmente concatenada; ela desafia qualquer convenção, então essa
recomendação não se aplica.
3. Agora insira minúsculas em tudo (incluindo acrônimos) e siga um destes
procedimentos:
 Coloque em maiúsculas o primeiro caractere de cada palavra para
alternar entre maiúsculas e minúsculas.
 Coloque em maiúscula o primeiro caractere de cada palavra, exceto a
primeira, para produzir a concatenação.
4. Por fim, junte todas as palavras em um único identificador.

A capitalização das palavras originais é praticamente ignorada.

33
Forma de prosa Correta Incorreta

"Solicitação Http XML" XmlHttpRequest XMLHTTPRequest

"novo ID de cliente" newCustomerId newCustomerID

"cronômetro interno" innerStopwatch innerStopWatch

"compatível com IPv6 no iOS" supportsIpv6OnIos supportsIPv6OnIOS

"Importador do YouTube" YouTubeImporter YoutubeImporter*

* Aceitável, mas não recomendado.

Observação: algumas palavras têm hifenização ambígua no idioma inglês. Por


exemplo, "nonempty" e "non-empty" estão corretos, de modo que os nomes dos
métodos checkNonempty e checkNonEmpty também estão corretos.

Documentação

Formatação

A formatação básica de blocos KDoc é vista neste exemplo:

/**
 * Multiple lines of KDoc text are written here,
 * wrapped normally…
 */
fun method(arg: String) {
    // …
}

...ou neste exemplo de linha única:

/** An especially short bit of KDoc. */

A forma básica é sempre aceitável. A forma de uma única linha pode ser substituída
quando a totalidade do bloco KDoc (incluindo marcadores de comentário) couber em
uma única linha. Isso só se aplica quando não há tags de bloco, como @return.

34
Parágrafos

Uma linha em branco, ou seja, uma linha contendo apenas o asterisco inicial alinhado
(*), aparece entre os parágrafos e antes do grupo de tags de bloco, se houver.

Tags de bloco

Qualquer uma das "tags de bloco" padrão que são usadas aparece na
ordem @constructor, @receiver, @param, @property, @return, @throws, @see e
elas nunca aparecem com uma descrição vazia. Quando uma tag de bloco não se
encaixa em uma única linha, as linhas de continuação são recuadas 4 espaços a partir
da posição de @.

Fragmento de resumo

Cada bloco KDoc começa com um breve fragmento de resumo. Esse fragmento é
muito importante: é a única parte do texto que aparece em determinados contextos,
como índices de classe e método.

Isso é um fragmento: uma frase nominal ou uma frase verbal, não uma frase completa.
Não começa com "A `Foo` is a..." ou "This method returns...", nem tem que formar
uma frase imperativa completa como "Save the record.". No entanto, o fragmento é
capitalizado e pontuado como se fosse uma frase completa.

Uso

No mínimo, o KDoc está presente para cada tipo public e todo


membro public ou protected desse tipo, com algumas exceções indicadas abaixo.

Exceção: funções autoexplicativas

O KDoc é opcional para funções “simples e óbvias” como getFoo e propriedades


como foo, casos em que realmente não vale a pena dizer nada.

Não é apropriado citar essa exceção para justificar a omissão de informações


relevantes que um típico leitor talvez precise saber. Por exemplo, para uma função
denominada getCanonicalName ou propriedade denominada canonicalName, não
omita a documentação (com a justificativa de que diria apenas /** Returns the
canonical name. */) se um leitor comum não tiver ideia do que o termo "nome
canônico" significa.

Exceção: substituições

O KDoc nem sempre está presente em um método que substitui um método de


supertipo.

35

Você também pode gostar