使用Retrofit发起POST请求时触发java.lang.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

