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

Jetpack Compose应用:无法通过Hyrule-Compendium-API加载物品详情

问题:Hyrule Compendium API详情页始终显示“No name”

使用Hyrule Compendium API开发Android应用时,列表展示功能正常,但点击列表项进入物品详情页时,始终显示“No name”。控制台显示请求URL正确,但无法正常渲染物品名称等属性,怀疑是物品属性访问方式有误,以下是完整代码实现:

CompendiumApi

interface CompendiumApi {

    @GET("all?game=2")
    suspend fun getTearsAllEntries(): CompendiumList

    @GET("all?game=1")
    suspend fun getBreathAllEntries(): CompendiumList

    @GET("entry/{id}")
    suspend fun getItemInfo(
        @Path("id") id: Int
    ): Data
}

CompendiumRepository

@ActivityScoped
class CompendiumRepository @Inject constructor(
   private val api: CompendiumApi
){
   suspend fun getTearsAllEntries(): Resource<CompendiumList> {
      val response = try {
         api.getTearsAllEntries()
      } catch (e: Exception) {
         return Resource.Error("An unknown error occurred.")
      }
      return Resource.Success(response)
   }

   suspend fun getBreathAllEntries(): Resource<CompendiumList> {
      val response = try {
         api.getBreathAllEntries()
      } catch (e: Exception) {
         return Resource.Error("An unknown error occurred.")
      }
      return Resource.Success(response)
   }

   suspend fun getItemInfo(id: Int) : Resource<Data> {
      val response = try {
         Log.d("getItemInfo",api.getItemInfo(id).toString())
         api.getItemInfo(id)
      } catch (e: Exception) {
         return Resource.Error("An unknow error ocurred.")
      }
      return Resource.Success(response)
   }
}

CompendiumDetailViewModel

@HiltViewModel
class CompendiumDetailViewModel @Inject constructor(
   private val repository: CompendiumRepository
) : ViewModel() {

   suspend fun getItemInfo(id: Int): Resource<Data> {
      return repository.getItemInfo(id)
   }
}

CompendiumDetailScreen

@Composable
fun CompendiumDetailCard(
   id: Int,
   viewModel: CompendiumDetailViewModel = hiltViewModel()
) {
   val itemInfo = produceState<Resource<Data>>(initialValue = Resource.Loading()) {
      value = viewModel.getItemInfo(id)
   }.value
   Log.d("itemDetailScreen", "item clicked: ${itemInfo.data.toString()}")
   ElevatedCard(
      elevation = CardDefaults.cardElevation(
         defaultElevation = 6.dp
      ),
      modifier = Modifier
         .fillMaxWidth()
   ) {
      ItemDetailStateWrapper(
         itemInfo = itemInfo,
         loadingModifier = Modifier
            .size(100.dp)
            .align(Alignment.CenterHorizontally)
            .padding(
               top = 16.dp,
               start = 16.dp,
               end = 16.dp,
               bottom = 16.dp
            )
      )
   }
}

@Composable
fun ItemDetailStateWrapper(
   itemInfo: Resource<Data>,
   loadingModifier: Modifier = Modifier
){
   when(itemInfo){
      is Resource.Success -> {
         ItemDetailInfo(
            itemInfo = itemInfo.data!!
         )
      }
      is Resource.Error -> {
         Text(
            text = itemInfo.message!!,
            color = Color.Red,
         )
      }
      is Resource.Loading -> {
         CircularProgressIndicator(
            color = MaterialTheme.colorScheme.primary,
            modifier = loadingModifier
         )
      }
   }
}

@Composable
fun ItemDetailInfo(
   itemInfo: Data
){
   Text(text = itemInfo.name ?: "No name") //"No name painted"
}

MainActivity(导航逻辑)

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      window.statusBarColor = getColor(R.color.black)
      setContent {
         ZeldaCompendiumTheme {
            val navController = rememberNavController()
            NavHost(
               navController = navController,
               startDestination = "compendium_navigation"
            ) {
               composable("compendium_navigation") {
                  HomeContainer(navController = navController)
               }
               composable("compendium_tears_screen") {
                  TearsContainer(
                     navController = navController
                  )
               }
               composable("compendium_breath_screen") {
                  BreathContainer(
                     navController = navController
                  )
               }
               composable(
                  "item_detail_screen/{id}",
                  arguments = listOf(
                     navArgument("id") {
                        type = NavType.IntType
                     }
                  )
               ) {
                  val id = remember {
                     it.arguments?.getInt("id")
                  }
                  if (id != null) {
                     CompendiumDetailCard(
                        id = id,
                     )
                  }
               }
            }
         }
      }
   }
}

数据类定义

// CompendiumList
data class CompendiumList(
   val data: List<Data>
)

// CompendiumListEntry
data class CompendiumListEntry (
   val compendiumName: String,
   val category: String,
   val imageURL: String,
   val id: Int
)

// Data
data class Data(
   val category: String,
   @SerializedName("common_location") val commonLocations: List<String>,
   @SerializedName("cooking_effect") val cookingEffect: String,
   val description: String,
   val dlc: Boolean,
   val drops: List<String>,
   val edible: Boolean,
   @SerializedName("fuse_attack_power") val fuseAttackPower: Int,
   @SerializedName("hearts_recovered")val heartsRecovered: Double,
   val id: Int,
   val image: String,
   val name: String,
   val properties: Properties
)

问题排查与修复方案

核心问题

  1. API响应结构不匹配:entry/{id}接口的实际响应是一个外层包含data字段的对象,而非直接返回Data实例。当前CompendiumApi中getItemInfo直接返回Data,导致Gson无法正确解析,所有字段值为null。
  2. 数据类字段非空性问题:Data类中所有字段定义为非空类型,但API响应中部分字段可能为空,解析失败后字段值为null,最终触发itemInfo.name ?: "No name"的默认值。

修复步骤

  1. 新增响应包裹类:创建用于接收单条物品响应的类:
data class SingleItemResponse(
    val data: Data
)
  1. 修正API接口定义:更新CompendiumApi中的getItemInfo方法:
@GET("entry/{id}")
suspend fun getItemInfo(@Path("id") id: Int): SingleItemResponse
  1. 更新Repository逻辑:调整getItemInfo方法,从包裹类中取出实际的Data对象:
suspend fun getItemInfo(id: Int) : Resource<Data> {
    val response = try {
        val apiResponse = api.getItemInfo(id)
        apiResponse.data
    } catch (e: Exception) {
        return Resource.Error("An unknown error occurred.")
    }
    return Resource.Success(response)
}
  1. 优化Data类可空性:将Data类中所有可能为空的字段改为可空类型,示例:
data class Data(
    val category: String?,
    @SerializedName("common_location") val commonLocations: List<String>?,
    @SerializedName("cooking_effect") val cookingEffect: String?,
    val description: String?,
    val dlc: Boolean?,
    val drops: List<String>?,
    val edible: Boolean?,
    @SerializedName("fuse_attack_power") val fuseAttackPower: Int?,
    @SerializedName("hearts_recovered")val heartsRecovered: Double?,
    val id: Int?,
    val image: String?,
    val name: String?,
    val properties: Properties?
)

(同步修改Properties类的字段可空性,确保与API响应匹配)

  1. 验证解析结果:在CompendiumDetailScreen中打印itemInfo.data的完整内容,确认字段值已正确解析。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:05:05