Deixe de usar lateinit — Faça o uso ser nulo em vez disso
Por que deixar o compilador pegar seus erros sempre é melhor do que encontrar em produção
“Você está depurando um crash em produção às 2 da manhã. A pilha de exceção aponta para uma propriedade lateinit que nunca foi inicializada. Parece familiar? Aqui está como aprendi a prevenir completamente esse tipo de bugs.”
kotlin.UninitializedPropertyAccessException:
lateinit property variable has not been initialized
at com.example.MainActivity.onResume(MainActivity.kt:25)Neste blog, vamos comparar essas duas exceções: Exceção de ponteiro nulo e Exceção de acesso não inicializado do Kotlin.
O que é uma exceção de ponteiro nulo?
Desde que começamos a escrever código, seja em Java ou Kotlin, teríamos encontrado uma exceção de ponteiro nulo. Em termos simples, se uma propriedade ou variável de tipos como String, Object etc. recebe um valor null no momento da sua acessibilidade, o aplicativo irá falhar devido a uma exceção de ponteiro nulo.
O que é uma exceção de acesso a propriedade não inicializada?
Nós não teríamos encontrado essa exceção enquanto desenvolvíamos aplicativos Android em Java. Esta é uma exceção específica da linguagem Kotlin. Encontraremos esta exceção quando uma propriedade ou variável não foi inicializada com nenhum valor. Posteriormente, ao tentar acessá-la, o aplicativo irá falhar devido a uma exceção de acesso a propriedade não inicializada. E isso se aplica a variáveis ou propriedades lateinit.
Como o Kotlin lida com NullPointerException?
Em Java, podemos atribuir um valor null a uma variável.
String variable = null;O compilador não mostrará nenhum erro para o código acima, mesmo ao tentar acessá-lo como abaixo
if (variable.isEmpty()) {
System.out.println("Hi");
}Mas a segurança de nulo do Kotlin é uma característica importante, e por isso não podemos simplesmente atribuir um valor null a uma variável quando especificamos explicitamente o tipo como String ou Int como abaixo
var variável: String = null // Erro do compilador
ou
var variável: Int = null // Erro do compiladorVamos ver um erro Null não pode ser um valor do tipo não nulo ‘String’, ou Null não pode ser um valor do tipo não nulo ‘Int’. Para resolver isso, temos que tornar a propriedade nullable usando o operador de chamada segura (?)
var variável: String? = nullDepois disso, se tentarmos acessar alguns dos métodos da variável String nullable, devemos usar chamada segura (?) como
variável?.length
variável?.isEmpty()em vez de
variável.length
variável.isEmpty()O que acontece no caso de lateinit?
Agora, vamos ver o que pode passar despercebido quando escrevemos uma variável como lateinit. O lateinit permite adiar a inicialização da propriedade. Ao usar lateinit, você deve inicializar sua propriedade o mais rápido possível. Não pode ter uma inicialização de valor nulo
Referência: https://developer.android.com/kotlin/common-patterns
class MainActivity: Activity() {
lateinit var variável: String // Correto
lateinit var variável: String?
ou // Erro do compilador
lateinit var variável: String? = null
}Não queremos inicializar a propriedade durante a declaração e queremos atribuí-la mais tarde em onCreate() ou qualquer outro método. É aí que usamos lateinit. Mas suponha que esqueçamos de inicializar um valor para variável em onCreate() e acessá-lo durante onResume() como
override fun onResume() {
super.onResume()
if (variável.length == 3) {
print("Hi")
}
}De acordo com a ordem de execução no ciclo de vida da atividade, onCreate() é chamado primeiro e depois onResume(), então se a variável for acessada sem nenhum valor inicializado para ela, teremos uma exceção de acesso à propriedade não inicializada. Portanto, precisaremos adicionar um guard antes de acessar a variável como abaixo
Como foi minha abordagem para corrigir a exceção causada por lateinit?
Se eu vi uma propriedade lateinit em código existente e recebi a exceção de acesso não inicializado ao tentar acessá-la, comecei a adicionar uma condição de verificação isInitialized na linha de código onde o problema ocorreu.
Mas aqui está a pegadinha:
Adicionar isInitialized pode parecer simples, mas há a chance de esquecer ou deixar de adicionar a condição, o que levará ao crash do aplicativo.
Como os revisores de meu código sugeriram uma alternativa melhor?
Normalmente, corrigimos o código e pedimos aos revisores para revisá-lo. Durante uma dessas instâncias, recebi um comentário
Get Dilipchandar’s stories in your inbox
Pergunta 1: Por que você não pode tornar a propriedade nula em vez de lateinit?
Minha resposta: Adicionar uma guarda isInitialized onde o problema ocorreu é uma alteração de uma linha de código. Se eu tornar a variável nula, terei que adicionar um operador de chamada segura (?) ou um operador de afirmação não nula (!!) em vários lugares onde essa variável foi usada.
Pergunta 2: Mas e se um novo desenvolvedor entrar e ele esquecer de adicionar a condição isInitialized novamente quando o problema for repetido mais tarde? Você não pode aproveitar a vantagem do erro do compilador em caso de propriedades nulas?
Começou a fazer sentido para mim. Quando faço uma propriedade nula, terei que usar ? ou !! pois o compilador lança um erro. Então não há chance de esquecer, já que podemos rodar o aplicativo apenas após corrigir o erro do compilador. E é sempre melhor usar o operador de chamada segura (?) do que o operador de afirmação não nula (!!) pois este último pode causar crash do aplicativo se o valor for nulo.
// ❌ Perigoso — contraria o propósito de nullable
variable!!.length // cai se for null, igual a lateinit
// ✅ Seguro — compilador exige tratamento
variable?.length // retorna null em vez de cairMas se eu usar lateinit, esquecer a inicialização do valor e acessá-lo posteriormente sem verificações adequadas (isInitialized), o compilador não mostrará nenhum erro. Portanto, um pode ser corrigido durante a compilação, enquanto o outro não.
Quando é aceitável usar lateinit?
lateinit ainda é apropriado para
- View binding / dependency injection
@Inject lateinit var viewModel: MyViewModel- Propriedades inicializadas em onCreate() que são garantidas para serem definidas antes de qualquer acesso
lateinit var binding: ActivityMainBinding
override fun onCreate(...) {
binding = ActivityMainBinding.inflate(layoutInflater)
} Princípio-chave:
A regra de ouro é simples: —
- Use nullable (?) quando uma propriedade pode realmente não ter um valor
- Use lateinit apenas quando você puder garantir a inicialização antes do primeiro acesso
- Nunca use !! a menos que você esteja certo de que o valor não pode ser null.
Deixe o compilador ser sua rede de segurança — está sempre disponível, ao contrário da sessão de depuração às 2h da manhã.

