Bradleycorn
02/28/2021, 4:37 PMModalBottomSheetLayout ... I have one setup to show a simple Text composable in the bottom sheet. But if I try to pass in the text to show, it doesn't work. The bottom sheet doesn't even show up. But if I "hard code" the text, it works fine. Code posted in the thread ...Bradleycorn
02/28/2021, 4:39 PM@ExperimentalMaterialApi
@Composable
fun BottomSheetScreen(message: Int?) {
val bottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
if (message != null) {
scope.launch { bottomSheetState.show() }
}
ModalBottomSheetLayout(
modifier = Modifier.fillMaxSize(),
sheetContent = {
BottomSheet(message = message)
},
sheetState =bottomSheetState
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("The message is:")
Text(message?.let { stringResource(id = it) } ?: "")
}
}
}
@Composable
fun BottomSheet(@StringRes message: Int?) {
val output = message?.let { stringResource(id = it)} ?: ""
// this doesn't work. I've logged the "output" and verified that is has proper text.
Text(text = output, modifier = Modifier.padding(vertical = 50.dp))
// If I do this instead, it works fine:
//Text(text = stringResource(R.string.message), modifier = Modifier.padding(vertical = 50.dp))
}Bradleycorn
02/28/2021, 4:44 PMBottomSheet composable, if I try to set the text of the Text composable, using the passed in message string resource, it doesn't work. The bottom sheet does not get displayed at all. However, if I don't used the passed in message, and instead just set the Text composable using a "hard coded" string resource, all of a sudden everything works great. What gives?
I have verified via debugging and log messages that the passed in message is a valid string resource identifier, and that the stringResource composable properly converts it to the correct string.Se7eN
02/28/2021, 4:45 PMval output = remember(message) { message?.let { stringResource(id = it)} ?: "" }Bradleycorn
02/28/2021, 4:46 PMstringResource is a composable function, and you can't call composables from the block of a rememberSe7eN
02/28/2021, 4:49 PMBradleycorn
02/28/2021, 4:52 PMBottomSheetScreen composable is getting called like this, from the main Acitivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(color = MaterialTheme.colors.background) {
var message by remember { mutableStateOf<Int?>(null)}
rememberCoroutineScope().launch {
delay(3_000)
message = R.string.message
}
BottomSheetScreen(message = message)
}
}
}
}
}jossiwolf
02/28/2021, 6:11 PMBradleycorn
02/28/2021, 6:11 PMBradleycorn
02/28/2021, 6:18 PMjossiwolf
02/28/2021, 6:20 PMbottomSheetState.show call throws a CancellationException or completes successfully?Bradleycorn
02/28/2021, 6:21 PMBradleycorn
02/28/2021, 6:21 PMjossiwolf
02/28/2021, 6:21 PMBradleycorn
02/28/2021, 6:21 PMjossiwolf
02/28/2021, 6:22 PMBradleycorn
02/28/2021, 6:25 PMBradleycorn
02/28/2021, 6:25 PMFailure(kotlinx.coroutines.JobCancellationException: ScopeCoroutine was cancelled; job=ScopeCoroutine{Cancelled}@cd2659c)Bradleycorn
02/28/2021, 6:26 PMBradleycorn
02/28/2021, 6:26 PMBradleycorn
02/28/2021, 6:29 PMBradleycorn
02/28/2021, 6:29 PMBradleycorn
02/28/2021, 6:29 PMjossiwolf
02/28/2021, 6:30 PMjossiwolf
02/28/2021, 6:30 PMBradleycorn
02/28/2021, 6:31 PMjossiwolf
02/28/2021, 6:31 PMjossiwolf
02/28/2021, 6:31 PMjossiwolf
02/28/2021, 6:31 PMjossiwolf
02/28/2021, 6:33 PMSwipeableState#processNewAnchors - see if that's called
SwipeableState#snapTo
SwipeableState#animateTo
afaik these are what can cause a new animation to runjossiwolf
02/28/2021, 6:40 PMshow call by 200msBradleycorn
02/28/2021, 6:42 PMjossiwolf
02/28/2021, 6:42 PMBradleycorn
02/28/2021, 6:43 PMBradleycorn
02/28/2021, 6:43 PMjossiwolf
02/28/2021, 6:43 PMModalBottomSheetLayout measures the sheet content when laying out everything. The composition of the body content is dependent on the sheet's height. I think what's happening is that the sheet's height changes when the text isn't null anymore, causing the body (and the swipeable anchors) to recomposeBradleycorn
02/28/2021, 6:44 PMBradleycorn
02/28/2021, 6:44 PMjossiwolf
02/28/2021, 6:45 PMjossiwolf
02/28/2021, 6:45 PMjossiwolf
02/28/2021, 6:45 PMMainActivity have any flags?Bradleycorn
02/28/2021, 6:47 PM@AndroidEntryPoint (though I'm not actually having anything injected into it yet).jossiwolf
02/28/2021, 6:49 PM@AndroidEntryPoint, can you repro it there?Bradleycorn
02/28/2021, 6:50 PMBradleycorn
02/28/2021, 6:51 PMBradleycorn
02/28/2021, 6:52 PMjossiwolf
02/28/2021, 6:52 PMjossiwolf
02/28/2021, 6:53 PMBradleycorn
02/28/2021, 6:56 PMBenjO
02/28/2021, 7:06 PMBradleycorn
02/28/2021, 7:08 PMjossiwolf
02/28/2021, 7:13 PMBradleycorn
02/28/2021, 7:40 PMBradleycorn
02/28/2021, 7:42 PMShakil Karim
02/28/2021, 7:52 PMif (message != null) {
scope.launch { bottomSheetState.show() }
}
before setting the bottomSheetState to ModalBottomSheetLayout that's why applying delay works.Bradleycorn
02/28/2021, 7:54 PMShakil Karim
02/28/2021, 7:56 PMjossiwolf
02/28/2021, 8:00 PMstringResource could be holding things upBradleycorn
02/28/2021, 8:00 PMBradleycorn
02/28/2021, 8:39 PMBradleycorn
02/28/2021, 8:39 PMBradleycorn
02/28/2021, 8:39 PMmeasures the sheet content when laying out everything. The composition of the body content is dependent on the sheet's height. I think what's happening is that the sheet's height changes when the text isn'tModalBottomSheetLayoutanymore, causing the body (and the swipeable anchors) to recomposenull
Bradleycorn
02/28/2021, 8:42 PMjossiwolf
02/28/2021, 9:42 PMjossiwolf
02/28/2021, 9:42 PMjossiwolf
02/28/2021, 9:43 PMjustasm
03/01/2021, 7:51 AMval scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState()
// in onClick or other callback..
scope.launch {
delay(1)
sheetState.show()
}
// alternatively, in composition like in the example in this thread
LaunchedEffect(message) {
delay(1)
sheetState.show()
}matvei
03/01/2021, 11:18 AMshow at the different point of time, the one in the bug is more correct.
the rule of thumb with coroutine launches is that you should never launch directly from the composition (like in your example above). We have special tools e.g LaunchedEffect to launch smth as a side effect, or DisposableEffect and SideEffect to just call a lambda as a side effects, where you can later do scope.launch . Please use those instead of calling launch from the composition.Alex Bieliaiev
03/03/2021, 10:45 AMNavHost inside a ModalBottomSheet . My solution is a bit more verbose, since I don't really like delay s:
/**
* That is horrible, but I don't have a better option ATM.
* Please refer to [androidx.compose.material.SwipeableState.processNewAnchors],
* specifically that part:
* val targetOffset = newAnchors.getOffset(currentValue)
* ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
* try {
* animateInternalToOffset(targetOffset, animationSpec)
* } catch (c: CancellationException) {
* // If the animation was interrupted for any reason, snap as a last resort.
* snapInternalToOffset(targetOffset)
* }
* The problem is that [androidx.compose.material.SwipeableState.processNewAnchors]
* cancels expanding animation, then I have to re-launch it first after the initial
* animateInternalToOffset(targetOffset, animationSpec) and then once again after
* the snapInternalToOffset(targetOffset).
*/
LaunchedEffect(Unit) {
try {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
} finally {
try {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
} finally {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
}
}matvei
03/03/2021, 11:14 AMBradleycorn
03/03/2021, 2:16 PMBradleycorn
03/03/2021, 2:18 PM