当 ViewModel 从不同的可组合项更新时可组合项不重组
Composable not recomposing when ViewModel updated from a different Composable
我正在使用 Jetpack Compose 开发 Android 应用程序。该应用程序显示项目列表。
它还有一个包含搜索栏的顶部栏。
我们有 3 个可组合项:项目列表、搜索栏组件和应用栏
(应用栏包含搜索栏)。
相同的 ViewModel class 被注入列表可组合项和搜索栏可组合项(使用 Hilt)。
ViewModel 具有项目列表:
var items by mutableStateOf<List<Item>?>(null)
以及搜索栏调用的搜索功能,用于过滤这些项目。
问题如下:列表可组合项在搜索项目时未重组。
注意:
- 我记录了搜索功能的输出,因此我确信该功能工作正常。
- 将搜索栏可组合项放置在包含项目列表的同一可组合项中时,搜索会按预期进行。
所以似乎只有当搜索栏在列表中时列表才会重新组合。
当搜索栏在应用栏中时,如何让它工作?
谢谢,
编辑:
通过在初始化 ViewModel 时生成随机值,Hilt 似乎在 Searchbar 可组合项和项目可组合项中注入了两个不同的实例。
是否可以将 ViewModel 作为单例注入?还是应该将我的数据保存在存储库中,然后将这些数据提供给 ViewModel?
这是不同 classes 的代码:
Searchbar.kt
@Composable
fun SearchBar(itemViewModel: ItemViewModel = hiltViewModel()){
var searchText by remember { mutableStateOf("") }
BasicTextField(
singleLine = true,
cursorBrush = SolidColor(Color.White),
value = searchText,
onValueChange = {
searchText = it
itemViewModel.searchByLabel(it)
},
modifier = Modifier
.fillMaxWidth()
.height(28.dp),
textStyle = LocalTextStyle.current.copy(
color = Color.White,
fontSize = MaterialTheme.typography.body2.fontSize
),
decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
Icons.Filled.Search,
contentDescription = "Search"
)
Column(Modifier.weight(1f)) {
Box(
modifier = Modifier.fillMaxWidth()
) {
if (searchText.isEmpty()) {
Text(
"Search",
style = LocalTextStyle.current.copy(
color = Color.White.copy(alpha = 0.7f),
fontSize = MaterialTheme.typography.body2.fontSize
)
)
}
innerTextField()
}
}
if (searchText.isNotEmpty()) {
IconButton(onClick = { searchText = "" }) {
Icon(Icons.Filled.Cancel, contentDescription = "Delete")
}
}
}
}
)
}
Items.kt
@Composable
fun Items(
navController: NavController,
itemViewModel: ItemViewModel = hiltViewModel(),
){
val items = itemViewModel.items
items?.let { items ->
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
items(items) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
navController.navigate("items/${item.id}")
},
Arrangement.spacedBy(8.dp)
) {
Text(item.label)
}
}
}
} ?: run {
Text(
text = "No protocols",
modifier = Modifier.fillMaxWidth()
)
}
}
ItemViewModel.kt
@HiltViewModel
class ItemViewModel @Inject constructor(private val repository: ItemRepository) : ViewModel() {
var items by mutableStateOf<List<Item>?>(null)
init{ loadItems() }
private fun loadItems(){
viewModelScope.launch {
items = repository.getItems()
}
}
fun searchByLabel(value: String){
viewModelScope.launch {
items = repository.search(value)
}
}
}
ItemRepository.kt
class ItemRepository(){
suspend fun getItems(): List<Item>? {
return listOf(Item(1, "Label 1"), Item(2, "Label 2"), Item(3, "Label 3"))
}
suspend fun search(value: String): List<Item>? {
return getItems()?.filter { it.label.lowercase().contains(value.lowercase())}
}
}
Item.kt
data class Item(var id: Int, var label: String){}
我最终将数据存储在存储库中,该存储库作为单例注入。这样,即使创建了多个 ViewModel 实例,它们仍然访问相同的数据。
我正在使用 Jetpack Compose 开发 Android 应用程序。该应用程序显示项目列表。 它还有一个包含搜索栏的顶部栏。
我们有 3 个可组合项:项目列表、搜索栏组件和应用栏 (应用栏包含搜索栏)。
相同的 ViewModel class 被注入列表可组合项和搜索栏可组合项(使用 Hilt)。 ViewModel 具有项目列表:
var items by mutableStateOf<List<Item>?>(null)
以及搜索栏调用的搜索功能,用于过滤这些项目。
问题如下:列表可组合项在搜索项目时未重组。
注意:
- 我记录了搜索功能的输出,因此我确信该功能工作正常。
- 将搜索栏可组合项放置在包含项目列表的同一可组合项中时,搜索会按预期进行。
所以似乎只有当搜索栏在列表中时列表才会重新组合。
当搜索栏在应用栏中时,如何让它工作?
谢谢,
编辑: 通过在初始化 ViewModel 时生成随机值,Hilt 似乎在 Searchbar 可组合项和项目可组合项中注入了两个不同的实例。
是否可以将 ViewModel 作为单例注入?还是应该将我的数据保存在存储库中,然后将这些数据提供给 ViewModel?
这是不同 classes 的代码:
Searchbar.kt
@Composable
fun SearchBar(itemViewModel: ItemViewModel = hiltViewModel()){
var searchText by remember { mutableStateOf("") }
BasicTextField(
singleLine = true,
cursorBrush = SolidColor(Color.White),
value = searchText,
onValueChange = {
searchText = it
itemViewModel.searchByLabel(it)
},
modifier = Modifier
.fillMaxWidth()
.height(28.dp),
textStyle = LocalTextStyle.current.copy(
color = Color.White,
fontSize = MaterialTheme.typography.body2.fontSize
),
decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
Icons.Filled.Search,
contentDescription = "Search"
)
Column(Modifier.weight(1f)) {
Box(
modifier = Modifier.fillMaxWidth()
) {
if (searchText.isEmpty()) {
Text(
"Search",
style = LocalTextStyle.current.copy(
color = Color.White.copy(alpha = 0.7f),
fontSize = MaterialTheme.typography.body2.fontSize
)
)
}
innerTextField()
}
}
if (searchText.isNotEmpty()) {
IconButton(onClick = { searchText = "" }) {
Icon(Icons.Filled.Cancel, contentDescription = "Delete")
}
}
}
}
)
}
Items.kt
@Composable
fun Items(
navController: NavController,
itemViewModel: ItemViewModel = hiltViewModel(),
){
val items = itemViewModel.items
items?.let { items ->
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
items(items) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
navController.navigate("items/${item.id}")
},
Arrangement.spacedBy(8.dp)
) {
Text(item.label)
}
}
}
} ?: run {
Text(
text = "No protocols",
modifier = Modifier.fillMaxWidth()
)
}
}
ItemViewModel.kt
@HiltViewModel
class ItemViewModel @Inject constructor(private val repository: ItemRepository) : ViewModel() {
var items by mutableStateOf<List<Item>?>(null)
init{ loadItems() }
private fun loadItems(){
viewModelScope.launch {
items = repository.getItems()
}
}
fun searchByLabel(value: String){
viewModelScope.launch {
items = repository.search(value)
}
}
}
ItemRepository.kt
class ItemRepository(){
suspend fun getItems(): List<Item>? {
return listOf(Item(1, "Label 1"), Item(2, "Label 2"), Item(3, "Label 3"))
}
suspend fun search(value: String): List<Item>? {
return getItems()?.filter { it.label.lowercase().contains(value.lowercase())}
}
}
Item.kt
data class Item(var id: Int, var label: String){}
我最终将数据存储在存储库中,该存储库作为单例注入。这样,即使创建了多个 ViewModel 实例,它们仍然访问相同的数据。