Navigating to a new page in Kotlin Compose - kotlin

I need help figuring out how when I click on the card it takes me to a whole new page that says "Success". Right now when I click it says "Success" but right above the card.
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun AlbumList(title: String, url: String){
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "MainScreen") {
composable("MainScreen") {MainActivity() }
composable("album") { testSwitch(title, url) }
}
Card(
shape = RoundedCornerShape(5.dp),
elevation = 16.dp,
modifier = Modifier
.combinedClickable(
onClick = {
navController.navigate("album"){
popUpTo("album") { inclusive = true }
}
}
)
.padding(start = 16.dp, end = 16.dp, top = 5.dp, bottom = 5.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Column(Modifier.padding(8.dp)) {
Text(
text = title,
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onSurface,
)
Text(
text = url,
style = MaterialTheme.typography.body2,
)
}
}
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun testSwitch(title: String, url: String) {
Text("Success")
}
enter image description here

As defined in the documentation:
Define your NavHost and the respective routes...
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { ProfileScreen(navController) }
composable("friendslist") { FriendsListScreen(navController) }
}
If you need to call the FriendsListScreen from ProfileScreen:
#Composable
fun ProfileScreen(navController: NavController) {
/*...*/
Button(onClick = { navController.navigate("friendslist") }) {
Text(text = "Navigate next")
}
/*...*/
}

Related

Jetpack compose scrollable table

I need to have a component that scrolls vertically and horizontally.
Here is what I did so far:
#Composable
fun Screen() {
val scope = rememberCoroutineScope()
val scrollOffset = remember {
mutableStateOf(value = 0F)
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(10) {
SimpleRow(
onScrollOffsetChange = {
scrollOffset.value = it
}
)
}
}
}
#Composable
private fun SimpleRow(
onScrollOffsetChange: (Float) -> Unit,
) {
val lazyRowState = rememberLazyListState()
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = lazyRowState
) {
item {
Text(
text = "firstText"
)
}
for (i in 1..30) {
item {
Text(text = "qwerty")
}
}
}
}
When anyone of these SimpleRow is scrolled I want to scroll all of the SimpleRows together.
And I do not know how many SimpleRows I have because they came from our server.
Is there any kind of composable or trick that can do it for me?
You can use multiple rows inside a column that scrolls horizontally and vertically, like this:
#Composable
fun MainContent() {
val scrollStateHorizontal = rememberScrollState()
val scrollStateVertical = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollStateVertical)
.horizontalScroll(state = scrollStateHorizontal)
) {
repeat(40){ c ->
Row {
repeat(40){ r ->
Item("col: $c | row: $r")
}
}
}
}
}
#Composable
fun Item(text: String) {
Text(
modifier = Modifier
.padding(5.dp)
.background(Color.Gray),
text = text,
color = Color.White
)
}

Why textfield not catch text inside and don't add to database

When I press button with onSendClicked is not adding text from textfield. I don't know where not catching text. I guess is somewhere mistake with viewmodel, becouse viewmodel don't get new value.
fun AddBar(
onSendClicked: () -> Unit
){
Row(Modifier.padding(5.dp)) {
var title by remember {
mutableStateOf("")
}
TextField(
value = title,
onValueChange = { title = it }
)
IconButton(onClick = {
onSendClicked()})
{
Icon(imageVector = Icons.Filled.ArrowForward, contentDescription = "Send Icon")
}
}
}
#Composable
fun MainScreen(
basketViewModel: BasketViewModel,
){
AddBar(onSendClicked = { basketViewModel.addToBasket() })
}
And viewModel
val id: MutableState<Int> = mutableStateOf(0)
val title: MutableState<String> = mutableStateOf("")
fun addToBasket(){
viewModelScope.launch(Dispatchers.IO) {
val basket = Basket(
title = title.value,
isChecked = false
)
repository.addToBasket(basket = basket)
}
}
Help....
You are never using the title state of the ViewModel. You are only updating the local title. For this to you have to stop using local title and just replace it with the title from viewModel. Something like that:
fun AddBar(
title: MutableState<String>,
onSendClicked: () -> Unit
){
Row(Modifier.padding(5.dp)) {
TextField(
value = title.value,
onValueChange = { title.value = it }
)
IconButton(onClick = {
onSendClicked()})
{
Icon(imageVector = Icons.Filled.ArrowForward, contentDescription = "Send Icon")
}
}
}
#Composable
fun MainScreen(
basketViewModel: BasketViewModel,
){
AddBar(
title = basketViewModel.title,
onSendClicked = { basketViewModel.addToBasket() }
)
}
You define the title both in view model and your main screen. Use the one in your view model.
fun AddBar(
title: String,
onValueChange: (String) -> Unit,
onSendClicked: () -> Unit
){
Row(Modifier.padding(5.dp)) {
TextField(
value = title,
onValueChange = { onValueChange(it) }
)
IconButton(
onClick = { onSendClicked() }
) {
Icon(
imageVector = Icons.Filled.ArrowForward,
contentDescription = "Send Icon"
)
}
}
}
#Composable
fun MainScreen(
basketViewModel: BasketViewModel,
){
AddBar(
title = basketViewModel.title,
onValueChange = { basketViewModel.changeTitle(it) }
onSendClicked = { basketViewModel.addToBasket() }
)
}
class BasketViewModel : ViewModel() {
var title by mutableStateOf("")
private set
fun changeTitle(value: String) {
title = value
}
fun addToBasket(){
viewModelScope.launch(Dispatchers.IO) {
val basket = Basket(
title = title.value,
isChecked = false
)
repository.addToBasket(basket = basket)
}
}
}

Recompose a Canvas while maintaining the previous drawing

This function draw figures on canvas but when I clicked on button its shows only latest clicked figure but I want to show all figures which was clicked previous.
I think drawScope state can be remembered as a mutable state but how to achieve it.
#Composable
fun PaintBrushLayout(
modifier: Modifier = Modifier,
) {
var clicked by remember {
mutableStateOf(false)
}
var figName by remember {
mutableStateOf("")
}
Column(
verticalArrangement = Arrangement.Bottom, modifier = modifier.fillMaxSize()
) {
Canvas(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(.9f)
) {
Log.d("in Paint", "PaintBrushLayout: $this")
if (clicked) {
Log.d("TAG", "PaintBrushLayout: $figName")
when (figName) {
"Circle" -> {
drawCircleFig(color = Color.Blue)
}
"Rectangle" -> {
drawRectangleFig()
}
"Oval" -> {
drawOvalFig(color = Color.Gray)
}
}
}
}
Row(
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
) {
BottomLayout("Circle") { click, name ->
clicked = click
figName = name
}
BottomLayout("Rectangle") { click, name ->
clicked = click
figName = name
}
BottomLayout("Oval") { click, name ->
clicked = click
figName = name
}
BottomLayout("Choose Color") { click, name ->
clicked = click
// figName = name
}
}
}
}
Create Bottom Layout of Buttons
#Composable
fun BottomLayout(
btnName: String,
click: (Boolean, String) -> Unit
) {
Button(onClick = {
click(true, btnName)
}, modifier = Modifier.padding(4.dp)) {
Text(text = btnName)
}
}
Draw Circle Figure on canvas
fun DrawScope.drawCircleFig(color: Color = Color.Red) {
drawCircle(
color = color
)
}
Draw Rectangle Figure on canvas
fun DrawScope.drawRectangleFig(color: Color = Color.Red) {
drawRect(
color = color
)
}
Draw Oval Figure on canvas
fun DrawScope.drawOvalFig(color: Color = Color.Red) {
drawOval(
color = color
)
}
I don't know if it's possible to recompose a Canvas and maintain its state prior to what it was previously, allowing you to drawn on top of what is already there. One solution is to keep track of a queue of the operations that you want to perform and then each time the Canvas is recomposed, you iterate through the queue and draw each item. If this is a drawingn app, it has the benefit of allowing the user to undo the previous steps. I also noticed that the code you provided doesn't recompose when you click on a button. I've noticed this issue before. The solution is to assign the remembered value to another value. Here is the sample on using a queue:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val queue = mutableListOf<DrawingObject>()
PaintBrushLayout(queue = queue)
}
}
}
#Composable
fun PaintBrushLayout(
modifier: Modifier = Modifier,
queue: MutableList<DrawingObject>
) {
var currentDrawingObject by remember { mutableStateOf(DrawingObject.None) }
var c = currentDrawingObject // This fixes a bug in recomposition.
fun addToQueue(drawingObject: DrawingObject) {
queue.add(drawingObject)
currentDrawingObject = drawingObject
}
Column(
verticalArrangement = Arrangement.Bottom, modifier = modifier.fillMaxSize()
) {
Canvas(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(.9f)
) {
queue.forEach { drawingObject ->
when (drawingObject) {
DrawingObject.Circle -> {
drawCircleFig(color = Color.Blue)
}
DrawingObject.Rectangle -> {
drawRectangleFig()
}
DrawingObject.Oval -> {
drawOvalFig(color = Color.Gray)
}
}
}
}
Row(
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
) {
BottomLayout("Circle") { click, name ->
addToQueue(DrawingObject.Circle)
}
BottomLayout("Rectangle") { click, name ->
addToQueue(DrawingObject.Rectangle)
}
BottomLayout("Oval") { click, name ->
addToQueue(DrawingObject.Oval)
}
BottomLayout("Choose Color") { click, name ->
}
}
}
}
#Composable
fun BottomLayout(
btnName: String,
click: (Boolean, String) -> Unit
) {
Button(onClick = {
click(true, btnName)
}, modifier = Modifier.padding(4.dp)) {
Text(text = btnName)
}
}
fun DrawScope.drawCircleFig(color: Color = Color.Red) {
drawCircle(
radius = 300f,
color = color
)
}
fun DrawScope.drawRectangleFig(color: Color = Color.Red) {
drawRect(
size = Size(200f, 200f),
color = color
)
}
fun DrawScope.drawOvalFig(color: Color = Color.Red) {
drawOval(
size = Size(100f, 100f),
color = color
)
}
enum class DrawingObject {
None,
Circle,
Rectangle,
Oval
}

JetpackCompose Navigation Nested Graphs cause "ViewModelStore should be set before setGraph call" exception

I am trying to apply Jetpack Compose navigation into my application.
My Screens: Login/Register screens and Bottom navbar screens(call, chat, settings).
I already found out that the best way to do this is using nested graphs.
But I keep getting ViewModelStore should be set before setGraph call exception. However, I don't think this is the right exception.
My navigation is already in the latest version. Probably my nested graph logic is not right.
Requirement:
I want to be able to navigate from the Login or Register screen to any BottomBar Screen & reverse
#Composable
fun SetupNavGraph(
navController: NavHostController,
userViewModel: UserViewModel
) {
NavHost(
navController = navController,
startDestination = BOTTOM_BAR_GRAPH_ROUTE,
route = ROOT_GRAPH_ROUTE
) {
loginNavGraph(navController = navController, userViewModel)
bottomBarNavGraph(navController = navController, userViewModel)
}
}
NavGraph.kt
fun NavGraphBuilder.loginNavGraph(
navController: NavHostController,
userViewModel: UserViewModel
) {
navigation(
startDestination = Screen.LoginScreen.route,
route = LOGIN_GRAPH_ROUTE
) {
composable(
route = Screen.LoginScreen.route,
content = {
LoginScreen(
navController = navController,
loginViewModel = userViewModel
)
})
composable(
route = Screen.RegisterScreen.route,
content = {
RegisterScreen(
navController = navController,
loginViewModel = userViewModel
)
})
}
}
LoginNavGraph.kt
fun NavGraphBuilder.bottomBarNavGraph(
navController: NavHostController,
userViewModel: UserViewModel
) {
navigation(
startDestination = Screen.AppScaffold.route,
route = BOTTOM_BAR_GRAPH_ROUTE
) {
composable(
route = Screen.AppScaffold.route,
content = {
AppScaffold(
navController = navController,
userViewModel = userViewModel
)
})
}
}
BottomBarNavGraph.kt
#Composable
fun AppScaffold(
navController: NavHostController,
userViewModel: UserViewModel
) {
val scaffoldState = rememberScaffoldState()
Scaffold(
bottomBar = {
BottomBar(mainNavController = navController)
},
scaffoldState = scaffoldState,
) {
NavHost(
navController = navController,
startDestination = NavigationScreen.EmergencyCallScreen.route
) {
composable(NavigationScreen.EmergencyCallScreen.route) {
EmergencyCallScreen(
navController = navController,
loginViewModel = userViewModel
)
}
composable(NavigationScreen.ChatScreen.route) { ChatScreen() }
composable(NavigationScreen.SettingsScreen.route) {
SettingsScreen(
navController = navController,
loginViewModel = userViewModel
)
}
}
}
}
AppScaffold.kt
#Composable
fun BottomBar(mainNavController: NavHostController) {
val items = listOf(
NavigationScreen.EmergencyCallScreen,
NavigationScreen.ChatScreen,
NavigationScreen.SettingsScreen,
)
BottomNavigation(
elevation = 5.dp,
) {
val navBackStackEntry by mainNavController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.map {
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = it.icon),
contentDescription = it.title
)
},
label = {
Text(
text = it.title
)
},
selected = currentRoute == it.route,
selectedContentColor = Color.White,
unselectedContentColor = Color.White.copy(alpha = 0.4f),
onClick = {
mainNavController.navigate(it.route) {
mainNavController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
restoreState = true
launchSingleTop = true
}
},
)
}
}
}
BottomBar.kt
const val ROOT_GRAPH_ROUTE = "root"
const val LOGIN_GRAPH_ROUTE = "login_register"
const val BOTTOM_BAR_GRAPH_ROUTE = "bottom_bar"
sealed class Screen(val route: String) {
object LoginScreen : Screen("login_screen")
object RegisterScreen : Screen("register_screen")
object AppScaffold : Screen("app_scaffold")
}
Screen.kt
sealed class NavigationScreen(val route: String, val title: String, #DrawableRes val icon: Int) {
object EmergencyCallScreen : NavigationScreen(
route = "emergency_call_screen",
title = "Emergency Call",
icon = R.drawable.ic_phone
)
object ChatScreen :
NavigationScreen(
route = "chat_screen",
title = "Chat",
icon = R.drawable.ic_chat)
object SettingsScreen : NavigationScreen(
route = "settings_screen",
title = "Settings",
icon = R.drawable.ic_settings
)
}
NavigationScreen.kt
After struggling some time with this issue, I made my way out by using two separated NavHost. It might not be the right way to do it but it works at the moment. You can find the example source code here:
https://github.com/talhaoz/JetPackCompose-LoginAndBottomBar
Hope they make the navigation easier on upcoming releases.
Nesting of NavHost is not allowed. It results in ViewModelStore should be set before setGraph call Exception. Generally, the bottom nav is outside of the NavHost, which is what the docs show. The recommended approach is a single NavHost, where you hide and show your bottom nav based on what destination you are on.
One NavHost, one NavHostController. Create a new NavHostController in front of the nested NavHost on AppScaffold.
Have similar issue when implement this common UI pattern:
HomePage(with BottomNavigationBar), this page is hosted by Inner nav controller
click some links of one page
navigate to a new page (with new Scaffold instance). This page is hosted by Outer nav controller.
Kinda hacked this issue by using 2 NavHost with 2 navController instance.
Basic idea is using some msg channel to tell the outer nav controller, a Channel in my case.
private val _pages: Channel<String> = Channel()
var pages = _pages.receiveAsFlow()
#Composable
fun Route() {
val navController1 = rememberNavController()
LaunchedEffect(true) {
pages.collect { page ->
navController1.navigate("detail")
}
}
NavHost(navController = navController1, startDestination = "home") {
composable("home") { MainPage() }
composable("detail") { DetailPage() }
}
}
#Composable
fun MainPage() {
val navController2 = rememberNavController()
val onTabSelected = { tab: String ->
navController2.navigate(tab) {
popUpTo(navController2.graph.findStartDestination().id) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
Scaffold(topBar = { TopAppBar(title = { Text("Home Title") }) },
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController2.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
BottomNavigationItem(
selected = currentDestination?.hierarchy?.any { it.route == "tab1" } == true,
onClick = { onTabSelected("tab1") },
icon = { Icon(imageVector = Icons.Default.Favorite, "") },
label = { Text("tab1") }
)
BottomNavigationItem(
selected = currentDestination?.hierarchy?.any { it.route == "tab2" } == true,
onClick = { onTabSelected("tab2") },
icon = { Icon(imageVector = Icons.Default.Favorite, "") },
label = { Text("tab2") }
)
BottomNavigationItem(
selected = currentDestination?.hierarchy?.any { it.route == "tab3" } == true,
onClick = { onTabSelected("tab3") },
icon = { Icon(imageVector = Icons.Default.Favorite, "") },
label = { Text("tab3") }
)
}
}
) { value ->
NavHost(navController = navController2, startDestination = "tab1") {
composable("tab1") { Home() }
composable("tab2") { Text("tab2") }
composable("tab3") { Text("tab3") }
}
}
}
class HomeViewModel: ViewModel()
#Composable
fun Home(viewModel: HomeViewModel = HomeViewModel()) {
Button(
onClick = {
viewModel.viewModelScope.launch {
_pages.send("detail")
}
},
modifier = Modifier.padding(all = 16.dp)
) {
Text("Home", modifier = Modifier.padding(all = 16.dp))
}
}
#Composable
fun DetailPage() {
Scaffold(topBar = { TopAppBar(title = { Text("Detail Title") }) }) {
Text("Detail")
}
}
Cons:
App needs to maintain UI stack information.
It's even harder to cope with responsive layout.
use rememberNavController() for your function
fun YourFunction(
navController: NavHostController = rememberNavController()
)
In my case, I had to create nav controller (for bottom bar) with in home screen.
#AndroidEntryPoint
class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContent {
Theme {
Surface(modifier = Modifier.fillMaxSize()) {
AppContainer()
}
}
}
}
}
#Composable
fun AppContainer() {
val mainNavController = rememberNavController()
// This was causing the issue. I moved this to HomeScreen.
// val bottomNavController = rememberNavController()
Box(
modifier = Modifier.background(BackgroundColor)
) {
NavGraph(mainNavController)
}
}
#Composable
fun HomeScreen(mainNavController: NavController) {
val bottomBarNavController = rememberNavController()
}

Compose navigation title is not updating

I am trying to update the title of the TopAppBar based on a live data in the ViewModel, which I update on different screens. It looks like the live data is getting updated properly, but the update is not getting reflected on the title of the TopAppBar. Here is the code:
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))
}
}
}
}
}
EDIT: The 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())
}
}
Can anyone please help to find the bug? Thanks!
Edit:
Here is the GitHub link of the project: https://github.com/rawhasan/compose-exercise-lazy-vertical-grid
The reason it's not working is because those are different objects created in different scopes.
When you're using a navController, each destination will have it's own scope for viewModel() creation. By the design you may have a view model for each destination, like HomeViewModel, DestinationViewModel, etc
You can't access an other destination view model from current destination scope, as well as you can't access view model from the outer scope(which you're trying to do)
What you can do, is instead of trying to retrieve it with viewModel(), you can pass outer scope view model to your composable:
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController, destinationViewModel) }
}
Check out more details about viewModel() in the documentation
Another problem with your code is that you're calling destinationViewModel.setTitle("Lazy Grid Layout") inside composable function. So this code will be called many times, which may lead to recomposition.
To call any actions inside composable, you need to use side-effects. LaunchedEffect in this case:
LaunchedEffect(Unit) {
destinationViewModel.setTitle("Destination Details")
}
This will be called only once after view appear. If you need to call it more frequently, you need to specify key instead of Unit, so it'll be recalled each time when the key changes
You might wanna have a look here https://stackoverflow.com/a/68671477/15880865
Also, you do not need to paste all the code in the question. Just the necessary bit. We shall ask for it if something is missing in the question. Just change your LiveData to mutableStateOf and then let me know if that fixed your problem. Also, you do not need to call observeAsState after modifying the type. Just refer to the link it contains all the info.
Thanks