Situações que NullPointerException podem acontecer no Kotlin
Tags: kotlin, programming
Published: 2023-06-16 11:23:00

Uma das motivações de usar o Kotlin é por ser uma linguagem segura, muito por conta da segurança a referências nulas ou [Null Safety](https://kotlinlang.org/docs/null-safety.html). 

De uma forma resumida, é a habilidade para evitar que apps escritos em Kotlin lancem a famosa [`NullPointerException`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-null-pointer-exception/) ou NPE. Para isso, a linguagem oferece o suporte a variáveis/parâmetros que podem ou não serem nulos.

Tipos que podem ser nulos (nullables) são identificados com o sufixo `?`, como por exemplo `String?`, `Int?` etc e isso significa que para chamar qualquer membro de um nullable, seja propriedade ou método, precisamos garantir que ele não é nulo.

## Null Safety no Kotlin

O Kotlin oferece uma série de recursos para lidar com nullables, seja por condições com `if`, chamada segura, operador elvis e outras possibilidades que você pode [conferir na página da documentação sobre null safety](https://kotlinlang.org/docs/null-safety.html#nullable-types-and-non-null-types).

Embora exista diversas opções para escrever códigos que nos protejam do NPE, nem sempre temos essaa garantia!

> _"Como assim, Alex? Me explica melhor isso ai..."_

Pois é, a partir do momento que a nossa aplicação precisa realizar alguma integração, o risco de NPE aumenta!

## Reflexão sobre o uso de nullables

O primeiro caso comum é o uso de código escrito em Java no Kotlin, pois não tem um suporte rigoroso como o do Kotlin que nos indique se variáveis, parâmetros ou retornos podem ser nulos.

Em outras palavras, se usarmos um código Java que tenha a possibilidade de retornar `null`, temos o risco de receber um NPE! Vamos seguir com uma demonstração, criando um modelo para representar um usuário:

```java
public class User {
    private final int id;
    private final String userName;

    User(Integer id, String userName) {
        this.id = id;
        this.userName = userName;
    }

    public Integer getId() {
        return id;
    }

    public String getUserName() {
        return userName;
    }
}
```

Então, vamos implementar um gerenciador de usuário que permite buscar usuários criados a partir de um id:

```java
import java.util.stream.Stream;

public class UserManager {

    public User findUserById(int userId) {
        var userFound = Stream.of(
                        new User(1, "alex"),
                        new User(2, "felipe"))
                .filter(user -> user.getId() == userId)
                .findFirst();
        return userFound.orElse(null);
    }

}
```

Observe que o código devolve `null` caso o usuário não for encontrado. Ao testar esse código em Kotlin com um id que não existe:

```kotlin
println(
    UserManager()
        .findUserById(3)
        .userName
)
```

![Mensagem da stack trace indicando o lançamento de um NullPointerException justificando que não pode chamar getUserName() porque o retorno foi null](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1i7041e7a38ovzds0upa.png)

Pois é, temos um NPE! E não para por aqui, qualquer código de integração que envolva fazer uma conversão, buscar dados etc, pode resultar em um NPE se não utilizarmos nullables.

Portanto, utilizou um código Java no Kotlin, ou fez uma integração com banco de dados ou API que precisa fazer uma busca, integração etc, considere o uso de nullables mesmo que a interface de comunicação não indique o problema!

## Evitando NPE com nullables

Vamos verificar como ficam os códigos utilizando nullables:

```kotlin
val user: User? = UserManager().findUserById(3)
println(
    user?.userName
)
```

Com apenas essa modificação simples, você diminui a fragilidade da sua aplicação a qualquer mudança que ocorra. 

Um caso comum em códigos de integração é o processo de parsing de objetos, como por exemplo, um [DTO](https://en.wikipedia.org/wiki/Data_transfer_object). A implementação desse tipo de objeto seria:

```kotlin
data class UserDTO(
    val id: Int?,
    val userName: String?
)
```

E sim, para cada propriedade nova, você deve considerá-la um nullable! E por fim, você pode oferecer métodos de conversão, como por exemplo, um `toUser()`:

```kotlin
data class UserDTO(
    val id: Int?,
    val userName: String?
) {
    fun toUser(): User = User(
        id ?: 0,
        userName ?: ""
    )
}
```

A regra de preenchimento de valores padrão você pode personalizar da maneira que preferir, até mesmo pode lançar exceptions caso não receba valores necessário, como por exemplo, o id:

```kotlin
data class UserDTO(
    val id: Int?,
    val userName: String?
) {
    fun toUser(): User = User(
        id ?: throw IllegalStateException("usuário deve conter um id"),
        userName ?: ""
    )
}
```

O que você achou desses pontos em relação a referências nulas no Kotlin? Tem outras dicas ou práticas que aplica no seu dia a dia? Aproveite para compartilhar nos comentários.