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

Jetpack Compose:解决资源未找到异常并实现银行列表搜索

解决ViewModel中通过字符串资源ID获取应用资源的问题

你遇到的Resources$NotFoundException是因为Resources.getSystem()只能访问系统内置资源,无法获取你的应用自己定义的资源(以0x7f开头的ID属于应用私有资源)。下面提供三种可行的解决方案:

方案一:在ViewModel中注入Application Context

ViewModel可以安全持有Application Context(Application是全局单例,不会引发内存泄漏),用它来获取应用资源:

修改BankViewModel代码

class BankViewModel(application: Application) : ViewModel() {
    private val _bankAccount = MutableStateFlow(BankAccount())
    val bankAccount: StateFlow<BankAccount> = _bankAccount.asStateFlow()

    private val appContext = application.applicationContext
    var bankList = mutableStateOf(Banks)

    private var cachedBankList = listOf<Bank>()
    private var isSearchStarting = true
    var isSearching = mutableStateOf(false)

    fun updateBankSearch(searchName: String) {
        _bankAccount.update { 
            it.copy(bankName = searchName)
        }
    }

    fun searchBankName(query: String) {
        val listToSearch = if(isSearchStarting) {
            bankList.value
        } else {
            cachedBankList
        }
        viewModelScope.launch {
            if (query.isEmpty()) {
                bankList.value = cachedBankList
                isSearching.value = false
                isSearchStarting = true
                return@launch
            }
            // 用Application Context获取应用字符串资源
            val results = listToSearch.filter {
                appContext.getString(it.bankName).contains(query.trim(), ignoreCase = true)
            }
            if (isSearchStarting) {
                cachedBankList = bankList.value
                isSearchStarting = false
            }
            bankList.value = results
            isSearching.value = true
        }
    }
}

初始化ViewModel(无依赖注入时)

在Activity/Fragment中手动创建ViewModel:

val viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return BankViewModel(application) as T
    }
})[BankViewModel::class.java]

方案二:提前解析字符串资源到Bank数据类

在初始化银行列表时,直接把字符串资源ID转换成实际字符串,ViewModel中只需对比字符串:

修改Bank数据类

data class Bank(
    val bankName: String,
    @DrawableRes val bankLogo: Int = R.drawable.bank_image_2
)

初始化银行列表(在Application/Activity中)

// 示例:在Application中初始化全局银行列表
class MyApp : Application() {
    companion object {
        lateinit var Banks: List<Bank>
    }

    override fun onCreate() {
        super.onCreate()
        Banks = listOf(
            Bank(getString(R.string.bank_icbc)),
            Bank(getString(R.string.bank_ccb)),
            // 其他银行...
        )
    }
}

此时ViewModel的搜索逻辑可简化为:

val results = listToSearch.filter {
    it.bankName.contains(query.trim(), ignoreCase = true)
}

方案三:在Compose UI层处理搜索过滤

遵循Compose单向数据流原则,把资源解析和过滤逻辑放在UI层(Composable函数中可直接调用stringResource):

@Composable
fun BankSearchScreen(viewModel: BankViewModel = viewModel()) {
    val bankList by viewModel.bankList.collectAsState()
    val searchQuery = remember { mutableStateOf("") }

    // 在UI层过滤数据
    val filteredBanks = if (searchQuery.value.isEmpty()) {
        bankList
    } else {
        bankList.filter {
            stringResource(it.bankName).contains(searchQuery.value.trim(), ignoreCase = true)
        }
    }

    // 渲染搜索框和过滤后的银行列表
    Column {
        TextField(
            value = searchQuery.value,
            onValueChange = { searchQuery.value = it },
            label = { Text("搜索银行") }
        )
        LazyColumn {
            items(filteredBanks) { bank ->
                BankItem(bank)
            }
        }
    }
}

方案对比

  • 方案一:适合需要在ViewModel中处理复杂业务逻辑的场景,保持UI层简洁。
  • 方案二:实现最简单,ViewModel无需处理资源相关逻辑,但需要提前初始化数据。
  • 方案三:符合Compose设计理念,把UI相关的解析和过滤放在UI层,ViewModel只负责提供原始数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:55:31