Android应用调用TheMealDB API返回Null问题求助
TheMealDB API 返回Null实例问题排查与修复
我正在使用TheMealDB免费API开发菜谱应用,但调用API获取数据时始终得到Null实例。使用的请求URL格式为www.themealdb.com/api/json/v1/1/search.php?s={Query},以下是问题排查与修复方案:
核心问题分析与修复
1. Retrofit接口请求参数配置错误
原RecipeService中的参数配置完全不符合TheMealDB的API规范:
- 搜索接口的参数名是
s,不是自定义的query @GET("search.php?s=")会导致URL被拼接成search.php?s=&query=xxx,完全无法命中正确接口
修复后的RecipeService:
interface RecipeService { // 仅保留接口路径,参数用@Query指定为API要求的"s" @GET("search.php") suspend fun get( @Query("s") query: String ): RecipeSearchResponse @GET("lookup.php") suspend fun getById( @Query("i") id: String // API的ID是字符串类型,不是Int ): RecipeSearchResponse }
2. API响应结构不匹配
TheMealDB搜索接口返回的是包含meals数组的JSON对象,而非单个菜谱对象。例如搜索"Arrabiata"的响应结构为:
{ "meals": [ { "idMeal": "52768", "strMeal": "Spicy Arrabiata Penne", // ...其他字段 } ] }
原代码直接用RecipeDTO接收整个响应,Gson无法解析,导致返回Null。需要创建外层响应类适配结构:
新增RecipeSearchResponse类:
package com.example.intojetpackcompose.network.Response import com.example.intojetpackcompose.network.model.RecipeDTO import com.google.gson.annotations.SerializedName class RecipeSearchResponse( @SerializedName("meals") val meals: List<RecipeDTO>? = null // 搜索可能无结果,用可空列表 )
3. RecipeDTO中非空字段导致解析失败
TheMealDB返回的菜谱中,大量strIngredient和strMeasure字段可能为null或空字符串,但原RecipeDTO将这些字段定义为非空String,Gson无法处理null值,直接解析失败返回Null。
修复后的RecipeDTO(所有可能为空的字段改为可空):
package com.example.intojetpackcompose.network.model import com.google.gson.annotations.SerializedName class RecipeDTO ( @SerializedName("idMeal") var idMeal: String? = null, @SerializedName("strMeal") var strMeal : String? = null, @SerializedName("strDrinkAlternate") var strDrinkAlternate : String? = null, @SerializedName("strCategory") var strCategory: String? = null, @SerializedName("strArea") var strArea: String? = null, @SerializedName("strInstructions") var strInstructions: String? = null, @SerializedName("strMealThumb") var strMealThumb : String? = null, @SerializedName("strTags") var strTags: String? = null, @SerializedName("strYoutube") var strYoutube: String? = null, // 所有食材字段改为可空 @SerializedName("strIngredient1") var strIngredient1: String? = null, @SerializedName("strIngredient2") var strIngredient2: String? = null, @SerializedName("strIngredient3") var strIngredient3: String? = null, @SerializedName("strIngredient4") var strIngredient4: String? = null, @SerializedName("strIngredient5") var strIngredient5: String? = null, @SerializedName("strIngredient6") var strIngredient6: String? = null, @SerializedName("strIngredient7") var strIngredient7: String? = null, @SerializedName("strIngredient8") var strIngredient8: String? = null, @SerializedName("strIngredient9") var strIngredient9: String? = null, @SerializedName("strIngredient10") var strIngredient10: String? = null, @SerializedName("strIngredient11") var strIngredient11: String? = null, @SerializedName("strIngredient12") var strIngredient12: String? = null, @SerializedName("strIngredient13") var strIngredient13: String? = null, @SerializedName("strIngredient14") var strIngredient14: String? = null, @SerializedName("strIngredient15") var strIngredient15: String? = null, @SerializedName("strIngredient16") var strIngredient16: String? = null, @SerializedName("strIngredient17") var strIngredient17: String? = null, @SerializedName("strIngredient18") var strIngredient18: String? = null, @SerializedName("strIngredient19") var strIngredient19: String? = null, @SerializedName("strIngredient20") var strIngredient20: String? = null, // 所有用量字段改为可空 @SerializedName("strMeasure1") var strMeasure1: String? = null, @SerializedName("strMeasure2") var strMeasure2: String? = null, @SerializedName("strMeasure3") var strMeasure3: String? = null, @SerializedName("strMeasure4") var strMeasure4: String? = null, @SerializedName("strMeasure5") var strMeasure5: String? = null, @SerializedName("strMeasure6") var strMeasure6: String? = null, @SerializedName("strMeasure7") var strMeasure7: String? = null, @SerializedName("strMeasure8") var strMeasure8: String? = null, @SerializedName("strMeasure9") var strMeasure9: String? = null, @SerializedName("strMeasure10") var strMeasure10: String? = null, @SerializedName("strMeasure11") var strMeasure11: String? = null, @SerializedName("strMeasure12") var strMeasure12: String? = null, @SerializedName("strMeasure13") var strMeasure13: String? = null, @SerializedName("strMeasure14") var strMeasure14: String? = null, @SerializedName("strMeasure15") var strMeasure15: String? = null, @SerializedName("strMeasure16") var strMeasure16: String? = null, @SerializedName("strMeasure17") var strMeasure17: String? = null, @SerializedName("strMeasure18") var strMeasure18: String? = null, @SerializedName("strMeasure19") var strMeasure19: String? = null, @SerializedName("strMeasure20") var strMeasure20: String? = null, @SerializedName("strSource") var strSource: String? = null, @SerializedName("strImageSource") var strImageSource: String? = null, @SerializedName("strCreativeCommonsConfirmed") var strCreativeCommonsConfirmed: String? = null, @SerializedName("dateModified") // API返回字段为dateModified,不是strModified var dateModified: String? = null )
4. MainActivity协程Scope优化
原代码使用CoroutineScope(IO)会导致协程不受Activity生命周期管理,建议改用lifecycleScope:
修复后的MainActivity代码片段:
class MainActivity : ComponentActivity() { @SuppressLint("CoroutineCreationDuringComposition") @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Log.d("STEP1","STEP1") val service = Retrofit.Builder() .baseUrl("https://www.themealdb.com/api/json/v1/1/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(RecipeService::class.java) // 用lifecycleScope绑定Activity生命周期 lifecycleScope.launch(Dispatchers.IO) { try{ Log.d("STEP2", "STEP2") val response = service.get("Arrabiata") // 获取列表中第一个菜谱并打印 response.meals?.firstOrNull()?.let { meal -> Log.d("RECIPE", "菜谱ID:${meal.idMeal},名称:${meal.strMeal}") } ?: Log.d("RECIPE", "未找到匹配的菜谱") } catch (e: Exception){ Log.e("API_ERROR", e.message ?: "未知错误") e.printStackTrace() } } } } }
总结
导致返回Null的核心原因:
- 接口参数配置错误,请求URL不符合API规范
- 未正确处理API返回的外层数组结构
- DTO中非空字段与API返回的null值冲突
- 协程Scope未绑定生命周期(虽不直接导致Null,但属于必要优化)
修复后即可正常获取TheMealDB的菜谱数据。
内容的提问来源于stack exchange,提问作者Cyclone
相关产品推荐
相关产品推荐

