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 remember
Se7eN
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'tModalBottomSheetLayout
anymore, 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