Inject ChildStack from ChildComponent into Parent #889
-
I'm currently trying to wrap my head around shared-element-transitions and I stumbled upon a chicken-egg problem. The following is a prototype of what I'm trying to build. It's not quite there yet thought. What I want to achieve is that the detail screen covers the whole screen instead of only the portion of the screen it's ParentComponent covers: video_2025-06-06_15-00-05.mp4The current structure is as follows: RootComponent - full screen, responsible for managing the paging navigation fun HomeScreen(component: RootComponent) {
val pages by component.pages.subscribeAsState()
val selectedPage = pages.selectedIndex
val pagerState = rememberNavigationPagerState(
initialPage = component.initialPage,
pageCount = { pages.items.size }
)
// Sync pager state with Decompose pages
LaunchedEffect(selectedPage) {
if (pagerState.currentPage != selectedPage) {
pagerState.animateToPage(selectedPage)
}
}
// Sync Decompose pages with pager state
LaunchedEffect(selectedPage, pagerState.currentPage) {
if (selectedPage != pagerState.currentPage) {
component.selectPage(pagerState.currentPage)
}
}
NavigationPagerScaffold(
pagerState,
Modifier.fillMaxSize().padding(bottom = 12.dp),
content = { pageIndex ->
val page = pages.items[pageIndex]
when (val child = page.instance) {
// other components ommitted
is RootComponent.Child.Finances -> FinancesRootScreen(child.component)
else -> throw IllegalStateException("Unknown page: $child")
}
},
bottomBar = {
NavigationPagerTabContainer(
state = pagerState,
onPageActivationRequest = component::selectPage
) { pageIndex ->
val page = pages.items[pageIndex]
when (val child = page.instance) {
// other components ommitted
is RootComponent.Child.Finances -> NavigationItem(
stringResource(Res.strings.bottom_navigation_finances_label),
painterResource(Res.images.wallet)
)
else -> throw IllegalStateException("Unknown page: $child")
}
}
}
)
} Each page is a separate Component (of course) as is the FinancesRootComponent (with the 'Wallet' label). fun FinancesRootScreen(component: FinancesRootComponent) {
SharedTransitionLayout {
ChildStack(
stack = component.childStack,
modifier = Modifier.fillMaxSize(),
animation = stackAnimation(
animator = fade() + scale(),
predictiveBackParams = {
PredictiveBackParams(
backHandler = component.backHandler,
onBack = component::onBack,
)
},
),
) {
when (val child = it.instance) {
is FinancesRootComponent.Child.Overview -> FinancesScreen(
child.component,
animatedVisibilityScope = this,
sharedTransitionScope = this@SharedTransitionLayout
)
is FinancesRootComponent.Child.AccountDetails -> AccountDetailsScreen(
child.component,
animatedVisibilityScope = this,
sharedTransitionScope = this@SharedTransitionLayout
)
}
}
}
} Each Account is bound to another Component which is handled as LazyChildItems (I got that from the sample decompose app). The problem I am facing now is the following: As mentioned in the beginning I want to open the 'AccountDetailsScreen in Fullscreen on top of the 'NavigationPagerScaffold' of the 'HomeScreen'. Which brings me to the question of the title. My hope is that I somehow just got something wrong about my mental model of how those components are supposed to be structured. If that would help I could probably also supply a sample repo that reflects the current state. Thanks. P.S. As I wrote this I thought that maybe the real kicker is that I rely on the AnimatedVisibilityScope of the ChildStack Composable. Is it somehow possible to provide a custom scope myself that I pass down myself? In that case I might be able to restructure my component tree in a way that the details screen is a direct child of the root component (similar to the sample app). |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 6 replies
-
I think you need to have a separate component for the fullscreen stack. It could be as follows:
You can pass @OptIn(ExperimentalSharedTransitionApi::class)
val LocalSharedTransitionScope: ProvidableCompositionLocal<SharedTransitionScope> =
compositionLocalOf { error("LocalSharedTransitionScope was not provided") }
val LocalAnimatedVisibilityScope: ProvidableCompositionLocal<AnimatedVisibilityScope> =
compositionLocalOf { error("LocalAnimatedVisibilityScope was not provided") }
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SharedTransitionsScope(
content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit,
) {
LocalSharedTransitionScope.current.content(LocalAnimatedVisibilityScope.current)
} And then you just need to provide those two scopes in your
Then in you child screen Composable: @Composable
fun FinancesScreen(...) {
SharedTransitionsScope {
...
}
} |
Beta Was this translation helpful? Give feedback.
I think you need to have a separate component for the fullscreen stack. It could be as follows:
You can pass
SharedTransitionScope
andAnimatedVisibilityScope
from the root Composable toAccountDetailsScreen
and toFinancesScreen
throughHomeScreen
. You can also define a Composition Local for this to simplify the code.