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

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的核心原因:

  1. 接口参数配置错误,请求URL不符合API规范
  2. 未正确处理API返回的外层数组结构
  3. DTO中非空字段与API返回的null值冲突
  4. 协程Scope未绑定生命周期(虽不直接导致Null,但属于必要优化)

修复后即可正常获取TheMealDB的菜谱数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:09:51