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