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

使用Retrofit发起POST请求时触发java.lang.NullPointerException

问题:Retrofit对接TMDB API创建Session时抛出NullPointerException

我用Retrofit+Jetpack Compose对接TMDB API创建会话,流程分三步:

  • 第一步:调用@GET接口生成request token,执行成功;
  • 第二步:跳转TMDB网页完成账号授权,运行正常;
  • 第三步:调用@POST接口创建session时,抛出java.lang.NullPointerException异常。

尝试添加TMDB官方文档的@Headers注解后问题仍未解决,相关代码如下:


RequestToken数据类

data class RequestToken(
    val expires_at: String = "",
    val request_token: String = "",
    val success: Boolean = false
)

SessionId数据类

data class SessionId(
    val session_id: String = "",
    val success: Boolean = false
)

API接口

interface ApiInterface {

    @GET("authentication/token/new?api_key=$apiKey")
    suspend fun getRequestToken(): Response<RequestToken>

    @POST("authentication/session/new?api_key=$apiKey")
    suspend fun createSession(@Body requestToken: String): Response<SessionId>
}

Retrofit实例

object RetrofitInstance {
    val api: ApiOperations by lazy {
        Retrofit.Builder()
            .baseUrl("https://api.themoviedb.org/3/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiOperations::class.java)
    }
}

ViewModel代码

// Global Instance Of ApiViewModel
val apiViewModel = ApiViewModel()

class ApiViewModel: ViewModel() {

    var requestToken by mutableStateOf(RequestToken())

    var sessionId by mutableStateOf(SessionId())

    fun accessRequestPageUrl(requestToken: String): String {
    return "https://www.themoviedb.org/authenticate/${requestToken}"}

}

API调用函数(ViewModel内)

fun getRequestToken(context: Context) {
    viewModelScope.launch {
        try {
          apiViewModel.requestToken = RetrofitInstance.api.getRequestToken().body()!!
            }catch (e: Exception){
                Toast.makeText(context, e.toString, Toast.LENGTH_LONG).show()
                return@launch
      }
   }
}

fun createSession(context: Context, requestToken: String) {
    viewModelScope.launch {
        try {
          apiViewModel.sessionId = RetrofitInstance.api.createSession(
              context = context, requestToken = requestToken).body()!!
            }catch (e: Exception){
                Toast.makeText(context, e.toString, Toast.LENGTH_LONG).show()
                return@launch
      }
   }
}

UI代码

val context = LocalContext.current

// Conditional That Redirects User To Login Page If Request Token Is Not Empty
if (apiViewModel.requestToken.request_token.isNotEmpty()) {
        context.startActivity(
            Intent(
                Intent.ACTION_VIEW,
                Uri.parse(apiViewModel.accessRequestPageUrl(apiViewModel.requestToken.request_token)))
        )
    }

Column(
        modifier = Modifier.fillMaxSize()
    ) {

    // Button That Creates The Request Token
    Button(onClick = {apiViewModel.getRequestToken(context = context)}){
       Text(text = "Login")
   }

    // Button That Creates The Session
    Button(onClick = {
            apiViewModel.createSession(
            context = context, requestToken = apiViewModel.requestToken)}) {

    }
}

排查方向与修复方案

1. 接口请求体格式错误

TMDB创建session的POST接口要求请求体是JSON格式,结构为{"request_token": "你的token值"},但当前直接传String类型的token,Gson会将其序列化为带引号的字符串字面量,不符合接口要求。

修复:
创建请求体数据类:

data class SessionRequest(
    val request_token: String
)

修改API接口:

@POST("authentication/session/new?api_key=$apiKey")
suspend fun createSession(@Body request: SessionRequest): Response<SessionId>

调用时传入封装后的对象:

// createSession函数内
val response = RetrofitInstance.api.createSession(SessionRequest(requestToken))

2. UI层传参错误

UI中调用createSession时,原代码传入的是RequestToken对象,而非String类型的token值,会导致类型不匹配,后续触发空指针。

修复:
传入正确的token字符串:

Button(onClick = {
    apiViewModel.createSession(
        context = context, 
        requestToken = apiViewModel.requestToken.request_token
    )
}) {
    Text(text = "Create Session")
}

3. 强制非空操作符!!触发空指针

直接用!!获取Response.body(),若接口返回错误(如token未授权、API key无效),body()会返回null,直接抛出NullPointerException。

修复:
先判断请求是否成功,再处理返回数据:

fun createSession(context: Context, requestToken: String) {
    viewModelScope.launch {
        try {
            val response = RetrofitInstance.api.createSession(SessionRequest(requestToken))
            if (response.isSuccessful) {
                response.body()?.let {
                    apiViewModel.sessionId = it
                } ?: run {
                    Toast.makeText(context, "获取Session失败:返回数据为空", Toast.LENGTH_LONG).show()
                }
            } else {
                val errorBody = response.errorBody()?.string()
                Toast.makeText(context, "接口请求失败:${response.code()} - $errorBody", Toast.LENGTH_LONG).show()
            }
        } catch (e: Exception) {
            Toast.makeText(context, e.toString(), Toast.LENGTH_LONG).show()
        }
    }
}

4. Retrofit实例接口类型不匹配

RetrofitInstance中创建的是ApiOperations接口,但实际接口定义为ApiInterface,会导致类型转换错误。

修复:
统一接口名称,修改RetrofitInstance代码:

val api: ApiInterface by lazy {
    Retrofit.Builder()
        .baseUrl("https://api.themoviedb.org/3/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(ApiInterface::class.java)
}

5. 全局ViewModel实例的生命周期问题

使用全局apiViewModel实例会导致Compose状态管理混乱,建议用官方ViewModel获取方式。

修复:
在UI中通过viewModel()获取实例:

val apiViewModel: ApiViewModel = viewModel()

删除全局的val apiViewModel = ApiViewModel()定义。


内容的提问来源于stack exchange,提问作者Bob Rasner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:12:12