I am trying to Shared transition, my layout is lik...
# compose-android
a
I am trying to Shared transition, my layout is like below
Copy code
SharedTransitionLayout {
    Scaffold {
        bottomBar = { if (fullScreen) { BottomBarUi() } } // bottom bar is shared as few screens shares the same bottom
    } {
        NavHost() //Here my other screens are present
    }
}
Now I am facing the issue, as I want to animate the
BottomBarUi
but in that scope I don't have
AnimatedVisibilityScope
as that is present inside
NavHost
->
composable
Is there anything wrong I am doing? Can I somehow wrap the
BottomBarUi
with a
AnimatedVisibility
and shared the top
AnimatedVisibilityScope
with NavHost so that animation happens in sync?
👀 1
s
You can't wrap it with just any AnimatedVisibility, it has to be the same one that is driving the NavHost transition as well if you want them to play in sync. Right now there'd be nothing to animate between, since the bottom bar lives completely above your NavHost. What you probably want to do here is not wrap your NavHost with a scaffold, but do the opposite. Each screen can have its own BottomBarUi, and make that share the same shared transition ID so that it matches between the screens that have this composable there.
a
RN that is what I am trying to do, so basically I want to use the nested graph as mentioned here and change the structure as below
Copy code
SharedTransitionLayout {
    // Here my other screens are present which are full screen
    NavHost {
        compoasble<Home> {
            // this used to be at the top now moved it inside the Home which is now NEW NESTED DESTINATION
            HomeScaffold {
                bottomBar = { if (fullScreen) { BottomBarUi() } }
            } {
                navigation(Feed) { // other bottom sheet UIs}
            }
        }
    }
}
This I am following with Jetsnack app Though I am surprised they are using a new NavHost instead of using the
navigation
function which creates nested nav. I might encounter some issue then will revert to follow Jetsnack exactly
Okay, I tried this.. Seems like this nested navigation will also not work as due to two NavHost we again got two
AnimatedVisibilityScope
😔 Now trying https://kotlinlang.slack.com/archives/C06CS4UCQ10/p1719560459770529?thread_ts=1719554859.324029&amp;cid=C06CS4UCQ10 🥱
s
Why do you have two NavHosts? 🤔
I am surprised they are using a new NavHost instead of using the
navigation
function which creates nested nav.
Where do they do that? It's probably an oversight, it is quite rare where you need a second NavHost.
a
So when you mentioned to not wrap NavHost with scaffold rather reverse it I thought smth like below
Copy code
NavHost { // this NavHost for full screen
   composable<FullScreenUiModel> { FullScreenU() }
   composable<BottomBarUiModel> { BottomBarUi() }
}
Now I followed the guide for bottom sheet at https://developer.android.com/develop/ui/compose/navigation#bottom-nav which shows code like below for implementing the BottomBar, which I thought will be the code for the
BottomBarUi
Copy code
@Composable
fun BottomBarUi() {
    Scaffold(
        bottomBar = {
            BottomNavigation { }
        }
    ) { innerPadding ->
        // here I though my second NavHost will be present
        NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
            composable<Profile> { ProfileScreen(...) }
            composable<Friends> { FriendsScreen(...) }
        }
    }
}
Also in Jetsnack app, you can see the first NavHost at JetsnackApp.kt (this has the top level NavHost and has
MainContainer
containing the bottom bar UI and full screen
SnackDetail
screen) and second NavHost at JetsnackApp.kt
MainContainer
composable this contains the Feed screen(Home), search screen, cart screen and profile screen(basically bottom bar UI screens)
I tried
navigation
to create a nested nav graph but when I was adding this inside a
Scaffold
in
content
composable lambda I was not able to make it work. But since I tried too many things very rapidly I was not sure whether it was mistake on my part or it is not suppose to work like that
s
That sample looks a tiny bit dated without type-safe nav APIs and such. Perhaps this was done as a hack to get shared element transitions in a sample, but this should no longer be the approach to take anymore. Just disregard it and do this instead as we discussed here
Copy code
@Composable
fun App() {
 NavHost(navController) {
  composable<Profile> { ProfileScreen(...) }
  composable<Friends> { FriendsScreen(...) }
 }
}

fun ProfileScreen() {
 Scaffold(bottomBar = BottomBar(...)) {
  ScreenContent()
 }
}

fun FriendsScreen() {
 Scaffold(bottomBar = BottomBar(...)) {
  ScreenContent()
 }
}

fun BottomBar(selected: Int, ...) {
  Box(Modifier.sharedElement(...))
}
a
Yes now this is my final code where 1. I have create
bottomNavComposable
which has
composable { Scaffold { content() } }
kind of structure 2. Using single NavHost 3. Shared the Scaffold, fab and bottom bar with
sharedElement
unique keys which is present in
bottomNavComposable
Scaffold
like you mentioned 4. Added some
.animateEnterExit()
in the bottom bar But it is also showing few issues
And I was not able to run the animation smoothly 1. sometimes instead of expand it goes side ways this happened in the video in the third trial in the
three_iteration
video(this happen very rarely and also happen with Jetsnack app)) 2. Screen flashes white(I am not sure this might be due usage of Surface but still debugging it) 3. Bottom bar height shift when changing the bottom bar items(shown in first try in
bottom_bar_shift
)
But thanks a lot for your help, and the approach mentioned in https://kotlinlang.slack.com/archives/C06CS4UCQ10/p1719554859324029 I was able to somehow make it work with the right approach. Now I am debugging these issues. Will update if I find anything
Video for the Jetsnack app where issue 1 happens(see the last animation which goes sideways). This is debug app but I also encounter this issue in Release app in my app
s
1. sometimes instead of expand it goes side ways this happened in the video in the third trial in the
three_iteration
video(this happen very rarely and also happen with Jetsnack app))
If you are coming from one screen without the bottom bar then it will have just the animation of the rest of the screen right? This is what you're preventing with .animateEnterExit, but doesn't always do what you want it to?
White flash
Yeah do not use Scaffold TBH if you can avoid it, no reason to introduce subcomposition in this mix.
Jump
Looks like something to do with the insets there, they are not there for a frame so the UI jumps somehow. Not sure if this has to do with subcomposition, shared elements or whatever. I'd try without Scaffold as a first step and take it from there.
see the last animation which goes sideways
I've seen this bug before in some older navigation/animation dependency version combos, but I think it also had to do with the fact that the child of NavHost was not always defining
fillMaxSize()
, could you double check that you are doing that properly in all destinations?
Copy code
@Composable
fun App() {
 NavHost(navController) {
  composable<Profile> { ProfileScreen(Modifier.fillMaxSize(), ...) }
  composable<Friends> { FriendsScreen(Modifier.fillMaxSize(), ...) }
 }
}
Just to be sure this is not it. Also are you on latest stable for compose animations + androidx.navigation?
a
Yes Scaffold was my doubt as well. Will try next approach without scaffold. Though these are genuine issues, as Shared element transition should work irrespective of using Subcomposition or not Will update with
fillMaxSize
though again this sounds like a bug 😅 My vesions as follow
Copy code
kotlin = "2.0.20"

jetbrainsCompose = { id = "org.jetbrains.compose", version = "1.7.0-beta01" }
androidx-navigationCompose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version = "2.8.0-alpha09" }
This is KMM project so compose ui is coupled with jetbrains plugin
If you are coming from one screen without the bottom bar then it will have just the animation of the rest of the screen right? This is what you’re preventing with .animateEnterExit, but doesn’t always do what you want it to?
but doesn't always do what you want it to?
I have added
animateEnterExit
but this is also not working shows different results in each run(I think Scaffold is really messing everything here). See also bottom bar when coming back to list shift slightly this could be due to insets as you mentioned
Mentioning @Doris Liu 😅 if she knows of any bug around the Subcompose layout or issue #1 mentioned here
s
Before anything, I'd make 100% sure that there is no bug present without subcomposition to even start checking what the bug with subcomposition is 😄 Did you try without scaffold and it all worked perfectly fine?
a
Not now.. checking that(will update here).. I thought if someone knew upfront if there is any bug..
Actually, I removed the
Scaffold
with
Surface { Colomnu {  } }
but bugs #1 and #2 are still happening #3 got fixed though not because of the removal of the Scaffold but the reordering of the modifiers(earlier I was using
sharedElement
first then
animateEnterExit
) now with animateEnterExit first and sharedElement later fixed the bottom sheet animation Not ideal still but attaching the video(this is with Scaffold and with fillMaxWidth) 🙂
s
Everything is like flashing and stuff. But it doesn't look related to shared elements here tbh. If there's one thing to try it'd be to just remove the shared elements modifier completely and see how that compares, to get a baseline of how things look like without any shared elements shenanigans
a
Yes, this could be it.. Let me try that
Youp even w/o shared transition issues #1, #2 happening, let me try to revert back to shared NavHost(initial code from where I started) or two NahHosts(Jetsnack one)
In the case of per screen different Scaffold, with shared element, could NavHost default animation or Surface color be producing those flashy colors blob thinking upside down
I think flash is appearing because of the surface and NavHost default animation. This all is happening in night mode. I tried this with a sample app with just NavHost and a simple list and details still seeing the flash. Earlier this was not happening as my whole
NavHost
was enclosed in a single
Scaffold
which is single
Surface
inside and since my other screens didn't have any
Surface
hence no bg and hence no animation
s
Yeah if you are fading through pages, obviously in-between the transition there will be nothing at 100% opacity. To prevent this, at the very top of your compose hierarchy you need to do a
Copy code
Surface(backgroundColor) {
  NavHost()
}
So that no matter what, the right background color is added around the entire app, despite whatever the NavHost destination might show. So tl;dr on this, no problems were with shared transitions then, and you gotta fix your app's problems without it, and then move on to implement it too and see if that by itself brings any issues. Did I get this right?
👌 1
a
Only issue with shared transition is that I applied the modifier in wrong order mentioned here But as of now no other issues I could think of
d
Yes,
animateEnterExit
needs to be before
sharedElement
. Otherwise, even when shared element keeps the UI in place, its child (in this case
animateEnterExit
would move the content in unexpected ways). Are you using sliding or shrinking/expanding for
animateEnterExit
?
a
My
animateEnterExit
contains the below code similar to Jetsnack app
Copy code
.animateEnterExit(
    enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically(
        spatialExpressiveSpring()
    ) { it },
    exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically(
        spatialExpressiveSpring()
    ) { it }
)
would move the content in unexpected ways
Yes I think that was happening.. as Bottom bar animating in weird ways
Currently, it works almost perfectly, with only two issues most prominently as below 1. Animation is not consistent(while going from detail to list, sometimes it appears smooth but sometimes the outline
OutlinedTextField
stays for a bit longer and the list item appears after a while. Attached is a video where the last detail to the list animation shows that). This could be an issue on my end(or might be due to usage of
OutLineTextField
) and I am still debugging that 2. Issue #1 mentioned at https://kotlinlang.slack.com/archives/C04TPPEQKEJ/p1726474510829169?thread_ts=1726393625.250919&cid=C04TPPEQKEJ is still there(not shown in this video). But currently, I am pretty sure this is not related to the shared element.
d
sometimes the outline
OutlinedTextField
stays for a bit longer and the list item appears after a while
Is each item also a sharedBounds? Is there any fade specified for the enter/exit of the OutlinedTextFields/sharedBounds? If there's no fade, you'd need to make sure the outgoing details page containing text fields to have a lower zIndex so that it doesn't render on top of the list item.
a
The list item and their corresponding UI in the detail item is using
sharedElement
which with just required params(i.e., with all default values left untouched) > Is there any fade specified for the enter/exit of the OutlinedTextFields/sharedBounds? fade is only applied to the bound of list item vs item detail(not in individual shared elements). Even removing those doesn't solve the bounding box appearing issue One thing I noticed is that if I wait for the list item -> detail animation to finish(you can observe the FAB disappearance for the animation finished state) before pressing the back button issue doesn't appear. I have attached two more videos one with a wait and one without a wait(the issue appears in this video). Now I have something to debug this further BTW thanks all for helping me this much 🙂
d
If this is only a problem when you interrupt the animation, it might be related to the alternative animation (i.e. spring) that the system uses to replace the interrupted duration-based animations. The spring may finish at a different time than the duration-based animation that it replaces. That timing discrepancy may be what we see here. Are the
OutlinedTextFields
shared elements also? How are they supposed to fade out?
a
OutlinedTextFields
is like below
Copy code
OutlinedTextField(
    modifier = Modifier
        .sharedElement(
            state = rememberSharedContentState("key_name"),
            animatedVisibilityScope = this@AnimatedVisibilityScope,
        )
        .fillMaxWidth()
        .focusProperties {
            next = focusRequesterName
            down = focusRequesterName
        }
        .focusRequester(focusRequesterAmount),
    value = value,
    onValueChange = { value = it },
)
So they are
sharedElement
with no animation on it. Overall container of list and detail page use Spring animation though
That timing discrepancy may be what we see here.
Is there a way to fix this?
s
I feel like your problems comes from something unrelated still. I would try to make a minimum repro project with nothing special other than the animations you wanna make and see if it happens there too. If it does, then it will be easier to debug.
a
I made one sample(only with a shared transition from list item to detail). The issue is still there, on interruption animation is different than its initial animation. Attaching the video and project (
I feel like your problems comes from something unrelated still
though I am not denying the fact that the problem can be from smth else as well) In this project, I am not using any custom animation just the default ones Also, in the video, you will see the first two animations(from list item to detail) with significant delay to allow the animation to finish, while in the last two, you will see the animation where I interrupted the animation(so you will observe the bounding boxes appearing on the top of list item UI). I always want my animation to be consistent and like how it ran for the first two iteration
Does latest project helps in identifying the issue?
d
Sorry for the late reply. It seems like a SeekableTransition specific issue. Could you file a bug?
BTW, I tried out the repro. When I replace the NavHost with a regular
AnimatedContent
, I don't see the issue. But I do see the issue with NavHost. The biggest difference between the two is the latter uses a seekable transition
a
It seems like a SeekableTransition specific issue.
Thanks for the reply.. Let me file it. I will add all those details
The weird sidewways screen change animation(mentioned above with video for Jetsack app and sample app at here) when switching the screen also happened in the CWIT podcast at

https://www.youtube.com/watch?v=sIo0Ij79Rq4&t=4503s

Kindof hijacked the podcast 😅
d
Hey, yea, now that we have a concrete repro case. We'll be able to get it fixed soon. 🙂
🦜 2
a
We have full video as well 😅
BTW is there tracking bug that is created for the same? I would like to link that in my sample app
d
I don't think there's a bug filed for this. But please feel free to file one and link it. I'll use that to track the fix as well
Upon further investigation, it seems like the navigation briefly cleared all content, which caused the AnimatedContent to measure size 0. It's a duplicate of https://issuetracker.google.com/353294030
🌟 1
😀 1