1
resposta

[Bug] [Bug] Minha classe InfoJogo não está funcionando como o esperado

Conforme apresentado no curso, o desenvolvimento da classe InfoJogo é efetuada deste modo:

package br.com.indieBase

class InfoJogo(val info:Jogo) { override fun toString(): String { return info.toString() } }

e mencionado na classe main assim:

package br.com.indieBase

import com.google.gson.Gson import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse.BodyHandlers

fun main() {

val client: HttpClient = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.cheapshark.com/api/1.0/games?ids=128%2C129%2C130"))
    .build()

val response = client
    .send(request, BodyHandlers.ofString())

val json = response.body()
println(json)

val gson = Gson()
val meuJogo = gson.fromJson(json, InfoJogo::class.java)

println(meuJogo)

}

no entando ao tentar rodar o código normalmente como abordado no curso, obtenho seguinte resultado no console:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "br.com.indieBase.Jogo.toString()" because "this.info" is null at br.com.indieBase.InfoJogo.toString(InfoJogo.kt:5) at java.base/java.lang.String.valueOf(String.java:4220) at java.base/java.io.PrintStream.println(PrintStream.java:1047) at br.com.indieBase.MainKt.main(Main.kt:26) at br.com.indieBase.MainKt.main(Main.kt)

1 resposta

Olá, Victor!

Este problema se parece um pouco com este aqui.

Está dando NullPointerException porque a sua data class está diferente do json que está sendo retornado.

Na aula, a estrutura esperada é esta aqui:

"info": {
    "title": "BioShock",
    "steamAppID": null,
    "thumb": "https://sttc.gamersgate.com/images/product/bioshock/cover-180-b0e244.jpg"
  },

Ou seja, ele espera um info, e dentro dele, precisa ter um title, um steamAppID e uma thumb.

Então você deve criar duas data class, uma classe para sinalizar o info e outra para sinalizar title, steamAppID e thumb, logo elas deveriam ser escritas assim:

InfoJogo

data class InfoJogo(val info: InfoApiShark) {
    override fun toString(): String {
        return info.toString()
    }
}

InfoApiShark

data class InfoApiShark(val title:String, val thumb:String, val steamAppID: String)

Na aula a professora não colocou o steamAppID, porque é uma informação que não era interessante para ela no momento, tanto que no exemplo do Bioshock ele vem nulo.

Essa explicação toda faz sentido quando a sua API é esta:

https://www.cheapshark.com/api/1.0/games?id=128

Esta API de cima, onde tem o endpoint /games?id={id} foi a que foi usada na aula, usando somente o id para a busca, já a sua que você menciona no tópico, é diferente, que é esta:

https://www.cheapshark.com/api/1.0/games?ids=128%2C129%2C130`

Note que uma usa id e outra usa ids, então o resultado será diferente, logo vai ter uma estrutura diferente. PS.: Para quem está lendo e ficou confuso com a URL, o "%2C" que se encontra no parâmetro, é uma codificação para vírgula.

Sendo assim, sabemos que um endpoint retorna um valor único de um jogo e outro endpoint retorna o valor de três jogos (128, 129, 130), então precisa ter uma estrutura de lista ou similar.

{
  "128": {
    "info": {
      "title": "BioShock",
      "steamAppID": null,
      "thumb": "https://sttc.gamersgate.com/images/product/bioshock/cover-180-b0e244.jpg"
    }
  },
  "129": {
    "info": {
      "title": "Red Orchestra 2: Heroes of Stalingrad",
      "steamAppID": null,
      "thumb": "https://sttc.gamersgate.com/images/product/red-orchestra-2-heroes-of-stalingrad/cover-180-f8237a.jpg"
    }
  },
  "130": {
    "info": {
      "title": "Renegade Ops",
      "steamAppID": "99300",
      "thumb": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/99300/capsule_sm_120.jpg?t=1614779423"
    }
  }
}

Então para solucionar o erro, há duas possibilidades:

1º Possibilidade: Trocar o endpoint de ids para id:

val client: HttpClient = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.cheapshark.com/api/1.0/games?id=128"))
    .build()
/*Mesmo código*/

Esta eu acho melhor, porque você tem um maior controle dos dados que está vindo.

2º Possibilidade: Fazer uma nova classe de dados:

Como foi citado anteriormente neste post, a classe tem que ter os mesmos atributos do json, como este novo retorno tem 128, 129 e 130, a classe também tem que ter EXATAMENTE estes mesmos nomes:

data class JogoResponse(
    val `128`: InfoJogo,
    val `129`: InfoJogo,
    val `130`: InfoJogo
)

Isso considerando que o InfoJogo que já foi criado e dentro dele tem as outras propriedades como info e dentro de info aquelas outras três: title, thumb, steamAppID. Em outras palavras, na InfoJogo não se toca.

Agora que temos esta "entidade principal", podemos chamar ela na main:

/*
Mesmo código
*/
val meuJogo = gson.fromJson(json, JogoResponse::class.java)

println(meuJogo)

A InfoJogo continua existindo, pois a JogoResponse utiliza ela, mas note que na geração da variável meuJogo, eu troquei o tipo para JogoResponse, que é exatamente o retorno do json.

Agora qual é o grande problema desta segunda possibilidade: como foi visto, eu tive que colocar explicitamente os valores "128", "129" e "130".

Aí tem a terceira possibilidade (é eu sei... eu sou um grande brincalhão). Com o teu mesmo endpoint, é só adicionar &format=array no final.

Terceira Possibilidade:

Trocar o endpoint para:

https://www.cheapshark.com/api/1.0/games?ids=128%2C129%2C130&format=array

Desta forma, ele vai vir como array, ao invés de chave e valor. Neste caso, só vai ter mais uma mudança, que é na tipagem declarada no gson:

/*
Mesmo código
*/
val gson = Gson()
val meuJogo = gson.fromJson(json, Array<InfoJogo>::class.java)
println(meuJogo)

Bom, acredito que seja isso. Espero ter explicado direitinho.

Bom código!