I'm using SharedTransitionLayout in Jetpack Compose to animate elements across different screens. The issue occurs when I use Modifier.sharedBounds inside the title section of CenterAlignedTopAppBar.
β If I place the sharedBounds modifier outside the title, everything works fine. β But when I use it inside title, the app crashes on launch with this error:
java.lang.IllegalStateException: Uninitialized LayoutCoordinates. Please make sure when using the SharedTransitionScope composable function, the modifier passed to the child content is being used, or use SharedTransitionLayout instead.
What I've observed so far:
If I remove sharedBounds, the crash disappears.
All transition-related objects (sharedTransitionScope, animatedVisibilityScope, sharedContentState) are properly initialized.
The crash only happens on the first screen that loads.
If I navigate to another screen where sharedBounds is inside TopAppBar, it works perfectly fine (as long as it's not the first screen).
Any screen that is set as the first screen crashes if it contains this AppBar setup.
Hereβs a simplified version of the code that causes the issue:
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
with(sharedTransitionScope) {
Text(
modifier = Modifier.sharedBounds(
sharedContentState = rememberSharedContentState(
key = "TITLE_TRANSITION_KEY"
),
animatedVisibilityScope = animatedVisibilityScope
),
text = "My App",
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
}
)
}
) { / Content / }
Temporary workaround that works:
If I check isReady before applying sharedBounds, the crash is avoided:
if (isReady) {
Modifier.sharedBounds(...)
}
But I would like to understand why this happens and how to properly fix it.
My question:
Why does sharedBounds inside TopAppBar only crash when it's used in the first screen, but not in subsequent screens?
Is there a proper way to ensure that sharedBounds is initialized correctly when the first screen loads?
Could this be a bug in SharedTransitionLayout, or am I missing something in my setup?
AbstractActivity.kt
abstract class AbstractActivity : ComponentActivity() {
@Inject
lateinit var preferences: PreferencesUseCase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val themeId = preferences.getInt(
CommonConstants.PREFERENCE_THEME_ID_KEY, CommonConstants.THEME_DARK_KEY
)
val appTheme = ThemeHelper.getTheme(themeId, ThemeHelper.DEFAULT_THEME)
setTheme(appTheme.resourceId)
enableEdgeToEdge()
setContent {
ApplicationTheme(appTheme, direction = LayoutDirection.Rtl) {
AppContent()
}
}
}
@Composable
abstract fun AppContent()
}
AppActivity.kt
@AndroidEntryPoint
class AppActivity : AbstractActivity() {
@Composable
override fun AppContent() {
val navController = rememberNavController()
SharedTransitionLayout {
NavHost(
navController = navController,
modifier = Modifier
.fillMaxSize()
.imePadding()
,
startDestination = AppDestinations.Main
) {
composable<AppDestinations.Main> {
MainScreen(
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this
)
}
composable<AppDestinations.Settings> {
SettingsScreen(
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this
)
}
}
}
}
}
MainScreen.kt
@Composable
fun MainScreen(
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
Scaffold(
topBar = {
with(sharedTransitionScope) {
CenterAlignedTopAppBar(
title = {
Text(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(
key = "TOOLBAR_TITLE_EXPLODE_BOUNDS"
),
animatedVisibilityScope = animatedVisibilityScope
)
,
text = stringResource(R.string.app_name),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,,
)
}
)
}
}
) { innerPadding ->
...
}
}
SettingsScreen.kt
@Composable
fun SettingsScreen(
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope,
) {
Scaffold(
topBar = {
with(sharedTransitionScope) {
TopAppBar(
title = {
Text(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(
key = "TOOLBAR_TITLE_EXPLODE_BOUNDS"
),
animatedVisibilityScope = animatedVisibilityScope
)
,
text = stringResource(R.string.toolbar_title_settings),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
)
}
}
) { innerPadding ->
...
}
}
Thanks in advance for any insights! I really appreciate any help in understanding why this happens and how to properly fix it. If you need more details, feel free to ask, and I'll be happy to provide additional information. π
1 Answer 1
The root cause of your problem appears to be related to the initialization timing of the LayoutCoordinates when using SharedTransitionLayout.When your app first launches and displays the initial screen (MainScreen), the TopAppBar's title composable is being measured and laid out before the LayoutCoordinates are fully initialized, causing the "Uninitialized LayoutCoordinates" error. This only happens on the first screen because subsequent screens benefit from already initialized layout coordinates.
Use LaunchedEffect with a delay
@Composable
fun MainScreen(
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
var isReady by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
// Small delay to ensure layout coordinates are initialized
delay(100)
isReady = true
}
Scaffold(
topBar = {
with(sharedTransitionScope) {
CenterAlignedTopAppBar(
title = {
Text(
modifier = if (isReady) {
Modifier.sharedBounds(
sharedContentState = rememberSharedContentState(
key = "TOOLBAR_TITLE_EXPLODE_BOUNDS"
),
animatedVisibilityScope = animatedVisibilityScope
)
} else Modifier,
text = stringResource(R.string.app_name),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
)
}
)
}
}
) { innerPadding ->
// Content
}
}
Comments
Explore related questions
See similar questions with these tags.