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
相关产品推荐
相关产品推荐

