Fixed
Status Update
Comments
cs...@google.com <cs...@google.com> #2
Never got around to posting the stacktraces + repro, sorry!
Simple repro:
@Composable
fun NoAssociatedStateIssueRepro() {
val state = rememberModalBottomSheetState(ModalBottomSheetValue.HalfExpanded)
ModalBottomSheetLayout(
sheetState = state,
sheetContent = { Box(Modifier.height(56.dp) }
) {
}
}
The same crash can be provoked by having an empty sheet content:
@Composable
fun EmptySheetContentIssueRepro() {
val state = rememberModalBottomSheetState(ModalBottomSheetValue.HalfExpanded)
ModalBottomSheetLayout(
sheetState = state,
sheetContent = { }
) {
}
}
This will crash with
java.lang.IllegalArgumentException: The initial value must have an associated anchor.
at androidx.compose.material.SwipeableState.ensureInit$material_debug(Swipeable.kt:139)
at androidx.compose.material.SwipeableKt$swipeable$3.invoke(Swipeable.kt:579)
at androidx.compose.material.SwipeableKt$swipeable$3.invoke(Swipeable.kt:571)
at androidx.compose.ui.ComposedModifierKt$materialize$result$1.invoke(ComposedModifier.kt:75)
at androidx.compose.ui.ComposedModifierKt$materialize$result$1.invoke(ComposedModifier.kt:70)
at androidx.compose.ui.Modifier$Element$DefaultImpls.foldIn(Modifier.kt:107)
at androidx.compose.ui.ComposedModifier.foldIn(ComposedModifier.kt:47)
at androidx.compose.ui.CombinedModifier.foldIn(Modifier.kt:149)
at androidx.compose.ui.ComposedModifierKt.materialize(ComposedModifier.kt:70)
at androidx.compose.ui.layout.LayoutKt$materializerOf$1.invoke-Deg8D_g(Layout.kt:174)
at androidx.compose.ui.layout.LayoutKt$materializerOf$1.invoke(Layout.kt:173)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:130)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:46)
at androidx.compose.material.ModalBottomSheetKt$ModalBottomSheetLayout$2.invoke-jYbf7pk(ModalBottomSheet.kt:399)
at androidx.compose.material.ModalBottomSheetKt$ModalBottomSheetLayout$2.invoke(ModalBottomSheet.kt:269)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:149)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:46)
at androidx.compose.material.ModalBottomSheetKt$BottomSheetStack$1$1$placeable$1.invoke(ModalBottomSheet.kt:344)
at androidx.compose.material.ModalBottomSheetKt$BottomSheetStack$1$1$placeable$1.invoke(ModalBottomSheet.kt:344)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:121)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:46)
at androidx.compose.ui.layout.SubcomposeLayoutState$subcompose$2$1.invoke(SubcomposeLayout.kt:167)
at androidx.compose.ui.layout.SubcomposeLayoutState$subcompose$2$1.invoke(SubcomposeLayout.kt:167)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:121)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:46)
at androidx.compose.runtime.ComposerKt.invokeComposable(Composer.kt:3422)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_debug(Composer.kt:2604)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:348)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_debug(Recomposer.kt:697)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_debug(Composer.kt:3028)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_debug(Composer.kt:3028)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:304)
at androidx.compose.ui.layout.SubcomposeLayoutState.subcomposeInto(SubcomposeLayout.kt:184)
at androidx.compose.ui.layout.SubcomposeLayoutState.access$subcomposeInto(SubcomposeLayout.kt:100)
at androidx.compose.ui.layout.SubcomposeLayoutState$subcompose$2.invoke(SubcomposeLayout.kt:160)
at androidx.compose.ui.layout.SubcomposeLayoutState$subcompose$2.invoke(SubcomposeLayout.kt:158)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.withNoObservations(SnapshotStateObserver.kt:137)
at androidx.compose.ui.node.OwnerSnapshotObserver.withNoSnapshotReadObservation$ui_debug(OwnerSnapshotObserver.kt:49)
at androidx.compose.ui.node.LayoutNode.withNoSnapshotReadObservation$ui_debug(LayoutNode.kt:1086)
at androidx.compose.ui.layout.SubcomposeLayoutState.subcompose(SubcomposeLayout.kt:158)
at androidx.compose.ui.layout.SubcomposeLayoutState.subcompose(SubcomposeLayout.kt:152)
ap...@google.com <ap...@google.com> #3
Thanks Jossi, we'll take a look into it :)
ch...@google.com <ch...@google.com>
ap...@google.com <ap...@google.com> #4
Resetting assignee to `None` in preparation of the triage bash on Thursday June 17th. Please reach out to nickanthony@ if you have any questions.
na...@google.com <na...@google.com> #5
Any updates on this? It still crashes in 1.2.0-beta02
na...@google.com <na...@google.com> #8
There are no updates to share on this yet. We'll update when we have news :)
Description
Summary
The current API for providing composition locals is very allocation heavy and requires several groups. This can be improved in several ways,
The current API will allocated an array to store the providers and a mall object to for each "provides" parameter. It will then create a persistent map of the provided values and then union of the existing map and this map as the persistent scope.
Some composable function just provide one provides which makes these allocation particularly heavey weight. Also, constructing the union persisent map can be expensive.
Proposals
1. Special case single provider
The single provider can be special cased to avoid the creating of the array, the
ProvidesValue
instance and the persistent map.This would require new API that takes the composition local definition and the value (as well as a corresponding lint with a quick-fix to migrate code to the new API).
2. Allow building the map directly outside composition.
Instead of building the map in composition, the local map (for large maps) can be constructed as helper or outside composition entirely. This is specifically helpful when the map is conditionally built the current API requires paying the local provider overhead multiple times for shared or conditional providers.
This will require a new API that allows providing a composition local map directly.
3. Faster persistent maps
The persistent map is a general map that handles many cases well but it doesn't handle small changes well (such as adding a single value). We could provide a wrapper around the persistent map that will handles the special cases found in the Compose library better such as a singleton wrapper for cases, such when a single value is provided.
This doesn't require any API changes.