如何使用视图模型的 mutableState 变量更新 UI 可组合项

问题描述 投票:0回答:1

我从另一个应用程序复制粘贴的代码,它执行完全相同的操作并且有效。我的代码中的 log.i() 语句显示信息发生了变化,但我的谷歌地图可组合项不会使用从 viewModel.getRestaurantList() 收到的新列表重新组合。我发现唯一可疑的是按钮内的 Log.i() 始终为空,即使视图模型中的函数内的另一个 Log.i() 显示列表已由函数 getRestaurantList 更新视图模型。这是相关代码:

充当屏幕的可组合项:

@Composable
fun SearchScreen(
    myName: String,
    navController: NavController,
    context: Context = LocalContext.current,
) {
    val viewModel: LocationViewModel = LocationViewModel(context)
    var user: User = User(myName, 1, 0.0, 0.0)
    ...
}

搜索屏幕中的可组合项可以工作但不会更新(地图会加载并且我可以导航,但单击按钮后基于收到的列表的标记不会出现):

ComposeMapDemo(
    restaurantList = viewModel.restaurantListResponse,
    myName,
    onMarkerClick = { marker, restaurant -> navController.navigate(route = ... },
)

调用更新餐厅列表函数的按钮:

Button(onClick = {
    //this log statement is always empty, no matter how often I click on the button
    Log.i("Tag", "restaurantList inside button" + viewModel.restaurantListResponse.toString())
    viewModel.getRestaurantList(restaurant)
}) {
    Text(text = "Scan")
}

视图模型:

class LocationViewModel(val context: Context) : ViewModel() {
    var restaurantListResponse: List<Restaurant> by mutableStateOf(listOf())

    fun getRestaurantList(restaurant: Restaurant) {
        viewModelScope.launch {
            try {
                restaurantListResponse = getRestaurantsListRequest(restaurant)
                //this Log.i shows that restaurantListResponse updates
                Log.i(
                    "Tag",
                    "inside the getRestaurantList function of viewmodel" + restaurantListResponse.toString()
                )
            } catch (e: Exception) {
                errorMessage = e.message.toString()
            }
        }
    }
}

谷歌地图可组合项的详细信息(我认为它不相关):

@Composable
fun ComposeMapDemo(
    restaurantList: List<Restaurant>,
    myName: String?,
    onMarkerClick: (Marker, restaurant) -> Boolean,
) {
    val currentOnMarkerClick by rememberUpdatedState(onMarkerClick)
    val cameraPositionState = rememberCameraPositionState()
    val markerState = rememberMarkerState()

    SideEffect {
        if (restaurantList.isEmpty()) return@SideEffect
        val restaurant = restaurantList[0]
        val lat = restaurant?.latitude ?: return@SideEffect
        val lng = restaurant?.longitude ?: return@SideEffect
        val latlng = LatLng(lat, lng)
        markerState.position = latlng
        cameraPositionState.position = CameraPosition.fromLatLngZoom(latlng, 500f)
    }

    GoogleMap(
        modifier = Modifier
            .fillMaxHeight(0.5F)
            .fillMaxWidth(0.75F),
        cameraPositionState = cameraPositionState
    ) {

        for (restaurant in restaurantList) {
            if (restaurant.open == false) {
                continue

            } else {
                Marker(
                    icon = BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_AZURE),
                    state = MarkerState(restaurant.position),
                    title = "${restaurant.name}",
                    onClick = { currentOnMarkerClick(it, restaurant) },
                )
            }
        }
    }
}
android kotlin android-jetpack-compose
1个回答
0
投票

在你的视图模型中,你使用这个:

var restaurantListResponse: List<Restaurant> by mutableStateOf(listOf())

虽然在可组合项中使用委托

by
来简化对变量的访问是一种很好的做法,但在视图模型中执行此操作将从 Compose 中隐藏 MutableState。由于 Compose 只能看到一个普通的 List 并且不知道底层的 State,因此它无法观察 State 的变化,因此当 State 发生变化时它永远不会触发重组。

如果您更改该变量以直接保存状态,它应该可以工作:

val restaurantListResponse: MutableState<List<Restaurant>> = mutableStateOf(listOf())

在任何访问该变量的地方都需要添加

.value

不过,这也有几个缺点。首先,该状态现在可以从视图模型外部写入。您可能应该将其设为私有,并添加另一个变量,将其公开为

State
而不是
MutableState
。但是,您一开始就不应该在视图模型中使用
MutableState
。许多旧文档和示例代码仍然建议这样做,但最佳实践随着时间的推移而发生了变化,为了避免这种方法的一些令人讨厌的边缘情况,现在的首选方法是使用
MutableStateFlow
:

private val _restaurantListResponse = MutableStateFlow<List<Restaurant>>(listOf())
val restaurantListResponse: StateFlow<List<Restaurant>> = _restaurantListResponse.asStateFlow()

在视图模型中,您可以像这样更改值(同时更新日志语句):

_restaurantListResponse.value = getRestaurantsListRequest(restaurant)

在您的撰写代码中,您必须先将 Flow 转换为 State,然后再将其传递给

ComposeMapDemo
:

val restaurantList by viewModel.restaurantListResponse.collectAsStateWithLifecycle()

注意,这需要 gradle 依赖

androidx.lifecycle:lifecycle-runtime-compose

© www.soinside.com 2019 - 2024. All rights reserved.