撰写导航标题未更新
Compose navigation title is not updating
我正在尝试根据我在不同屏幕上更新的 ViewModel 中的实时数据更新 TopAppBar
的 title
。看起来实时数据正在正确更新,但更新没有反映在 TopAppBar 的标题上。这是代码:
class MainActivity : ComponentActivity() {
@ExperimentalFoundationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LazyVerticalGridActivityScreen()
}
}
}
@ExperimentalFoundationApi
@Composable
fun LazyVerticalGridActivityScreen(destinationViewModel: DestinationViewModel = viewModel()) {
val navController = rememberNavController()
var canPop by remember { mutableStateOf(false) }
// getting the latest title value from the view model
val title: String by destinationViewModel.title.observeAsState("")
Log.d("MainActivity_title", title) // not getting called
navController.addOnDestinationChangedListener { controller, _, _ ->
canPop = controller.previousBackStackEntry != null
}
val navigationIcon: (@Composable () -> Unit)? =
if (canPop) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
}
} else {
null
}
Scaffold(
topBar = {
TopAppBar(title = { Text(title) }, navigationIcon = navigationIcon) // updating the title
},
content = {
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController) }
}
}
}
)
}
@ExperimentalFoundationApi
@Composable
fun HomeScreen(navController: NavHostController, destinationViewModel: DestinationViewModel = viewModel()) {
val destinations = DestinationDataSource().loadData()
// updating the title in the view model
destinationViewModel.setTitle("Lazy Grid Layout")
LazyVerticalGrid(
cells = GridCells.Adaptive(minSize = 140.dp),
contentPadding = PaddingValues(8.dp)
) {
itemsIndexed(destinations) { index, destination ->
Row(Modifier.padding(8.dp)) {
ItemLayout(destination, index, navController)
}
}
}
}
@Composable
fun ItemLayout(
destination: Destination,
index: Int,
navController: NavHostController
) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.background(MaterialTheme.colors.primaryVariant)
.fillMaxWidth()
.clickable {
navController.navigate("details/$index")
}
) {
Image(
painter = painterResource(destination.photoId),
contentDescription = stringResource(destination.nameId),
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
Text(
text = stringResource(destination.nameId),
color = Color.White,
fontSize = 14.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
}
@Composable
fun DetailsScreen(
index: String,
navController: NavController,
destinationViewModel: DestinationViewModel = viewModel()
) {
val dataSource = DestinationDataSource().loadData()
val destination = dataSource[index.toInt()]
val destinationName = stringResource(destination.nameId)
val destinationDesc = stringResource(destination.descriptionId)
val destinationImage = painterResource(destination.photoId)
// updating the title in the view model
destinationViewModel.setTitle("Destination Details")
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
Image(
painter = destinationImage,
contentDescription = destinationName,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Text(
text = destinationName,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 16.dp)
)
Text(text = destinationDesc, lineHeight = 24.sp)
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = {
navController.navigate("home") {
popUpTo("home") { inclusive = true }
}
},
modifier = Modifier.padding(top = 24.dp)
) {
Image(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryVariant),
modifier = Modifier.size(20.dp)
)
Text("Back to Destinations", modifier = Modifier.padding(start = 16.dp))
}
}
}
}
}
编辑:ViewModel
class DestinationViewModel : ViewModel() {
private var _title = MutableLiveData("")
val title: LiveData<String>
get() = _title
fun setTitle(newTitle: String) {
_title.value = newTitle
Log.d("ViewModel_title", _title.value.toString())
Log.d("ViewModelTitle", title.value.toString())
}
}
谁能帮忙找出错误?谢谢!
编辑:
这里是项目的GitHublink:https://github.com/rawhasan/compose-exercise-lazy-vertical-grid
你可能想看看这里
此外,您不需要粘贴问题中的所有代码。只是必要的一点。如果问题中缺少某些内容,我们将要求它。只需将 LiveData
更改为 mutableStateOf
,然后让我知道这是否解决了您的问题。另外,修改类型后不需要再调用observeAsState
。只需参考 link 它包含所有信息。
谢谢
它不起作用的原因是因为它们是在不同范围内创建的不同对象。
当您使用 navController
时,每个目的地都会有自己的 viewModel()
创建范围。根据设计,您可能为每个目的地都有一个视图模型,例如 HomeViewModel
、DestinationViewModel
等
您无法从当前目标范围访问其他目标视图模型,也无法从外部范围访问视图模型(您正在尝试这样做)
您可以做的不是尝试使用 viewModel()
检索它,而是可以将外部范围视图模型传递给您的可组合项:
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController, destinationViewModel) }
}
中查看有关 viewModel()
的更多详细信息
您的代码的另一个问题是您在可组合函数中调用 destinationViewModel.setTitle("Lazy Grid Layout")
。所以这段代码会被多次调用,可能会导致重组
要在可组合项中调用任何操作,您需要使用 side-effects。 LaunchedEffect
在这种情况下:
LaunchedEffect(Unit) {
destinationViewModel.setTitle("Destination Details")
}
视图出现后只会调用一次。如果你需要更频繁地调用它,你需要指定key而不是Unit
,所以每次key改变时都会调用它
我正在尝试根据我在不同屏幕上更新的 ViewModel 中的实时数据更新 TopAppBar
的 title
。看起来实时数据正在正确更新,但更新没有反映在 TopAppBar 的标题上。这是代码:
class MainActivity : ComponentActivity() {
@ExperimentalFoundationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LazyVerticalGridActivityScreen()
}
}
}
@ExperimentalFoundationApi
@Composable
fun LazyVerticalGridActivityScreen(destinationViewModel: DestinationViewModel = viewModel()) {
val navController = rememberNavController()
var canPop by remember { mutableStateOf(false) }
// getting the latest title value from the view model
val title: String by destinationViewModel.title.observeAsState("")
Log.d("MainActivity_title", title) // not getting called
navController.addOnDestinationChangedListener { controller, _, _ ->
canPop = controller.previousBackStackEntry != null
}
val navigationIcon: (@Composable () -> Unit)? =
if (canPop) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
}
} else {
null
}
Scaffold(
topBar = {
TopAppBar(title = { Text(title) }, navigationIcon = navigationIcon) // updating the title
},
content = {
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController) }
}
}
}
)
}
@ExperimentalFoundationApi
@Composable
fun HomeScreen(navController: NavHostController, destinationViewModel: DestinationViewModel = viewModel()) {
val destinations = DestinationDataSource().loadData()
// updating the title in the view model
destinationViewModel.setTitle("Lazy Grid Layout")
LazyVerticalGrid(
cells = GridCells.Adaptive(minSize = 140.dp),
contentPadding = PaddingValues(8.dp)
) {
itemsIndexed(destinations) { index, destination ->
Row(Modifier.padding(8.dp)) {
ItemLayout(destination, index, navController)
}
}
}
}
@Composable
fun ItemLayout(
destination: Destination,
index: Int,
navController: NavHostController
) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.background(MaterialTheme.colors.primaryVariant)
.fillMaxWidth()
.clickable {
navController.navigate("details/$index")
}
) {
Image(
painter = painterResource(destination.photoId),
contentDescription = stringResource(destination.nameId),
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
Text(
text = stringResource(destination.nameId),
color = Color.White,
fontSize = 14.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
}
@Composable
fun DetailsScreen(
index: String,
navController: NavController,
destinationViewModel: DestinationViewModel = viewModel()
) {
val dataSource = DestinationDataSource().loadData()
val destination = dataSource[index.toInt()]
val destinationName = stringResource(destination.nameId)
val destinationDesc = stringResource(destination.descriptionId)
val destinationImage = painterResource(destination.photoId)
// updating the title in the view model
destinationViewModel.setTitle("Destination Details")
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
Image(
painter = destinationImage,
contentDescription = destinationName,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Text(
text = destinationName,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 16.dp)
)
Text(text = destinationDesc, lineHeight = 24.sp)
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = {
navController.navigate("home") {
popUpTo("home") { inclusive = true }
}
},
modifier = Modifier.padding(top = 24.dp)
) {
Image(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryVariant),
modifier = Modifier.size(20.dp)
)
Text("Back to Destinations", modifier = Modifier.padding(start = 16.dp))
}
}
}
}
}
编辑:ViewModel
class DestinationViewModel : ViewModel() {
private var _title = MutableLiveData("")
val title: LiveData<String>
get() = _title
fun setTitle(newTitle: String) {
_title.value = newTitle
Log.d("ViewModel_title", _title.value.toString())
Log.d("ViewModelTitle", title.value.toString())
}
}
谁能帮忙找出错误?谢谢!
编辑:
这里是项目的GitHublink:https://github.com/rawhasan/compose-exercise-lazy-vertical-grid
你可能想看看这里
此外,您不需要粘贴问题中的所有代码。只是必要的一点。如果问题中缺少某些内容,我们将要求它。只需将 LiveData
更改为 mutableStateOf
,然后让我知道这是否解决了您的问题。另外,修改类型后不需要再调用observeAsState
。只需参考 link 它包含所有信息。
谢谢
它不起作用的原因是因为它们是在不同范围内创建的不同对象。
当您使用 navController
时,每个目的地都会有自己的 viewModel()
创建范围。根据设计,您可能为每个目的地都有一个视图模型,例如 HomeViewModel
、DestinationViewModel
等
您无法从当前目标范围访问其他目标视图模型,也无法从外部范围访问视图模型(您正在尝试这样做)
您可以做的不是尝试使用 viewModel()
检索它,而是可以将外部范围视图模型传递给您的可组合项:
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController, destinationViewModel) }
}
中查看有关 viewModel()
的更多详细信息
您的代码的另一个问题是您在可组合函数中调用 destinationViewModel.setTitle("Lazy Grid Layout")
。所以这段代码会被多次调用,可能会导致重组
要在可组合项中调用任何操作,您需要使用 side-effects。 LaunchedEffect
在这种情况下:
LaunchedEffect(Unit) {
destinationViewModel.setTitle("Destination Details")
}
视图出现后只会调用一次。如果你需要更频繁地调用它,你需要指定key而不是Unit
,所以每次key改变时都会调用它