如何解决与 retrofit2 相关的 NullPointerException。DefaultCallAdapterFactory$ExecutorCallbackCall

How do I resolve this NullPointerException related to retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall

提问人:Soham Chougule 提问时间:10/27/2023 最后编辑:Brian Tompsett - 汤莱恩Soham Chougule 更新时间:10/27/2023 访问量:36

问:

我正在尝试制作一个 Pokedex 应用程序,该应用程序使用 retrofit2 从 API 获取口袋妖怪数据。 当我尝试运行它时,它会记录以下错误

FATAL EXCEPTION: main                                                                                                
Process: com.example.pokemonapp, PID: 10766                                                                                            
java.lang.NullPointerException: Parameter specified as non-null is null: method com.example.pokemonapp.PokeAdapter.<init>, parameter pokes                                                                                      
    at com.example.pokemonapp.PokeAdapter.<init>(Unknown Source:7)                                                                                          
    at com.example.pokemonapp.MainActivity$getPokemon$1.onResponse(MainActivity.kt:31)                                                                                          
    at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$retrofit2-DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89)                                                                                             
    at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1$$ExternalSyntheticLambda0.run(Unknown Source:6)                                                                                               
    at android.os.Handler.handleCallback(Handler.java:958)                                                                                                  
    at android.os.Handler.dispatchMessage(Handler.java:99)                                                                                                      
    at android.os.Looper.loopOnce(Looper.java:205)                                                                                                          
    at android.os.Looper.loop(Looper.java:294)                                                                                                              
    at android.app.ActivityThread.main(ActivityThread.java:8177)                                                                                                                    
    at java.lang.reflect.Method.invoke(Native Method)                                                                                                                       
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)                                                                                                                            
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

MainActivity.kt (英语)

class MainActivity : AppCompatActivity() {
    lateinit var adapter: PokeAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getPokemon()
    }

    private fun getPokemon() {
            val pokeball: Call<PokeApiResponse> = PokeInterface.ApiService.pokeInstance.getPokemonList()
            pokeball.enqueue(object: Callback<PokeApiResponse> {
                override fun onResponse(
                    call: Call<PokeApiResponse>,
                    response: Response<PokeApiResponse>
                ) {
                    val pokedex = response.body()
                    if(pokedex!= null)
                    {
                        Log.d("CHECK", pokeball.toString())
                        adapter = PokeAdapter(this@MainActivity, pokedex.pokemons)
                        val pokeList = findViewById<RecyclerView>(R.id.pokemonList)
                        pokeList.adapter = adapter
                        pokeList.layoutManager = LinearLayoutManager(this@MainActivity)
                    }
                }


                override fun onFailure(call: Call<PokeApiResponse>, t: Throwable) {
                    Log.d("CHECK", "Error in fetching Pokemon", t)
                }
            })
        }
    }

ApiService.kt 接口

const val BASE_URL = "https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/"

interface PokeInterface{
    @GET("pokedex.json")
    fun getPokemonList() : Call<PokeApiResponse>

    //https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json

    object ApiService{
        val pokeInstance: PokeInterface
        init{
            val retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            pokeInstance = retrofit.create(PokeInterface::class.java)
        }
    }
}

PokeApiResponse.kt (英语)

data class PokeApiResponse(
    val pokemons: List<Poke>
)

戳.kt

data class Poke(
    val name: String,
    val img: String
)

PokeAdapter.kt (英语)

class PokeAdapter(val context: Context, val pokes: List<Poke>): RecyclerView.Adapter<PokeAdapter.PokemonViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false)
        return PokemonViewHolder(view)
    }

    override fun getItemCount(): Int {
        return pokes.size
    }

    override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) {
        val pokemon = pokes[position]
        holder.pokeTitle.text = pokemon.name
    }

    class PokemonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var pokeImage = itemView.findViewById<ImageView>(R.id.pokeImage)
        var pokeTitle = itemView.findViewById<TextView>(R.id.pokeName)
    }
}

build.gradle

implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")

在 MainActivity.kt 中,我尝试记录使用标签“CHECK”获取的数据,它记录了以下消息:

改造2.DefaultCallAdapterFactory$ExecutorCallbackCall@6d104a9

早些时候,当我没有实现时,改造可以正确获取数据,并且获取的数据也正确记录在标签“CHECK”下,但是现在它一直在给出消息。RecyclerViewDefaultCallAdapterFactory$ExecutorCallbackCall

我花了几个小时试图在网上找到解决方案,但没有任何效果。 在这方面的任何帮助将不胜感激。

Android REST Kotlin 改造2

评论


答:

0赞 Gastón Saillén 10/27/2023 #1

我认为 NPE 可能来自您的日志

override fun onResponse(call: Call<PokeApiResponse>, response: Response<PokeApiResponse>) {
    val pokedex = response.body()
    if (pokedex != null) {
        Log.d("CHECK", "Received ${pokedex.pokemons.size} Pokemon")
        adapter = PokeAdapter(this@MainActivity, pokedex.pokemons)
        val pokeList = findViewById<RecyclerView>(R.id.pokemonList)
        pokeList.adapter = adapter
        pokeList.layoutManager = LinearLayoutManager(this@MainActivity)
    }
}

尝试记录 pokedex 列表而不是 pokeball

请尝试按照您的 json 建议使用此数组名称

data class PokeApiResponse(
    @SerializedName("pokemon")
    val pokemons: List<Poke>
)

评论

0赞 Soham Chougule 10/27/2023
我试图按照您的建议记录 pokedex 列表,但现在它根本没有在该标签下记录任何内容。该错误似乎也已更改为不同的 NullPointerException:java.lang.NullPointerException:尝试在 retrofit2 的 com.example.pokemonapp.MainActivity$getPokemon$1.onResponse(MainActivity.kt:31) 的空对象引用上调用接口方法“int java.util.List.size()”。DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$retrofit2-DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89) . . .
0赞 Gastón Saillén 10/27/2023
那是因为 Pokemon 是 null,请在日志行处使用断点检查什么是 null 并评估
0赞 Gastón Saillén 10/27/2023
我添加了额外的代码安全
0赞 Moonbloom 10/27/2023 #2

您的问题可能是以下 2 种情况中的 1 种(也可能是 2 种情况的混合)

1)
GSON 库不考虑 Kotlin 的可空性。

您的模型中有一个名为 的列表。
此列表标记为非 null。
PokeApiResponsepokemons

但是,当 GSON 解析 JSON 数据时,JSON 响应不包含具有该名称的列表,然后 GSON 只需将列表的值分配给 null。
它可以做到这一点,因为它在内部使用 Java Reflection 来访问该字段。
pokemon

然后,当您在 Activity 中获取响应并尝试访问它时,该字段将为 null,因此您会得到 NullPointerException。
根据您的代码和模型,这应该是不可能的。但是由于 GSON 使用 Java Reflection,因此这是可能的。这是GSON不是一个好用的库的主要原因。这很容易,但也充满了这样的主要缺点。

我个人花了几周时间在我们的项目中完全摆脱了 GSON,并用 Moshi (https://github.com/square/moshi) 取而代之,它将以更好和可控的方式处理此类情况。

2) 如果您在文件中设置了 to,它将混淆您的代码。
这可能会导致在代码中调用的列表在编译的代码中被调用。
然后,当 GSON 想要将 JSON 数据解析到您的模型时,JSON 数据有一个名为 的列表,但您的模型列表被调用,因此它无法正确解析它。
minifyEnabledtruebuild.gradlepokemonsapokemonsa

若要解决此问题,请添加到模型(以及所有模型中的所有字段)@SerializedName("pokemons")

data class PokeApiResponse(
    @SerializedName("pokemons")
    val pokemons: List<Poke>
)