Ran into a blocker with Compose Navigation. Every ...
# compose
m
Ran into a blocker with Compose Navigation. Every Compose Screen gets white background if navigated using Compose Navigation. Meaning I can’t create screen B that will be placed on top of screen A where screen A will be partially visible in the background. This is pretty strange.
sample code. I was expecting to see the
DetailsScreen
with
MainScreen
background (Color.Red), but instead I see white background. 😕
m
Why do you find that strange? You're telling compose that when you navigate to "details" you want to draw a composable that contains a single Column (that wraps the content) with green background, with a text view in it. Which is exactly what you are getting. Your composition has changed and the "old screen" isn't being rendered anymore. If you want compose to draw your other screen beneath the new one, then you need tell compose that in "details" it should first draw your first screen and then draw the other content on top of that, with whatever effects you want, or you need to open up a new fragment/activity. (I am sure there are many ways to achieve this sort of thing).
m
well, I find it strange because every compose screen gets the white background I didn’t ask him to add, it makes more sense to me to make the background transparent or at least have an ability to get rid of that white background. Also, in old Fragment way it will work just as I wanted it to work. I tried to make my example as simple as possible, obviously my use case is not just one button under another one. It’s more to show case the problem.
m
I believe you can still make that background transparent, but it will only draw what is below the activity you're in, so likely that will be your launcher. When drawing "details" there isn't an "old screen" behind that gets drawn, rather you've replaced the whole view hierarchy. This is the basic premise of declarative UI.
m
so far I couldn’t find a way to get rid of the white background and make the Compose Screen used by Compose Navigation transparent 😞
m
This seems to work for me, making the nav host partially transparent, although the activity it is in is still not transparent of course
Copy code
NavHost(
    navController = navController, 
    startDestination = ...,
    modifier = Modifier.background(color= Color.Green.copy(alpha = 0.2f))
) {
@Mykola Did you get it working? I set a translucent theme for my activity, making this silly sample.
m
@Mikael Ohlson unfortunately not. As far as I understanding setting the color of NavHost modifier will not help here, the Navhost modifier seems to be a base layout for all the screen in its host, but doesn’t help me make one of its screen background transparent 😕
m
I'm not sure I am following what the problem is. You can do the same for each "screen" composable's background as well, as you see from my screenshot. If you want your activity to be translucent, you'll have to set a theme for it specifying that in your AndroidManifest.xml. Is it that you a second navigation destination (B) to be drawn on top of your first navigation destination (A)?
Because in that case, you have to duplicate that composable hierarchy of A in screen B as well, so that you can draw B on top of A. There is no "screen" below, the whole hierarchy is replaced.
Or you can of course navigate to a separate activity or fragment instead.
m
the thing is that I need the Screen B on top (not below) of the Screen A, so if the Screen B is only covering 90%(or 10%, doesn’t matter) of the screen space the Screen A has to be visible at the background.
m
Then you have to explicitly tell compose that you want that drawn by drawing the composables in screen A also when you draw screen B. Screen A doesn't exist otherwise. This is conceptually the same as
Copy code
if (someBoolean) {
    ScreenA()
} else {
    ScreenB()
}
If you want ScreenB on top of ScreenA, then you have to do something similar to:
```if (someBoolean) {
ScreenA()
} else {
ScreenA()
ScreenB()
}```
and make sure they align as you want them to.
m
yes, I’m aware of this and appreciate you trying to help. My question though was specifically around using Screen A and Screen B with Compose Navigation, so far I haven’t found a way to do it. Basically every Compose Navigation screen has a full sized white background and I can’t get rid of it. It seems like it’s the way Compose team wanted it to work, but I wasn’t sure. As I said before I find it strange, because with old Fragments this is quite easy to achieve using transparent background. I could use bottom sheets or dialogs, but they have their own UI side-effects that I don’t want, I guess I just have to live with them. Thanks for looking at it Mikael!
m
Ok, I am truly lost now. In my screen shot above (the dim green blob above the home screen) I managed to get Compose Navigation to be transparent, and rendering on a translucent activity. The whole thing is see through. What part am I missing? What component is it that you are unable to change the background for? Maybe if you could draw the expected UI, so that I can see what you’re trying for? In your example code, this view
Copy code
@Composable
fun DetailsScreen() {
    Column(
        modifier = Modifier.background(Color.Green)
    ) {
        Text(text = "Details Screen")
    }
}
is being rendered as a small green screen text atop a white background. Which is quite understandable, since both the nav host and the column will shrink wrap the text view. What you are seeing is the activity window and its default background. If you set your activity’s theme to be translucent, your whole nav host and everything in it will be transparent.
Copy code
<style name="Theme.AppCompat.Translucent" parent="Theme.AppCompat.NoActionBar">
    <item name="android:background">#33000000</item> <!-- Or any transparency or color you need -->
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:colorBackgroundCacheHint">@null</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
Here is me using Navigation to navigate between two screens. The only change from my “normal” setup is that I’ve changed the activity theme in AndroidManifest to that given above.
m
I think the confusion is mostly around translucent activity, but I don’t need an activity to be translucent, I don’t think I ever mentioned that, sorry if I made it confusing. Attached the video what I am trying to achieve:
I cannot achieve it using Compose Navigation only without fragments
m
You can, but your details screen composable needs to tell compose to also draw the thing you want in the background, in this case the MainScreen. When you change route, your screen is recomposed with only the composables in that route being drawn. There is no "background" to draw over, since you've told compose to ONLY draw the details screen. You don't get that for free, since all your UI is now in effect being drawn in a single view and any compositing has to happen via layering of composables. In pseudo code, your details route would look similar to this, but you probably have to tweak it to get it to lay out properly on top.
Copy code
Composable
fun AppNav(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = "main"
    ) {
        composable("main") {
            MainScreen { navController.navigate("details") }
        }
        composable("details") {
            MainScreen { }
            DetailsScreen()
        }
    }
}
and if you want it to use fading and stuff like that, you probably have to add a modifier / surface for that. Of course you can still use the compose navigation component to navigate to a separate fragment / activity on top of your current content. Making it possible to pass modifiers to your MainScreen may be useful, since you may want to pass a blur modifier or similar to it when drawing it behind the Details Screen.
m
don’t you think that this is a hack rather than a solution?
Copy code
composable("details") {
            MainScreen { }
            DetailsScreen()
        }
My MainScreen is extremely complicated and I don’t want to recompose it again just to be able to place DetailsScreen() on top of it
m
I don't really see what the alternative should be here. Automagically drawing the previous route under the current route would be expensive for everyone, and seems to go against some of the very concepts of Compose (like it being a declarative graph). That said, similar work seems to be ongoing to handle transition animations between destinations, so maybe it would be possible to make a feature request if you have a solid use case? Otherwise, maybe there is something clever that can be done here by grabbing a screen shot and drawing on top of that instead, but that probably has other negative consequences. Maybe someone who has more experience with Compose than me can give better suggestions. 🙂
But agreed, not being able to scope your view models in a meaningful way is not great. I guess a possible work around might be to have an entirely separate navigation graph for things that should be drawn on top of MainScreen? Not sure if that works at all.
m
actually I was thinking about an additional nav graph too, will look into it more. Thanks for spending your time with this Mikael!
m
👀 1