How to do Responsive UI using Compose with Kotlin for Mobile and tablet

566 Views Asked by At

I have a case like using a single Compose UI for both the mobile and Tablet in both orientations (Portrait and Landscape), I have a simple login design which is developed for mobile but I need this to be working on a tablet too but I'm facing the view is been hidden because of the modifier I have added in each view as I focussed on the mobile device, Now I need to know how the Single Compose UI can be used in both mobile and tablet with the same design view without any impact

@Composable
fun TestLogin(
) {

val userNameFieldValue = remember { mutableStateOf("") }
val passwordFieldValue = remember { mutableStateOf("") }
val passwordVisibility = remember { mutableStateOf(false) }
val isLoginError = remember { mutableStateOf(false) }
val isInternetError = remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current

Box(
    Modifier
        .fillMaxSize()
        .background(color = colorResource(id = R.color.white)),
    contentAlignment = Alignment.Center) {
    Card(
        shape = RoundedCornerShape(20.dp),
        modifier = Modifier
            .padding(start = 10.dp, end = 10.dp)
    ) {
        Column(
            modifier = Modifier
                .padding(start = 16.dp, end = 16.dp, bottom = 10.dp, top = 10.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ) {

            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_background),
                tint = Color.Unspecified,
                contentDescription = "success",
                modifier = Modifier
                    .align(Alignment.CenterHorizontally)
            )
            Text(
                text = "Welcome to the App \n Hi There",
                fontWeight = FontWeight.ExtraBold,
                fontSize = 32.sp,
                color = colorResource(R.color.black),
                modifier = Modifier
                    .padding(bottom = 24.dp)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
            Text(
                text = "Login",
                fontWeight = FontWeight.Bold,
                fontSize = 22.sp,
                color = colorResource(R.color.black),
                modifier = Modifier
                    .padding(top = 50.dp, bottom = 24.dp)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
            OutlinedTextField(
                modifier = Modifier
                    .padding(bottom = 40.dp)
                    .fillMaxWidth(),
                value = userNameFieldValue.value,
                textStyle = TextStyle(
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Normal
                ),
                singleLine = true,
                onValueChange = {
                    isLoginError.value = false
                    userNameFieldValue.value = it
                },
                keyboardOptions = KeyboardOptions(
                    imeAction = ImeAction.Next
                ),
                enabled = true,
                label = { Text(text = "username") },
                colors = TextFieldDefaults.outlinedTextFieldColors(
                    focusedBorderColor = colorResource(id = R.color.black),
                    unfocusedBorderColor = colorResource(id = R.color.black),
                    cursorColor = colorResource(id = R.color.black)
                )

            )
            OutlinedTextField(
                modifier = Modifier
                    .padding(bottom = 32.dp)
                    .fillMaxWidth(),
                value = passwordFieldValue.value,
                textStyle = TextStyle(
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Normal
                ),
                enabled = true,
                singleLine = true,
                onValueChange = {
                    isLoginError.value = false
                    passwordFieldValue.value = it
                },
                label = { Text(text = "password") },
                visualTransformation = if (passwordVisibility.value) {
                    VisualTransformation.None
                } else {
                    PasswordVisualTransformation()
                },
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Password
                ),
                colors = TextFieldDefaults.outlinedTextFieldColors(
                    focusedBorderColor = colorResource(id = R.color.black),
                    unfocusedBorderColor = colorResource(id = R.color.black),
                    cursorColor = colorResource(id = R.color.black)
                )
            )
            Row(
                Modifier
                    .background(
                        colorResource(
                            id = getLoginButtonColor(
                                userNameFieldValue,
                                passwordFieldValue,
                                isInternetError
                            )
                        ),
                        RoundedCornerShape(5.dp)
                    )
                    .align(Alignment.CenterHorizontally)
                    .fillMaxWidth()
                    .padding(15.dp)
                    .clickable(
                        enabled = userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty() && !isInternetError.value,
                        onClick = {}
                    ),
                content = {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .padding(end = 10.dp)
                                .size(20.dp)
                                .align(Alignment.CenterVertically),
                            color = colorResource(id = R.color.white),
                            strokeWidth = 3.dp
                        )
                    Text(
                        "Login",
                        fontWeight = FontWeight.Medium,
                        fontSize = 14.sp,
                        textAlign = TextAlign.Center,
                        color = colorResource(id = getLoginButtonTextColor(userNameFieldValue, passwordFieldValue, isInternetError)))
                },
                horizontalArrangement = Arrangement.Center
            )
            Text(
                text = "Forgot Password",
                fontWeight = FontWeight.ExtraBold,
                color = colorResource(id = R.color.black),
                modifier = Modifier
                    .padding(top = 28.dp),
                textAlign = TextAlign.Center
            )
            Text(
                text = "Not a User ",
                fontWeight = FontWeight.ExtraBold,
                color = colorResource(id = R.color.black),
                modifier = Modifier
                    .padding(top = 24.dp)
                    .clickable { },
                textAlign = TextAlign.Center
            )
        }
    }
}
}


fun getLoginButtonColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if(!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.black else R.color.purple_200
}

fun getLoginButtonTextColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if(!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.white else R.color.white
}

@Preview
@Composable
fun previewView() {
    TestLogin()
}

Above code is the code for mobile device focussed , it can be see from the image Mobile_device When seen in tablet facing the view hidden Tablet_landscape

[Expected Mobile Design][3]

Expected tablet Design

I need to change the Dimensions based on the Screen ratio and there will be no change in the UI

3

There are 3 best solutions below

5
Megh Lath On

Edited:

Try out this code which contains usage weight modifier.

@Composable
fun TestLogin(
) {

    val userNameFieldValue = remember { mutableStateOf("") }
    val passwordFieldValue = remember { mutableStateOf("") }
    val passwordVisibility = remember { mutableStateOf(false) }
    val isLoginError = remember { mutableStateOf(false) }
    val isInternetError = remember { mutableStateOf(false) }
    val configuration = LocalConfiguration.current

    Column(
        Modifier
            .fillMaxSize()
            .background(color = colorResource(id = R.color.white)),
        verticalArrangement = Arrangement.Center,
    horizontalAlignment = Alignment.CenterHorizontally) {
        Card(
            shape = RoundedCornerShape(20.dp),
            modifier = Modifier
                .padding(start = 10.dp, end = 10.dp).weight(1f)
                .verticalScroll(rememberScrollState())
        ) {
            Column(
                modifier = Modifier
                    .padding(start = 16.dp, end = 16.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center,
            ) {

                Spacer(modifier = Modifier.height(12.dp).weight(0.5f))

                Icon(
                    painter = painterResource(id = R.drawable.ic_launcher_background),
                    tint = Color.Unspecified,
                    contentDescription = "success",
                    modifier = Modifier
                        .align(Alignment.CenterHorizontally)
                )
                Text(
                    text = "Welcome to the App \n Hi There",
                    fontWeight = FontWeight.ExtraBold,
                    fontSize = 32.sp,
                    color = colorResource(R.color.black),
                    modifier = Modifier
                        .fillMaxWidth(),
                    textAlign = TextAlign.Center
                )

                Spacer(modifier = Modifier.height(48.dp).weight(1f))

                Text(
                    text = "Login",
                    fontWeight = FontWeight.Bold,
                    fontSize = 22.sp,
                    color = colorResource(R.color.black),
                    modifier = Modifier
                        .fillMaxWidth(),
                    textAlign = TextAlign.Center
                )

                Spacer(modifier = Modifier.height(24.dp).weight(0.5f))

                OutlinedTextField(
                    modifier = Modifier
                        .fillMaxWidth(),
                    value = userNameFieldValue.value,
                    textStyle = TextStyle(
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Normal
                    ),
                    singleLine = true,
                    onValueChange = {
                        isLoginError.value = false
                        userNameFieldValue.value = it
                    },
                    keyboardOptions = KeyboardOptions(
                        imeAction = ImeAction.Next
                    ),
                    enabled = true,
                    label = { Text(text = "username") },
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = colorResource(id = R.color.black),
                        unfocusedBorderColor = colorResource(id = R.color.black),
                        cursorColor = colorResource(id = R.color.black)
                    )

                )

                Spacer(modifier = Modifier.height(40.dp).weight(0.5f))

                OutlinedTextField(
                    modifier = Modifier
                        .fillMaxWidth(),
                    value = passwordFieldValue.value,
                    textStyle = TextStyle(
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Normal
                    ),
                    enabled = true,
                    singleLine = true,
                    onValueChange = {
                        isLoginError.value = false
                        passwordFieldValue.value = it
                    },
                    label = { Text(text = "password") },
                    visualTransformation = if (passwordVisibility.value) {
                        VisualTransformation.None
                    } else {
                        PasswordVisualTransformation()
                    },
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Password
                    ),
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = colorResource(id = R.color.black),
                        unfocusedBorderColor = colorResource(id = R.color.black),
                        cursorColor = colorResource(id = R.color.black)
                    )
                )

                Spacer(modifier = Modifier.height(32.dp).weight(0.5f))

                Row(
                    Modifier
                        .background(
                            colorResource(
                                id = getLoginButtonColor(
                                    userNameFieldValue,
                                    passwordFieldValue,
                                    isInternetError
                                )
                            ),
                            RoundedCornerShape(5.dp)
                        )
                        .align(Alignment.CenterHorizontally)
                        .fillMaxWidth()
                        .padding(15.dp)
                        .clickable(
                            enabled = userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty() && !isInternetError.value,
                            onClick = {}
                        ),
                    content = {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .padding(end = 10.dp)
                                .size(20.dp)
                                .align(Alignment.CenterVertically),
                            color = colorResource(id = R.color.white),
                            strokeWidth = 3.dp
                        )
                        Text(
                            "Login",
                            fontWeight = FontWeight.Medium,
                            fontSize = 14.sp,
                            textAlign = TextAlign.Center,
                            color = colorResource(id = getLoginButtonTextColor(userNameFieldValue, passwordFieldValue, isInternetError)))
                    },
                    horizontalArrangement = Arrangement.Center
                )

                Spacer(modifier = Modifier.height(28.dp).weight(0.5f))

                Text(
                    text = "Forgot Password",
                    fontWeight = FontWeight.ExtraBold,
                    color = colorResource(id = R.color.black),
                    textAlign = TextAlign.Center
                )

                Spacer(modifier = Modifier.height(24.dp).weight(0.5f))

                Text(
                    text = "Not a User ",
                    fontWeight = FontWeight.ExtraBold,
                    color = colorResource(id = R.color.black),
                    modifier = Modifier
                        .clickable { },
                    textAlign = TextAlign.Center
                )

                Spacer(modifier = Modifier.height(28.dp).weight(0.5f))

            }
        }
    }
}

2
Ayman Ait On

the answer to your question consistes of two parts.

  • supporting differents phone screens:

to support diffrent phones screens avoid using static/hardcoded DP values for a composable’s dimensions and switch to relative sizes using modifiers such as fillMaxSize(),fillMaxHeight(),fillMaxWidth(), weight() or widthIn(). and test every screen you have at least on two different phones with different screen sizes.

  • supporting differents devices phone/tablet/foldables/pc:

for this topic there is a whole documentation for best user experience because for your exemple even if the whole view fits in your tablet it does not look great to the user (stretched).

tip: if you don't care about user experience in tablets then just use the first method

0
Eliza Camber On

There are two solutions to your problem. Either you'll make the Column scrollable (easy but ugly) or you can check the window size class and adapt your layout accordingly. Checking the window size if fairly easy and depending on the size you can adjust your paddings or even use a completely different UI/ Composable.

setContent {
     val windowSizeClass = calculateWindowSizeClass(this)
     MyApp(windowSizeClass)
}
when (windowSizeClass.heightSizeClass) {
        WindowWidthSizeClass.Compact -> ...
        WindowWidthSizeClass.Medium -> ...
        else -> ...
    }

[(shameless self promotion :)) I gave a talk very recently btw on how to support all form factors. The video was released just yesterday and can be found here.]