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

Kotlin开发汇率兑换APP遭遇IndexOutOfBoundsException错误求助

解决汇率兑换应用的IndexOutOfBoundsException异常

在开发汇率兑换应用时,尝试获取首个货币数据触发IndexOutOfBoundsException,原因是exchangeDetail数组为空时直接访问了索引0。相关代码如下:

相关代码片段

CalculateView代码

Column {
    if(calculateViewModel.loading){
        CircularProgressIndicator()
    }else{
        Text(text = calculateViewModel.exchangeDetail[0].get("EUR").toString())
        Text(text = calculateViewModel.exchangeDetail[0].get(currency2).toString())
    }
}

CalculateViewModel代码

class CalculateViewModel: ViewModel() {

    private val exchangeService = ExchangeService()
    var loading: Boolean by mutableStateOf(false)
    var errorMessage : String by mutableStateOf("")
    var exchangeDetail : MutableList<Map<String, Float>> by mutableStateOf(mutableListOf())

    fun getExchangeRatio(input1: String, input2: String) = viewModelScope.launch {
        errorMessage = ""
        loading = true
        try {
            val ratio = exchangeService.getExchangeCourse(input1,input2)
            exchangeDetail.add(ratio.data)
        }catch (e: Exception){
            errorMessage = e.message.toString()
        }finally {
            loading = false
        }
    }
}

ExchangeDto类代码

@kotlinx.serialization.Serializable
data class ExchangeDto (
    val data: Map<String, Float>
    //Zugriff über .get()
)

APIManager代码

class APIManager {
    private val apiKey = "Om1xoJkPAxk9neFbFvzezQtxAuNHdSERTfN8CPbk"
    var jsonHttpClient = HttpClient {
        expectSuccess = true
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
            })
        }
        defaultRequest {
            url.host = "api.freecurrencyapi.com"
            url.protocol = URLProtocol.HTTPS
            url.encodedPath = "/v1/latest?apikey=$apiKey" + url.encodedPath
            contentType(ContentType.Application.Json)
            headers {
                append(HttpHeaders.Authorization,
                    "Bearer $apiKey")
            }
        }

        HttpResponseValidator {
            getCustomResponseValidator(this)
        }
    }
}

核心问题分析

直接访问exchangeDetail[0]未做空安全校验:

  • 首次调用API前,exchangeDetail是空列表
  • API请求失败时,exchangeDetail不会被填充
  • 即使请求成功,也需先确认列表有元素再访问索引

修复步骤

1. 优化View层的空安全与错误处理

修改CalculateView代码,增加空判断、错误状态展示,同时处理Map取值可能为null的情况:

Column {
    if(calculateViewModel.loading){
        CircularProgressIndicator()
    }else {
        // 展示错误信息
        calculateViewModel.errorMessage.takeIf { it.isNotEmpty() }?.let {
            Text(text = "请求失败: $it", color = Color.Red)
        } ?: run {
            // 判断列表是否有数据
            calculateViewModel.exchangeDetail.firstOrNull()?.let { dataMap ->
                // 处理Map取值为null的情况
                val eurValue = dataMap.get("EUR")?.toString() ?: "无数据"
                val currency2Value = dataMap.get(currency2)?.toString() ?: "无数据"
                Text(text = eurValue)
                Text(text = currency2Value)
            } ?: Text(text = "暂无汇率数据")
        }
    }
}

2. ViewModel层优化(可选)

若无需存储历史汇率数据,没必要用MutableList<Map<String, Float>>,直接用单个Map的State更简洁,避免列表操作问题:

class CalculateViewModel: ViewModel() {

    private val exchangeService = ExchangeService()
    var loading: Boolean by mutableStateOf(false)
    var errorMessage : String by mutableStateOf("")
    // 改为单个Map,初始为空
    var exchangeData : Map<String, Float> by mutableStateOf(emptyMap())

    fun getExchangeRatio(input1: String, input2: String) = viewModelScope.launch {
        errorMessage = ""
        loading = true
        try {
            val ratio = exchangeService.getExchangeCourse(input1,input2)
            exchangeData = ratio.data
        }catch (e: Exception){
            errorMessage = e.message.toString()
            // 请求失败时清空数据,避免旧数据残留
            exchangeData = emptyMap()
        }finally {
            loading = false
        }
    }
}

对应的View层可简化为:

Column {
    if(calculateViewModel.loading){
        CircularProgressIndicator()
    }else {
        calculateViewModel.errorMessage.takeIf { it.isNotEmpty() }?.let {
            Text(text = "请求失败: $it", color = Color.Red)
        } ?: run {
            if(calculateViewModel.exchangeData.isNotEmpty()){
                val eurValue = calculateViewModel.exchangeData.get("EUR")?.toString() ?: "无数据"
                val currency2Value = calculateViewModel.exchangeData.get(currency2)?.toString() ?: "无数据"
                Text(text = eurValue)
                Text(text = currency2Value)
            }else{
                Text(text = "暂无汇率数据")
            }
        }
    }
}

3. 额外建议

  • 在ViewModel的try块中打印ratio.data,确认API返回的货币键值对是否符合预期
  • 针对get()方法返回null的场景,始终设置默认值,避免空指针异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:44:59