Cleaner screens using Gemini-Bard and Extract Composable refactor

In my opinion, one of the best refactors is extract function. Martin Fowler describe it in his book Refactoring Improving the Design of Existing Code

“You have a code fragment that can be grouped together. Turn the fragment into a method whose name explains the purpose of the method.”

 void printOwing(double amount) {
   printBanner();
   //print details
   System.out.println ("name:" + _name);
   System.out.println ("amount" + amount);
 }

With the method extracted looks like this

void printOwing(double amount) {
   printBanner();
   printDetails(amount);
 }
 
void printDetails (double amount) {
   System.out.println ("name:" + _name);
   System.out.println ("amount" + amount);
 }

The same principle can be applied to composables in Jetpack Compose. And it can be achieved easily using Gemini Bard IA.

Consider this example. It is a form for personal information.


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PersonalInfoScreen(
) {
    Surface {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(
                text = "Enter your personal information",
                style = MaterialTheme.typography.bodyLarge,
            )

            // Name and Last Name
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
            ) {
                OutlinedTextField(
                    value ="",
                    onValueChange = {  },
                    label = { Text("Name") },
                    modifier = Modifier.weight(1f),
                    singleLine = true,
                )
                OutlinedTextField(
                    value = "",
                    onValueChange = {  },
                    label = { Text("Last Name") },
                    modifier = Modifier.weight(1f),
                    singleLine = true,
                )
            }

            // Email
            OutlinedTextField(
                value = "",
                onValueChange = {  },
                label = { Text("Email") },
                modifier = Modifier.fillMaxWidth(),
                singleLine = true,
            )

            // Nationality
            OutlinedTextField(
                value = "",
                onValueChange = {  },
                label = { Text("Nationality") },
                modifier = Modifier.fillMaxWidth(),
                singleLine = true,
            )

            // Birthdate
            DatePicker(
                state = DatePickerState(null, null, IntRange(1900, 2030), DisplayMode.Input) ,
                title = { Text("Birthdate") },
                modifier = Modifier.fillMaxWidth(),
            )


            Button(
                onClick = {},
                modifier = Modifier.fillMaxWidth(),
            ) {
                Text("Submit")
            }
        }
    }
}

It would be great if we extract each field into a Composable. This has these benefits:

  • You can add a preview for each field
  • You can test it individually
  • You can make modifications easier

So, lets ask Bard to extract those composables

Here is the code that Bard gives me. I had to fix some errors:




@Composable
fun NameField(
    modifier: Modifier = Modifier,
    value: String = "",
    onValueChange: (String) -> Unit,
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("Name") },
        modifier = modifier.fillMaxWidth(),
        singleLine = true,
    )
}

@Composable
fun LastNameField(
    modifier: Modifier = Modifier,
    value: String = "",
    onValueChange: (String) -> Unit,
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("Last Name") },
        modifier = modifier.fillMaxWidth(),
        singleLine = true,
    )
}

@Composable
fun EmailField(
    modifier: Modifier = Modifier,
    value: String = "",
    onValueChange: (String) -> Unit,
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("Email") },
        modifier = modifier.fillMaxWidth(),
        singleLine = true,
    )
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BirthdateField(
    modifier: Modifier = Modifier,
    birthDate: LocalDate? = null,
    onBirthDateChange: (LocalDate?) -> Unit,
) {
    DatePicker(
        state = DatePickerState(null, null, IntRange(1900, 2030), DisplayMode.Input) ,
        title = { Text("Birthdate") },
        modifier = modifier.fillMaxWidth()

    )
}

@Composable
fun SubmitButton(
    modifier: Modifier = Modifier,
    onClick: () -> Unit,
) {
    Button(
        onClick = onClick,
        modifier = modifier.fillMaxWidth(),
    ) {
        Text("Submit")
    }
}


@Composable
fun NationalityField(
    modifier: Modifier = Modifier,
    value: String = "",
    onValueChange: (String) -> Unit,
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("Nationality") },
        modifier = modifier.fillMaxWidth(),
        singleLine = true,
    )
}

Now I asked for the original code using this new functions

@Composable
fun PersonalInfoScreen(
    name: String = "",
    onNameChange: (String) -> Unit,
    lastName: String = "",
    onLastNameChange: (String) -> Unit,
    email: String = "",
    onEmailChange: (String) -> Unit,
    nationality: String = "",
    onNationalityChange: (String) -> Unit,
    birthDate: LocalDate? = null,
    onBirthDateChange: (LocalDate?) -> Unit,
    onSubmit: () -> Unit,
) {
    Surface {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(
                text = "Enter your personal information",
                style = MaterialTheme.typography.bodyLarge,
            )

            NameField(
                value = name,
                onValueChange = onNameChange,
            )

            LastNameField(
                value = lastName,
                onValueChange = onLastNameChange,
            )

            EmailField(
                value = email,
                onValueChange = onEmailChange,
            )

            NationalityField(
                value = nationality,
                onValueChange = onNationalityChange,
            )

            BirthdateField(
                birthDate = birthDate,
                onBirthDateChange = onBirthDateChange,
            )

            SubmitButton(
                onClick = onSubmit,
            )
        }
    }
}

It looks much cleaner!

I ask for the preview and Bard give me this code

@Preview(showBackground = true)
@Composable
fun PersonalInfoScreenPreview() {
    PersonalInfoScreen(
        name = "Jane Doe",
        lastName = "Johnson",
        email = "janedoe@example.com",
        nationality = "Canada",
        birthDate = null,
        onNameChange = { /* Handle name change here */ },
        onLastNameChange = { /* Handle last name change here */ },
        onEmailChange = { /* Handle email change here */ },
        onNationalityChange = { /* Handle nationality change here */ },
        onBirthDateChange = { /* Handle birth date change here */ },
        onSubmit = { /* Handle submit action here */ },
    )
}

This is how it looks

Leave a Reply

Your email address will not be published. Required fields are marked *