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
相关产品推荐
相关产品推荐

