Forms with Jetpack Compose

With the introduction of the toolkit “Jetpack Compose”, Android developers say goodbye to old XML layouts. The declarative description of user interfaces simplifies the creation of screens, data binding and reusability.

Benji
3 min readFeb 9, 2023
a simple Android text field

Despite the many advantages of Jetpack Compose, certain use cases still require a lot of code. I realised this in particular when I wanted to create a larger form for an Android app. In this story I’d like to show you how to create a very simple form with Jetpack Compose.

You are probably also interested in my next story, in which I will show you a self-developed library with which you can create more complex forms with validators and data binding with as little code as possible.

Simple text field

The following example is an official Android example for a simple TextField composable.

@Composable
fun SimpleFilledTextFieldSample() {
var textState by remember { mutableStateOf("Hello") }

TextField(
value = textState,
onValueChange = { textState = it },
label = { Text("Label") }
)
}

But what exactly happens here?
The above composable renders a TextField. In addition, the textState itself keeps the state of the text field value stored. Whenever the value gets changed, the state is overwritten and the new text value is shown in the text field.

Separation of concerns

Well, this text field works perfectly. However, in a normal use case, I am often interested in more than just the basic functionality of a text field. For example, I often would like to add validators to form fields.

Also I want to follow the standardised architecture principles of Android and separate the different layers. I therefore want to have the data in a ViewModel with a UiState.

The following example shows again a simple text field with a “minimum length validator”. This example also follows the standardised architecture principles.

a simple text field with a min-length validator
data class MainUiState(
val currentName: String = "",
val currentNameErrors: MutableList<String> = mutableListOf(),
)
class MainViewModel: ViewModel() {
// Main UI state
private val _uiState = MutableStateFlow(MainUiState())
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()

fun updateName(name: String) {
val errors = mutableListOf<String>()
if (name.length < 4) {
errors.add("The name must be at least 4 characters.")
}
_uiState.value = _uiState.value.copy(
currentName = name,
currentNameErrors = errors
)
}
}
class MainActivity : ComponentActivity() {
// ...

@Composable
fun SimpleFilledTextFieldSample(
mainViewModel: MainViewModel = viewModel()
) {
val mainUiState by mainViewModel.uiState.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = mainUiState.currentName,
singleLine = true,
modifier = Modifier.fillMaxWidth(),
onValueChange = { mainViewModel.updateName(it) },
label = { Text(text = "Enter your Name") },
isError = mainUiState.currentNameErrors.isNotEmpty()
)
mainUiState.currentNameErrors.forEach {
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = it,
color = Color.Red
)
}
}
}
}

Conclusion
For this single text field, we had to write quite a lot of code. Now imagine that you also want a password field, a date picker and a custom picker. The pickers should also be able to handle specific data types like a Date object or any other object.

Compose Form Library

With the self developed open source library Compose Form it’s very easy to create an advanced form, which follows the standardised architecture principles, with multiple fields, pickers, validators and various other features.
Read my next story to learn how to use and customize Compose Form.

https://medium.com/@benlue/advanced-forms-in-android-with-compose-form-154ee0bff65b

--

--

Benji

Software & Mobile — Engineer | interested in best practices and always looking for new challenges