Jetpack compose BottomNavigation - java.lang.IllegalStateException: Already attached to lifecycleOwner - kotlin

When I double click the same item or if I go to each composable screen very quickly i receive an error, How do I solve this problem? I tried changing few things but I just can't solve it and I can't find any resources to fix this problem.
Bottom Navigation implementation
#Composable
fun BottomNav(
navController: NavController,
bottomNavState: MutableState<Boolean>,
) {
val navItems = listOf(
Screen.HomeScreen,
Screen.BookmarkScreen,
Screen.MyRecipesScreen,
Screen.FavouriteScreen
)
AnimatedVisibility(
visible = bottomNavState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
BottomNavigation(
backgroundColor = Color.White,
elevation = 12.dp
) {
val bottomNavBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = bottomNavBackStackEntry?.destination
navItems.forEach { item ->
BottomNavigationItem(
icon = {
Icon(painter = painterResource(id = item.icon), contentDescription = "")
},
label = {
Text(text = item.title)
},
selected = currentDestination?.hierarchy?.any { it.route == item.route } == true,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
selectedContentColor = Color.Black,
unselectedContentColor = Color.LightGray,
alwaysShowLabel = true,
)
}
}
}
)
}
Error Received
2022-03-05 11:50:10.183 8460-8460/com.im.cookgaloreapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.im.cookgaloreapp, PID: 8460
java.lang.IllegalStateException: Already attached to lifecycleOwner
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:38)
at androidx.lifecycle.SavedStateHandleAttacher.onRecreated(SavedStateHandleSupport.kt:139)
at androidx.savedstate.Recreator.reflectiveNew(Recreator.java:90)
at androidx.savedstate.Recreator.onStateChanged(Recreator.java:62)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:271)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:313)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:151)
at androidx.lifecycle.LifecycleRegistry.setCurrentState(LifecycleRegistry.java:121)
at androidx.navigation.NavBackStackEntry.updateState(NavBackStackEntry.kt:188)
at androidx.navigation.NavBackStackEntry.setMaxLifecycle(NavBackStackEntry.kt:154)
at androidx.navigation.NavController.updateBackStackLifecycle$navigation_runtime_release(NavController.kt:987)
at androidx.navigation.NavController.dispatchOnDestinationChanged(NavController.kt:892)
at androidx.navigation.NavController.navigate(NavController.kt:1726)
at androidx.navigation.NavController.navigate(NavController.kt:1658)
at androidx.navigation.NavController.navigate(NavController.kt:1980)
at androidx.navigation.NavController.navigate$default(NavController.kt:1975)
at androidx.navigation.NavController.navigate(NavController.kt:1961)
at com.im.cookgaloreapp.ui.components.BottomNavigationKt$BottomNav$3$1$1$2.invoke(BottomNavigation.kt:67)
at com.im.cookgaloreapp.ui.components.BottomNavigationKt$BottomNav$3$1$1$2.invoke(BottomNavigation.kt:57)
at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke-k-4lQ0M(Clickable.kt:153)
at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke(Clickable.kt:142)
at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1$1.invokeSuspend(TapGestureDetector.kt:223)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:511)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:406)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:419)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:310)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:179)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:98)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
2022-03-05 11:50:10.184 8460-8460/com.im.cookgaloreapp E/AndroidRuntime: at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1205)
at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1155)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1095)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:488)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1871)
at android.app.Activity.dispatchTouchEvent(Activity.java:4125)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:446)
at android.view.View.dispatchPointerEvent(View.java:14568)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6016)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5819)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5310)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5485)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5542)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8080)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8031)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7992)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8203)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:220)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loop(Looper.java:183)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

I'm facing the same problem using the latest compose navigation dependency 2.5.0-alpha03.
I don't know why it's happening.
Philip Dukhov is right, you should report this issue.
Here is a dirty workaround :
try {
// The navigation code that throw this exception
} catch (e: IllegalStateException) {
if (e.message != "Already attached to lifecycleOwner") {
throw e
} else {
// You can log the exception if you want
}
}

I was experiencing the same bug in 2.5.0-alpha01, 2.5.0-alpha02, and 2.5.0-alpha03 but finally was able to get it to work in 2.5.0-alpha04. 4th time is the charm!

This is indeed a navigation error. The problem is that the Tab is repeatedly clicked. The temporary solution is to return equally.
NavigationBarItem(
icon = {
Icon(painterResource(id = screen.icon), contentDescription = screen.route)
},
selected = currentRoute == screen.route,
onClick = {
if (currentRoute == screen.route) {
return#NavigationBarItem
}
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)

There is indeed a bug in 2.5.0-alpha03. Remitting to 2.5.0-alpha02 fixed the bug
implementation 'androidx.navigation:navigation-compose:2.5.0-alpha03'
to
implementation 'androidx.navigation:navigation-compose:2.5.0-alpha02'
fixed it for me

Related

Problem with custom dialog show in jetpack compose. Not correct background

I'm created custom dialog from common class in ini
init {
activity.setContent {
CustomDialog(viewModel)
}
}
#Composable
fun CustomDialog(viewModel: ViewModel){
Dialog(
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
}
}
But under dialog background is an empty activity, but must be a preference activity.
Not correct composable:
correct dialog via XML:
I tried, but didn't help
Surface(modifier = Modifier.background(Color.Transparent)) {
CustomDialog(viewModel)
}
```
Exampe:
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(size = 16.dp)
).clip(shape = RoundedCornerShape(size = 16.dp))
) {
//.....
}
}
Found solution:
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog?.window?.setDimAmount(0.0f)

How to refresh UI in Kotlin with Compose desktop when runBlocking?

I'm learning Kotlin and Compose Desktop and I'm trying refresh the UI before fetch data from an API.
But the request is running inside a runBlocking, thus the UI freezes until request is completed.
This is my code, everything works.
val client = HttpClient(CIO)
#OptIn(ExperimentalComposeUiApi::class)
#Composable
#Preview
fun App() {
var text by remember { mutableStateOf("Test button") }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(50.dp)
) {
Button(
onClick = {
text = "Wait..."//How to refresh UI to display this text?
runBlocking {
delay(5000)//blocking test
val response: HttpResponse = client.request("https://myapi.com/") {
// Configure request parameters exposed by HttpRequestBuilder
}
if (response.status == HttpStatusCode.OK) {
val body = response.body<String>()
println(body)
} else {
println("Error has occurred")
}
}
text = "Test button"
},
modifier = Modifier.fillMaxWidth()
) {
Text(text)
}
OutlinedTextField(
value = "",
singleLine = true,
onValueChange = { text = it }
)
}
}
}
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
state = WindowState(size = DpSize(350.dp, 500.dp)),
title = "Compose test"
) {
App()
}
}
How to achieve that?
The problem here is that you are using runBlocking at all.
The most straightforward solution for your case would be to replace your runBlocking {} with a coroutine scope. At the top of your App() function, create your scope: val scope = rememberCoroutineScope(), then instead of runBlocking you can say scope.launch {}.
New code would be:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
#Preview
fun App() {
var text by remember { mutableStateOf("Test button") }
val scope = rememberCoroutineScope()
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(50.dp)
) {
Button(
onClick = {
text = "Wait..."//How to refresh UI to display this text?
scope.launch {
delay(5000)//blocking test
val response: HttpResponse = client.request("https://myapi.com/") {
// Configure request parameters exposed by HttpRequestBuilder
}
if (response.status == HttpStatusCode.OK) {
val body = response.body<String>()
println(body)
} else {
println("Error has occurred")
}
}
text = "Test button"
},
modifier = Modifier.fillMaxWidth()
) {
Text(text)
}
OutlinedTextField(
value = "",
singleLine = true,
onValueChange = { text = it }
)
}
}
}
I saw one comment say to use LaunchedEffect() but this won't work in your case since you can't use that in an onClick since it's not a Composable.

LazyColumn inside Alertdialog shows error in Jetpack Compose

I'am trying to show a LazyColumn inside an alert dialog so the user can choose between a list of items, and click on it. The alert dialog will show without problem, i can click on any item which is on screen and close it, but as soon as i try to scroll between the items, it will give the following error:
E/InputEventReceiver: Exception dispatching input event.
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.app, PID: 22418
java.lang.IllegalArgumentException: Failed requirement.
at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure(MeasureAndLayoutDelegate.kt:177)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:228)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:38)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:201)
at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:662)
at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1073)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1059)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3920)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3594)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3920)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3594)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3920)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3594)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3920)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3594)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:913)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1957)
at android.app.Dialog.dispatchTouchEvent(Dialog.java:1162)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:871)
at android.view.View.dispatchPointerEvent(View.java:15458)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:7457)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7233)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6595)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6652)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6618)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6786)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6626)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6843)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6599)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6652)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6618)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6626)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6599)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9880)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:9718)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:9671)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:10014)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:220)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:200)
at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:9960)
at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:10056)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1010)
at android.view.Choreographer.doCallbacks(Choreographer.java:809)
at android.view.Choreographer.doFrame(Choreographer.java:737)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:995)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8595)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
I/Process: Sending signal. PID: 22418 SIG: 9
This is the AlertDialog code:
AlertDialog(
onDismissRequest = { showDialog.value = false },
modifier = Modifier.fillMaxHeight(.80f),
text = {
LazyColumn{
items(30){ i ->
Text(text = i.toString())
}
}
},
buttons = {
Button(onClick = { /*TODO*/ }) {
Text(text = "Cancel")
}
}
)
The same problem happens to me although I found that if I use the Material3 library scrolling is not a problem anymore:
implementation "androidx.compose.material3:material3:1.0.0-alpha03"
AlertDialog(
onDismissRequest = { },
modifier = Modifier.fillMaxHeight(0.8f),
text = {
LazyColumn() {
items(30) { i ->
Text(text = i.toString())
}
}
},
dismissButton = {
Button(onClick = { /*TODO*/ }) {
Text(text = "Cancel")
}
}
)
Make sure to import:
import androidx.compose.material3.AlertDialog
I don't know if this is the correct answer but I hope it helps in any case.
Another workaround is the usage of the base androidx.compose.ui.window.Dialog. This Dialog works without any crashes when using the LazyColumn:
#Composable
#Preview(showBackground = true)
fun SimpleDialog(onDismissRequest: () -> Unit = {}) {
Dialog(
onDismissRequest = onDismissRequest,
content = {
Surface(shape = RoundedCornerShape(8.dp)) {
Column(
modifier = Modifier.padding(8.dp),
) {
Text(text = "Simple Dialog", style = MaterialTheme.typography.h6)
LazyColumn(
modifier = Modifier.height(150.dp),
content = {
items(30){ i ->
Text(text = i.toString())
}
}
)
Button(onClick = onDismissRequest) {
Text("Cancel")
}
}
}
},
)
}

How to test onDismissRequest attribute of AlertDialog?

In its simplest form I have this dialog:
#Composable
fun MyDialog(
showDialogState: MutableState<Boolean>
) {
if (showDialogState.value) {
AlertDialog(onDismissRequest = { showDialogState.value = false },
// Other irrelevant attributes have been omitted
)
}
}
How can I trigger "onDismissRequest" on this composable in Robolectric?
This is usually how I build my composable tests by the way:
#Config(sdk = [Build.VERSION_CODES.O_MR1])
#RunWith(AndroidJUnit4::class)
#LooperMode(LooperMode.Mode.PAUSED)
class MyDialogTest {
#get:Rule
val composeTestRule = createComposeRule()
#Test
fun `MyDialog - when showing state and dismissed - changes showing state`() {
val state = mutableStateOf(true)
composeTestRule.setContent {
MyDialog(
showDialogState = state
)
}
// TODO: How do I trigger dismiss!?
assertFalse(state.value)
}
}
Compose version: 1.1.0-rc01
Android Gradle Plugin version: 7.0.4
Robolectric version: 4.7.3
I don't think this is possible at the moment. I have written this test to confirm:
val onButtonPressed = mock<() -> Unit>()
composeTestRule.setContent {
Scaffold(topBar = {
TopAppBar {
Text(text = "This test does not work")
}
}) {
AlertDialog(
onDismissRequest = {},
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
),
title = { Text(text = "This is a dialog")},
confirmButton = { Button(onClick = {}) {
Text(text = "Confirm")
}}
)
Column(modifier = Modifier.fillMaxSize()) {
Spacer(modifier = Modifier.weight(1f))
Button(onClick = onButtonPressed) {
Text(text = "test")
}
}
}
}
composeTestRule.onNode(isDialog()).assertExists()
composeTestRule.onNodeWithText("test", ignoreCase = true).performClick()
verify(onButtonPressed).invoke()
composeTestRule.onNode(isDialog()).assertDoesNotExist()
Even though the button is "behind" the dialog, it receives click events without dismissing the dialog.
Manual testing has confirmed that the implementation works, so perhaps a UIAutomator test could automate this, but that seems like an overly complicated way of solving this issue.
I quote the official documentation:
Dismiss the dialog when the user clicks outside the dialog or on the
back button. If you want to disable that functionality, simply use an
empty onCloseRequest.
https://foso.github.io/Jetpack-Compose-Playground/material/alertdialog/

Jetpack compose: scrolling in dialog gives strange effects

I'm trying to create a dialog where you can pick a number. I use FlowRow from Accompanist. But it gives strange results when scrolling. I guess that I'm missing something here.
The code:
#Composable
fun AlertDialogErrorTest() {
var showDlg by remember { mutableStateOf(false)}
val scrollState = rememberScrollState()
if (showDlg) {
AlertDialog(
onDismissRequest = { showDlg = false },
title = { Text(text = "Pick something from the list") },
text = {
FlowRow(modifier = Modifier.verticalScroll(scrollState)) {
for (i in 1..100) {
Box (modifier = Modifier.size(48.dp).padding(8.dp).background(Color.LightGray), contentAlignment = Alignment.Center) {
Text (i.toString())
}
}
}
},
confirmButton = {
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
},
dismissButton = {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
}
)
}
Button(onClick = {showDlg = true}) {
Text("Show dialog")
}
}
The result when scrolling:
This is because AlertDialog does not currently support scrollable content. If you think this is a bug, you can report it in the Compose issue tracker.
Meanwhile, you can use a plain Dialog, which lays under the AlertDialog.
Dialog(
onDismissRequest = { showDlg = false },
content = {
Column(Modifier.background(Color.White)) {
Text(
text = "Pick something from the list",
style = MaterialTheme.typography.subtitle1,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
FlowRow(
modifier = Modifier
.verticalScroll(scrollState)
.weight(1f)
) {
for (i in 1..100) {
Box(
modifier = Modifier
.size(48.dp)
.padding(8.dp)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(i.toString())
}
}
}
FlowRow(
mainAxisSpacing = 8.dp,
crossAxisSpacing = 12.dp,
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
}
}
},
)
Result:
I have faced the same issue, and the work around is to pass null in both title , and text, and to put the whole content in the buttons, you might check the below example, and please forgive my naming :).
AlertDialog(
onDismissRequest = {
state.onDismiss(state.data)
},
title = null,
text = null,
buttons = {
Column {
val expandedState =
state.data.expandedState
expandedState?.let {
RenderFieldPerCorrespondingState(it.title)
LazyColumnViewGroupComposable(
viewGroupState = it.itemsState,
scrollTo = scrollTo
)
ViewGroupComposable(viewGroupState = it.actionsState)
}
}
}
)