Hello, I am facing weird issue on some devices (Xi...
# compose
h
Hello, I am facing weird issue on some devices (Xiaomi mainly). Here is the output of the error stack :
Copy code
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.cardshop.app.compose, PID: 12745
    java.lang.IllegalStateException: measure() may not be called multiple times on the same Measurable
        at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:86)
        at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:71)
        at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1293)
        at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImpl.kt:89)
        at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:51)
        at androidx.compose.foundation.ScrollingLayoutModifier.measure-3p2s80s(Scroll.kt:342)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:306)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131)
        at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(Padding.kt:364)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39)
        at androidx.compose.foundation.layout.FillModifier.measure-3p2s80s(Size.kt:658)
        at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39)
        at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$3.invoke(OuterMeasurablePlaceable.kt:100)
        at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$3.invoke(OuterMeasurablePlaceable.kt:99)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:126)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:88)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:76)
        at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:99)
        at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1302)
        at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release$default(LayoutNode.kt:1298)
        at androidx.compose.ui.node.LayoutNode.onBeforeLayoutChildren(LayoutNode.kt:1080)
        at androidx.compose.ui.node.LayoutNode.layoutChildren$ui_release(LayoutNode.kt:961)
        at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.kt:954)
        at androidx.compose.ui.node.InnerPlaceable.placeAt-f8xVGno(InnerPlaceable.kt:125)
        at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.kt:31)
        at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.kt:370)
        at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50$default(Placeable.kt:203)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper$measure$1$1.placeChildren(DelegatingLayoutNodeWrapper.kt:138)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.placeAt-f8xVGno(DelegatingLayoutNodeWrapper.kt:126)
        at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.kt:31)
        at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.kt:370)
🧵 2
🧵 2
l
Are you trying to define a custom layout ?
h
@loloof64 No, the UI is basically a Column with Text and TextField to enter email address. And a Button.
l
Ok sorry. I may not be of good help, but could you share the
@Composable
or snippet of the
@Composable
which has issue ?
h
Copy code
enum class SignupEmailVerification {
    ENTER_EMAIL, EMAIL_SENT
}

@OptIn(ExperimentalPagerApi::class, ExperimentalComposeUiApi::class)
@Composable
fun SignupEmail(pagerState: PagerState, authViewModel: AuthViewModel) {

    val confirmText = stringResource(R.string.confirm_text)


    val scope = rememberCoroutineScope()
    var emailValue by remember { mutableStateOf("") }
    var nextButtonText by remember { mutableStateOf(confirmText) }
    var emailValueErrorState by remember { mutableStateOf(false) }
    var signupEmailStep by remember { mutableStateOf(SignupEmailVerification.ENTER_EMAIL) }
    
    val isSendingEmail by authViewModel.isSendingEmailOtp.observeAsState(false)
    val emailOtpSent by authViewModel.emailOtpSent.observeAsState(false)
    val emailOtpError by authViewModel.emailOtpError.observeAsState(false)


    val emailCode = remember { mutableStateOf("") }

    val keyboardController = LocalSoftwareKeyboardController.current

    val appContext = LocalContext.current

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(start = 24.dp, end = 24.dp)
            .verticalScroll(rememberScrollState()),
        verticalArrangement = <http://Arrangement.Top|Arrangement.Top>,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Spacer(modifier = Modifier.height(24.dp))
        Text(
            text = stringResource(R.string.signup_what_is_your_email),
            modifier = Modifier.fillMaxWidth(),
            textAlign = TextAlign.Start,
            color = Black,
            style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.W600)
        )
        Spacer(modifier = Modifier.height(8.dp))

        TextField(
            modifier = Modifier.fillMaxWidth(),
            value = emailValue,
            placeholder = { Text(text = "Email") },
            colors = TextFieldDefaults.textFieldColors(
                cursorColor = Black,
                backgroundColor = Color(0xFFF1F3FD),
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
                disabledIndicatorColor = Color.Transparent
            ),
            onValueChange = {
                if (emailValueErrorState) emailValueErrorState = !emailValueErrorState
                emailValue = it
            },
            isError = emailValueErrorState,
            enabled = !emailOtpSent,
            shape = RoundedCornerShape(4.dp),
            singleLine = true,
            textStyle = LocalTextStyle.current.copy(color = Black),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Email,
            ),
            trailingIcon = {
                if (emailValue.isNotEmpty()) {
                    IconButton(onClick = when (emailOtpSent) {
                        true -> {
                            { }
                        }
                        else -> {
                            { emailValue = "" }
                        }
                    }) {
                        Icon(
                            imageVector = Icons.Outlined.Close,
                            contentDescription = null
                        )
                    }
                }
            }
        )
        Text(
            text = stringResource(R.string.signup_you_needto_comfirm_text),
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 4.dp),
            textAlign = TextAlign.Start,
            style = TextStyle(color = Black, fontSize = 14.sp)
        )
        Spacer(modifier = Modifier.height(32.dp))

        // After the email is sent
        if (emailOtpSent) {
            nextButtonText = stringResource(R.string.next_text)
            signupEmailStep = SignupEmailVerification.EMAIL_SENT
            Text(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 8.dp),
                text = stringResource(R.string.signup_enter_otp_sent_to_text),
                textAlign = TextAlign.Start,
            )
            Text(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 8.dp),
                text = emailValue,
                textAlign = TextAlign.Start,
            )
            Spacer(modifier = Modifier.height(32.dp))

            TechsioOtpComponent(otpValue = {
                emailCode.value = it
            })

            Spacer(modifier = Modifier.height(32.dp))
        }

        Button(
            onClick = {
                // Hide the virtual keyboard
                keyboardController?.hide()

                if (signupEmailStep == SignupEmailVerification.ENTER_EMAIL) {
                    // Check is correct email schema
                    if (!Patterns.EMAIL_ADDRESS.matcher(emailValue).matches()) {
                        emailValueErrorState = true
                    } else {
                        // send the email verification
                        authViewModel.sendSignupEmailOtp(emailValue = emailValue)
                    }
                } else {
                    // construct a list of not NULL values
                    if (emailCode.value.length == 6) {
                        authViewModel.setSignupUserValue(
                            email = emailValue,
                            otp = emailCode.value
                        )
                        // Change to previous step
                        signupEmailStep = SignupEmailVerification.ENTER_EMAIL
                        scope.launch {
                            pagerState.animateScrollToPage(1)
                        }

                    } else {
                        Toast.makeText(
                            appContext,
                            "Email code is not correct",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            },
            colors = ButtonDefaults.buttonColors(
                disabledBackgroundColor = Color.Gray
            ),
            enabled = !isSendingEmail,
            modifier = Modifier,
            shape = RoundedCornerShape(4.dp)
        ) {
            if (isSendingEmail) CircularProgressIndicator()
            else Text(
                text = nextButtonText,
                modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
                style = MaterialTheme.typography.h6.copy(color = whiteColor)
            )
        }
    }

}
f
It's maybe this bug in compose? I see pager state and that's implemented using LazyColumn I believe so it could be related