You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit接收API数据但List为空,触发空指针异常求助

问题描述

我在个人项目中尝试通过Retrofit从API获取用户数据,但每次访问数据List时发现它为空。当我在ViewModel中调用类似users.value.get(0).name的代码时,应用会崩溃,抛出空指针异常。相关代码如下:

PurpleTeapotDatingApiService.kt

package com.example.purpleteapotdating.API

import com.example.redteapotdating.models.Users
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

private const val BASE_URL = "https://w1yeg.wiremockapi.cloud"

/**
 * Building a Moshi object with a Kotlin adapter factory that retrofit will be using
 */

private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()


/**
 * Building the retrofit object with the moshi converter
 */

private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()



interface PurpleTeapotDatingApiService {
    @GET("users")
    suspend fun getUsers(): List<Users>
}

object PurpleTeapotDatingAPI{
    val retrofitService: PurpleTeapotDatingApiService by lazy { retrofit.create(PurpleTeapotDatingApiService::class.java) }
}

Users 模型

data class Users (
    @Json(name = "id")val id: Int,
    @Json(name = "name")val name: String,
    @Json(name = "photo")val photo: String?,
    @Json(name = "gender")val gender: String?,
    @Json(name = "about")val about: String?,
    @Json(name = "school")val school: String?,
    @Json(name = "hobbies") val hobbies: List<String>?
)

MainActivity

package com.example.purpleteapotdating.view

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
import com.example.purpleteapotdating.R

class MainActivity : AppCompatActivity(R.layout.activity_main) {


    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //Retrieve the navController from the NavHost Fragment
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController

        //Set up the action bar for use with the NavController
        setupActionBarWithNavController(navController)
    }

    /**
     * Handles the navigation when the user chooses back from the action bar
     */
    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }
}

PurpleTeapotDatingFragment.kt

package com.example.purpleteapotdating.view

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.example.purpleteapotdating.databinding.FragmentDatingBinding
import com.example.purpleteapotdating.viewmodel.PurpleTeapotDatingViewModel
import com.example.redteapotdating.models.Users

class PurpleTeapotDatingFragment: Fragment() {
    
    private var binding: FragmentDatingBinding? = null
    private val viewModel: PurpleTeapotDatingViewModel by activityViewModels()

    private lateinit var usersList: List<Users>

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val fragmentBinding = FragmentDatingBinding.inflate(inflater,container,false)
        binding = fragmentBinding
        return fragmentBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding?.viewModel = viewModel
        binding?.datingFragment = this
        binding?.lifecycleOwner = this
    }

    override fun onDestroyView() {
        super.onDestroyView()
        binding = null
    }
}

PurpleTeapotDatingViewModel

package com.example.purpleteapotdating.viewmodel

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.purpleteapotdating.API.PurpleTeapotDatingAPI
import com.example.redteapotdating.models.Users
import kotlinx.coroutines.launch

class PurpleTeapotDatingViewModel: ViewModel() {

    //internal mutable data
    private val _users = MutableLiveData<List<Users>>()

    //external immutable data
    val users: LiveData<List<Users>> = _users

    init {
        getUsers()
    }

    private fun getUsers()
    {
        viewModelScope.launch {
            Log.v("getUsers()", "Loading...")
            try {
                _users.value = PurpleTeapotDatingAPI.retrofitService.getUsers()
                Log.v("getUsers()", "Success!!")
            } catch (e: Exception) {
                Log.e("getUsers()","Failure: ${e.message}")
                _users.value = listOf()
            }
        }
    }
}

fragment_dating.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.example.purpleteapotdating.viewmodel.PurpleTeapotDatingViewModel" />
        <variable
            name="datingFragment"
            type="com.example.purpleteapotdating.view.PurpleTeapotDatingFragment" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/name"
                android:layout_marginBottom="16dp"
                android:textAppearance="?attr/textAppearanceHeadline3"
                android:textColor="@color/material_on_background_emphasis_medium"
                tools:text="Jim"/>

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/image"
                android:layout_marginBottom="16dp"
                tools:src="@tools:sample/avatars"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id = "@+id/genderLayout"
                android:layout_marginBottom="16dp"
                android:orientation="vertical">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/gender"
                    android:textAppearance="?attr/textAppearanceHeadline4"
                    android:layout_marginBottom="8dp"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="Gender" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/genderIdentity"
                    android:textAppearance="?attr/textAppearanceHeadline6"
                    android:layout_marginBottom="8dp"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="Male" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:layout_marginBottom="16dp"
                android:id="@+id/aboutLayout">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/about"
                    android:textAppearance="?attr/textAppearanceHeadline4"
                    android:layout_marginBottom="8dp"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="About" />

                <TextView
                    android:id="@+id/AboutMe"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="8dp"
                    android:textAppearance="?attr/textAppearanceHeadline6"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="i like turtelz :)" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:id="@+id/schoolLayout"
                android:layout_marginBottom="16dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/school"
                    android:layout_marginBottom="8dp"
                    android:textAppearance="?attr/textAppearanceHeadline4"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="School" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id = "@+id/schoolName"
                    android:layout_marginBottom="8dp"
                    tools:text="John Jay college"
                    android:textAppearance="?attr/textAppearanceHeadline6"
                    android:textColor="@color/material_on_background_emphasis_medium" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:layout_marginBottom="16dp"
                android:id="@+id/hobbiesLayout">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/hobbies"
                    android:textAppearance="?attr/textAppearanceHeadline4"
                    android:layout_marginBottom="8dp"
                    android:textColor="@color/material_on_background_emphasis_medium"
                    tools:text="Hobbies" />

                <androidx.recyclerview.widget.RecyclerView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/hobbiesList"
                    android:layout_marginBottom="8dp"
                    android:text=""
                    android:textAppearance="?attr/textAppearanceHeadline6"
                    android:textColor="@color/material_on_background_emphasis_medium" />
            </LinearLayout>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="end"
                tools:text="next">
            </Button>


        </LinearLayout>

    </ScrollView>

</layout>
问题分析与解决方案

1. LiveData初始值为空触发空指针

你的_users是MutableLiveData<List<Users>>,默认初始值为null。API请求是异步操作,当你直接调用users.value.get(0)时,可能请求还未完成,users.value仍为null,直接崩溃。

修复:给_users设置初始空列表,避免null:

private val _users = MutableLiveData<List<Users>>(emptyList())

2. 未观察LiveData变化,无法获取最新数据

Fragment中没有监听users的变化,即使API请求成功,也无法触发UI更新,直接访问users.value拿到的可能还是初始值。

修复:在Fragment的onViewCreated中添加LiveData观察者:

viewModel.users.observe(viewLifecycleOwner) { userList ->
    usersList = userList
    // 安全获取第一个用户,避免列表为空时崩溃
    userList.firstOrNull()?.let { user ->
        binding?.name?.text = user.name
        // 其他UI控件更新逻辑
    }
}

3. XML布局未绑定LiveData且未做空安全处理

当前布局未将ViewModel数据绑定到UI,即使绑定也会因为初始空列表崩溃,需要添加空安全判断。

修复:修改XML中控件的绑定逻辑,添加空安全和默认值:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/name"
    android:layout_marginBottom="16dp"
    android:textAppearance="?attr/textAppearanceHeadline3"
    android:textColor="@color/material_on_background_emphasis_medium"
    android:text="@{viewModel.users.size() > 0 ? viewModel.users[0].name : `加载中...`}"
    tools:text="Jim"/>

4. 检查API响应格式与Model是否匹配

如果API返回的JSON结构和Users模型不匹配,Moshi无法解析,会返回空列表或抛出异常。比如API返回的是包含用户数组的对象(而非直接数组),当前接口定义会解析失败。

修复:

  • 用工具查看API实际返回的JSON结构,如果是{"users": [/* 用户数组 */]},创建包装类:
data class UserResponse(
    @Json(name = "users") val users: List<Users>
)
  • 修改接口:
@GET("users")
suspend fun getUsers(): UserResponse
  • 在ViewModel中解析:
_users.value = PurpleTeapotDatingAPI.retrofitService.getUsers().users

5. 确认添加网络权限

确保AndroidManifest.xml中添加了网络权限:

<uses-permission android:name="android.permission.INTERNET" />

内容的提问来源于stack exchange,提问作者Karan Vadhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:39:54