https://kotlinlang.org logo
#compose
Title
# compose
p

Pablo

03/04/2024, 9:59 PM
Need help understanding how to merge adaptative patterns with navigation in Compose. Having an app that follows a List-List-Detail scheme (categories, places of that category, and detail of the place), in expanded screens... how should I organize the UI? What should I draw in the navhost composable routes? When the scren is compact its very simple, each nagiation route should display a list, a list or a detail, but what happens when it is a expanded screen? There aren't anymore three navigation routes... first the app should display two lists, the categories and the places of that categories, each list on a half of the screen. Then, when the user presses a place, the ui should change to another screen which displays the places on the left and the detail on the right. I tryed it but I don't like the implementation, for sure there will be a better way to achieve this.
This is a sample code of what I tryed:
Copy code
NavHost(
            navController = navController,
            startDestination = Screen.CategoriesListScreen.name,
            modifier = Modifier
                .padding(innerPadding)
                .fillMaxHeight()
        ) {
            if (screenType == ScreenType.LIST_ONLY) {
                composable(route = Screen.CategoriesListScreen.name) {
                    ListScreen(
                        listItems = CategoriesProvider.categories,
                        onItemClicked = {
                            viewModel.updateCurrentCategory(it as Category)
                            navController.navigate(Screen.PlacesListScreen.name)
                        },
                        modifier = Modifier
                    )
                }
                composable(route = Screen.PlacesListScreen.name) {
                    ListScreen(
                        listItems = uiState.placesList,
                        onItemClicked = {
                            viewModel.updateCurrentPlace(it as Place)
                            navController.navigate(Screen.DetailScreen.name)
                        },
                        modifier = Modifier
                    )
                }
                composable(route = Screen.DetailScreen.name) {
                    PlaceDetail(
                        place = uiState.selectedPlace
                    )
                }
            } else {
                composable(route = Screen.CategoriesListScreen.name) {
                    Row {
                        ListScreen(
                            listItems = CategoriesProvider.categories,
                            onItemClicked = {
                                viewModel.updateCurrentCategory(it as Category)
                                navController.navigate(Screen.PlacesListScreen.name)
                            },
                            modifier = Modifier.weight(1f)
                        )
                        ListScreen(
                            listItems = uiState.placesList,
                            onItemClicked = {
                                viewModel.updateCurrentPlace(it as Place)
                                navController.navigate(Screen.DetailScreen.name)
                            },
                            modifier = Modifier.weight(1f)
                        )
                    }
                }
                composable(route = Screen.PlacesListScreen.name) {
                    Row {
                        ListScreen(
                            listItems = uiState.placesList,
                            onItemClicked = {
                                viewModel.updateCurrentPlace(it as Place)
                                navController.navigate(Screen.DetailScreen.name)
                            },
                            modifier = Modifier.weight(1f)
                        )
                        PlaceDetail(
                            place = uiState.selectedPlace,
                            modifier = Modifier.weight(1f)
                        )
                    }
                }
                composable(route = Screen.DetailScreen.name) {
                    Row {
                        ListScreen(
                            listItems = uiState.placesList,
                            onItemClicked = {
                                viewModel.updateCurrentPlace(it as Place)
                                navController.navigate(Screen.DetailScreen.name)
                            },
                            modifier = Modifier.weight(1f)
                        )
                        PlaceDetail(
                            place = uiState.selectedPlace,
                            modifier = Modifier.weight(1f)
                        )
                    }
                }
            }
        }
i

Ian Lake

03/04/2024, 10:37 PM
Did you watch the talk linked to you 4 days ago? It went through exactly this: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1709246266899579?thread_ts=1709241539.949939&cid=CJLTWPH7S
p

Pablo

03/04/2024, 10:44 PM
yes, I watched it, please, can you tell me exactly at which point they are explaining exactly how to resolve this issue?
I think I can't relate the video explanations with this issue
Also, another issue I found is that In my compose starter mind I think the navigation graph should be different in expanded devices, because there is only two screen possibles instead of three, so I created this:
Copy code
enum class Screen {
    CategoriesListScreen,
    PlacesListScreen,
    DetailScreen
}

enum class ExpandedScreen {
    CategoriesAndPlacesScreen,
    PlacesAndDetailScreen
}
But that is not correct because it crashes when changing device size dinamically. I think i'm completly loss with this
i

Ian Lake

03/04/2024, 10:47 PM
No, you don't ever change your navigation graph based on screen size - you change the contents of a screen. That's the whole second half of the talk, starting at this point:

https://youtu.be/LTLQhC6VadI?t=522

p

Pablo

03/04/2024, 10:47 PM
but there are only two screens when it's expanded device
in the navigation route enum, each route represents one screen
i

Ian Lake

03/04/2024, 10:49 PM
Just keep watching the talk, the whole point is that you have one navigation destination that adapts to every screen size. On phones, it shows one list pane or the other detail pane (including a
BackHandler
to handle system back separately from the NavHost's back handling to return to the list pane), but on larger devices it shows both panes side by side:

https://youtu.be/LTLQhC6VadI?t=615

p

Pablo

03/04/2024, 10:52 PM
but then what happens with the routes?
there are three routes
in the video case, supposedly there are two routes, but I can't find them on the video
In my case there are three routes:
Copy code
enum class Screen {
    CategoriesListScreen,
    PlacesListScreen,
    DetailScreen
}
i

Ian Lake

03/04/2024, 10:53 PM
right, there's only two routes
p

Pablo

03/04/2024, 10:53 PM
in expanded, there are two screens:
CategoriesListScreen,
PlacesListScreen,
and
PlacesListScreen,
DetailScreen
i

Ian Lake

03/04/2024, 10:54 PM
every screen in the NavHost has to fill the entire box that is the NavHost - that's the very first thing he says in that first timestamp I linked
p

Pablo

03/04/2024, 10:54 PM
I understand that
but the three routes on my case are merged in two possible screens
and I don't understand how to manage it
the first screen should have the first two routes combined:
PlacesListScreen,
DetailScreen
The second screen should have the second and the third routes combined
PlacesListScreen,
DetailScreen
I understand that I must combine these composables, I can do that
the issue is with the navigation code and which routes should display each combination, and what to do with the third possible route.
Supposedly I have to create three "composable" components in the navhost
but I have only two screens
am I explaining the issue? can you understand me?
i

Ian Lake

03/04/2024, 11:00 PM
If you have three screens, none of which take the full size of the NavHost on a tablet screen, then they shouldn't be separate destinations in your NavHost at all. You have one destination that has a sort of three pane layout inside of it. You wouldn't use the Navigation APIs within a single screen at all
p

Pablo

03/04/2024, 11:03 PM
No, i have two possible screens when in a expanded device
the first screen should have the first two routes combined:
CategoriesListScreen,
PlacesListScreen,
The second screen should have the second and the third routes combined
PlacesListScreen,
DetailScreen
i

Ian Lake

03/04/2024, 11:04 PM
Please verify you have the names right there - you pasted the same names twice
p

Pablo

03/04/2024, 11:04 PM
corrected
i

Ian Lake

03/04/2024, 11:04 PM
Right, that's one screen that has three panes - just only two of them are visible at once
CategoriesListScreen
slides out to the left,
PlacesListScreen
moves over,
DetailScreen
slides in, etc. - that is the only way that the state of
PlaceesListScreen
is going to be stable and kept as you move back and forth
None of those screens take up the full screen of a tablet, hence, they are not separate routes. You just have one responsive UI with three panes
p

Pablo

03/04/2024, 11:06 PM
then, in the video example, we have only one screen with two panes
so, what happens with the two possible routes of that video sample when it is in a expanded device?
why haves two routes if it is only a screen with two panes which are visible at the same time in a expanded device?
i

Ian Lake

03/04/2024, 11:10 PM
It isn't two routes on any screen size - the graph never changes based on screen size. It is always one route that has a responsive UI. That responsive UI chooses whether you only see one pane at a time or both panes side by side
p

Pablo

03/04/2024, 11:10 PM
well, I'm completly loss now
you told me previously that the video sample had two routes "there's only two routes"
In fact I thought that every list detail scheme will result in two routes in a navhost component
but now you mean that there are only one route?
i

Ian Lake

03/04/2024, 11:14 PM
Yeah, it took a while for me to understand you have a three pane layout where the middle pane swaps from the right to the left side of the screen
Any route you have takes up the full size of the NavHost. It is the responsibility of that single route to adapt itself to fill the entire space provided to it. If you want to display something side by side on the largest screen size, then it needs to all be part of a single route, since that single route is filling the entire area
p

Pablo

03/04/2024, 11:17 PM
yes, I understand that
my issue is not with that
let me try to explain it again
in compact size, it's ok, I simple do this:
Copy code
NavHost(
    navController = navController,
    startDestination = Screen.CategoriesListScreen.name,
    modifier = modifier.fillMaxHeight()
) {
    composable(route = Screen.CategoriesListScreen.name) {
        ListScreen(
            listItems = CategoriesProvider.categories,
            onItemClicked = { onCategoryClicked(it as Category) },
            modifier = Modifier
        )
    }
    composable(route = Screen.PlacesListScreen.name) {
        ListScreen(
            listItems = uiState.placesList,
            onItemClicked = { onPlaceClicked(it as Place) },
            modifier = Modifier
        )
    }
    composable(route = Screen.DetailScreen.name) {
        PlaceDetail(
            place = uiState.selectedPlace,
            onPlaceBackPressed
        )
    }
}
but what should I put inside the navhost in expanded size?
i

Ian Lake

03/04/2024, 11:18 PM
And like I said, if you want
PlacesListScreen
to have the same state whether you are in `CategoriesListScreen`+`PlacesListScreen` or `PlacesListScreen`+`DetailScreen`, then that all needs to be in one route
With one three pane layout
p

Pablo

03/04/2024, 11:19 PM
I can't understand how to apply your solution to my problem
please, check my source code for compact size
and tell me what composable objects should be inside the navhost when in expanded size
i can't figure out
you can see the three routes for compact size
can't understand how to translate that to expanded size
i

Ian Lake

03/04/2024, 11:24 PM
You're doing the conversion the wrong way - start with the biggest screen size: what takes up the entire size on a tablet. It is one three pane layout that goes from `CategoriesListScreen`+`PlacesListScreen` to moving the
PlacesListScreen
from the right half of the screen to the left to be `PlacesListScreen`+`DetailScreen`. That's one route. You don't even need Navigation if that's your entire app (but maybe you have a photo viewer that takes the full screen - that would be a good example of where you need a second route) Now that you've gotten your tablet layout working, your problem is much easier: how does your three pane layout adapt to screen sizes that don't fit two panes side by side. Easy: it only displays one pane at a time. Nothing else changes, your three pane layout just adapts to smaller screen sizes
p

Pablo

03/04/2024, 11:27 PM
ok forgot the three screens, let's get back to the video sample, imagine that my app is equal to the video sample and it is a simple LIST DETAIL app, it haves two routes in the navigation component:
Copy code
NavHost(
    navController = navController,
    startDestination = Screen.CategoriesListScreen.name,
    modifier = modifier.fillMaxHeight()
) {
    composable(route = Screen.PlacesListScreen.name) {
        ListScreen(
            listItems = uiState.placesList,
            onItemClicked = { onPlaceClicked(it as Place) },
            modifier = Modifier
        )
    }
    composable(route = Screen.DetailScreen.name) {
        PlaceDetail(
            place = uiState.selectedPlace,
            onPlaceBackPressed
        )
    }
}
how can I fill that navigation component when the app is in expanded device?
i

Ian Lake

03/04/2024, 11:30 PM
Again, you don't. You start from your biggest screen size, that defines your route, which is a single list detail route:

https://youtu.be/LTLQhC6VadI?t=718

p

Pablo

03/04/2024, 11:31 PM
what? then the video haves only one route?
i

Ian Lake

03/04/2024, 11:31 PM
if you only have a single two pane layout on a tablet, that's one route
p

Pablo

03/04/2024, 11:31 PM
then why they are telling that the video is using navigation? what sense haves to use navigation if you have only one route?
and why the codelab in working on it's telling me that I must use navigation for this app if the app is a list detail app?
i

Ian Lake

03/04/2024, 11:32 PM
usually, apps are a bit more complicated than just a list detail - like I mentioned above, maybe you have a photo viewer that is also full screen, even on tablets. Or maybe you have a login flow that is separate from your list detail, etc., etc.
p

Pablo

03/04/2024, 11:33 PM
well, the video is not talking about other screens
the video is talking about a list detail implemented with navigation
and the codelab i'm working on, it's just a list detail
This app should:Use the Jetpack Navigation component to enable users to navigate through your app.
the app tells you explicitly to simply have a list-list-detail scheme
with categories, places, and details
and they explicitly tells you to use navigation
and the video is using navigation too for a list detail
I'm very confussed now if you tell me that navigation is not necessary for a list detail because it is only one screen
also, if navigation is not necessary because a list detail is just a screen in a expanded device.. what happens with a compact device? in a compact device a list detail is perfect for two screens and for using navigation
so I'm exactly at the same point that in the begining, not understanding how to use navigation with a list detail scheme
so @Ian Lake maybe the video and the codelab are wrong and navigation can't be used with adaptative design if you have only a List Detail scheme in your app?
being just one screen, making it impossible to use two routes? and making it nonsense to use navigation for just one route?
i

Ian Lake

03/04/2024, 11:45 PM
Sorry Pablo, I can't explain it anymore. You've exhausted me quite completely. Best of luck
p

Pablo

03/04/2024, 11:47 PM
thank you, don't worry, at least you tried it, I appreciate it a lot
s

Stylianos Gakis

03/04/2024, 11:48 PM
in a compact device a list detail is perfect for two screens and for using navigation
Yes, but I feel like you gotta think of this from both perspectives and then work with that. If you only had phone UI yes two routes makes sense. In tablet it does not make sense to have two routes, it's not possible in fact. So you're in this situation where you gotta find what's the common denominator between the two. You can't have two routes on the tablet UI. But you can absolutely make do with one route and some internal navigation with a BackHandler on your phone UI. So there you have it, you choose the common denominator and go with that solution. And the video shows how that BackHandler would be used only in that phone case, and only when you are on the detail screen. I feel like that's what Ian is trying to convey to you.
p

Pablo

03/04/2024, 11:50 PM
then, you think the same as me, that the video and the codelab have an error and using navigation for a list detail if you are trying to use adaptative ui is impossible
can't understand why they make that mistake
s

Stylianos Gakis

03/04/2024, 11:54 PM
I don't see that codelab mentioning anything about big screen support. It's not a mistake if you're not building big screen support, and especially not if you're a beginner trying to learn, which the codelabs are often geared towards.
p

Pablo

03/04/2024, 11:54 PM
at least now thanks to Ian Lake I understood and learnt that the best way to know if haves sense to use navigation is start to think how to display it on the bigger device, if that device only needs a screen to display it, it is only a route, even if are two screens in a compact device
well, they are explicitly asking you to build the app adaptative, for compact and expanded devices
s

Stylianos Gakis

03/04/2024, 11:55 PM
There's no need to ping people by the way who are already in the conversation, it's a bit frustrating getting a ping when you're part of the conversation anyway. Just write the name without the @
👍 1
p

Pablo

03/04/2024, 11:56 PM
check this Stylianos: https://developer.android.com/codelabs/basic-android-kotlin-compose-my-city This app should:Use the Jetpack Navigation component to enable users to navigate through your app.Use adaptive layouts that account for all different screen sizes.
they are requesting you to combine adaptative design with navigation on a list detail app
s

Stylianos Gakis

03/04/2024, 11:57 PM
Yeah you are right they do mention big screens, I saw it after reading better myself too. You can raise a ticket on the issue tracker to ask for it to be clarified a bit better if you want to.
p

Pablo

03/04/2024, 11:57 PM
that means building for compact or expanded device, on an app with just a screen, and using nav host
thank you guys, sorry for the long talk, now I understand the problem
and Ian, this long conversation has helped me to understand much better how this works, don't feel it hasn't helped
in fact it helped me a lot
👍 2
s

Stylianos Gakis

03/05/2024, 12:08 AM
I learned some things here too, so thank you for asking and to Ian for answering. I feel like I will link back to this conversation many times in the future 😅
3 Views