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 )
问题排查与修复方案
核心问题
- API响应结构不匹配:
entry/{id}接口的实际响应是一个外层包含data字段的对象,而非直接返回Data实例。当前CompendiumApi中getItemInfo直接返回Data,导致Gson无法正确解析,所有字段值为null。 - 数据类字段非空性问题:
Data类中所有字段定义为非空类型,但API响应中部分字段可能为空,解析失败后字段值为null,最终触发itemInfo.name ?: "No name"的默认值。
修复步骤
- 新增响应包裹类:创建用于接收单条物品响应的类:
data class SingleItemResponse( val data: Data )
- 修正API接口定义:更新
CompendiumApi中的getItemInfo方法:
@GET("entry/{id}") suspend fun getItemInfo(@Path("id") id: Int): SingleItemResponse
- 更新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) }
- 优化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响应匹配)
- 验证解析结果:在
CompendiumDetailScreen中打印itemInfo.data的完整内容,确认字段值已正确解析。
内容的提问来源于stack exchange,提问作者Javi
相关产品推荐
相关产品推荐

