In most apps, whether it's a simple note app or a full-fledged messenger, user interaction is required one way or another, from the signup/login screen to the ability to send messages, search for notes, and so on. In this topic, we will look at a way to receive incoming data from the user via a text field.
TextField overview
To accept input from the user console applications, we use the readln() function you are already familiar with. However, in a real application, you won't use it. To receive data from the user, Jetpack Compose provides TextField and OutlinedTextField composables, which allow the user to enter text or numbers. Both composables contain the same set of parameters and provide the same functionality for developers and users, the differences are only in the appearance of these composables:
Now let's take a look at the parameters that these composables provide us with in the OutlinedTextField example:
@Composable
fun OutlinedTextField(
value: String, // or TextFieldValue
onValueChange: (String) -> Unit, // or (TextFieldValue) -> Unit
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = TextFieldDefaults.OutlinedTextFieldShape
colors: TextFieldColors = TextFieldDefaults. outlinedTextFieldColors()
)
androidx.compose.material.*) or Material 3 (androidx.compose.material3.*).Everything we will talk about next applies to both TextField and OutlinedTextField, but for convenience, we'll refer to both as TextField.
The value of TextField
The required parameter value of the composable TextField must contain the text that the user enters (or default text). As an argument, this parameter can take a String or a TextFieldValue, which in turn can also take values of type String and the already learned annotatedString.
You're probably wondering why you can't just use an annotatedString as an argument, like a Text composable? Of course it would be logical. However, a TextFieldValue instance provides significantly more options for handling user-entered text. First of all, TextFieldValue contains information not only about the text entered by the user but also about the current position of the cursor and the range of text selected by the user. For the convenience of processing the specified data, this class contains several functions, such as getSelectedText(), getTextBeforeSelection() or getTextAfterSelection().
The second and last required parameter of the composable TextField is onValueChange, which takes a lambda expression or a function with a single parameter of the String type. It's the callback that is triggered when the user changes input values in TextField and returns the changed value, which needs to be assigned to the value parameter. Let's do it!
var login = TextFieldValue("")
@Composable
fun LoginField() {
TextField(
value = login,
onValueChange = { input ->
login = input
}
)
}
If you tried to run this code and enter text in the emulator or on the device and nothing happens, then I hasten to please you, this is absolutely normal. Those who are already somewhat familiar with Jetpack Compose know the reason.
We'll discuss this feature in one of the future topics on Recomposition. For now, to make it work, you need to pass a String or TextFieldValue as an argument to the mutableStateOf() function, which returns an object of the MutableState type. And to get or change the value of this instance, call its value variable as shown below:
var login = mutableStateOf(TextFieldValue(""))
@Composable
fun LoginField() {
TextField(
value = login.value,
onValueChange = { input ->
login.value = input
}
)
}
It's useful to note that it would be best to use function references instead of lambda expressions to improve code quality. This will increase the readability of the code and ensure the convenience of its further maintenance, thanks to the separation of concerns improved by the use of future references.
var login = mutableStateOf(TextFieldValue(""))
fun setLoginField(value: TextFieldValue) {
login.value = value
}
@Composable
fun LoginField() {
TextField(
value = login.value,
onValueChange = ::setLoginField
)
} Hints and icons
The TextField composable has several options that allow you to visually highlight each text field with icons and text that serve as a hint to users and help them easily identify what each text field is responsible for. To do this, optional parameters are provided, such as label and placeholder, which accept a lambda expression that contains composables. In addition, TextField also has the leadingIcon and trailingIcon parameters, which also accept a lambda expression, often with the Icon composable, and display them respectively at the beginning and end of the text box. We will talk about them in the future topics, and below is just an example of using these parameters, and the result obtained:
@Composable
fun LoginField() {
OutlinedTextField(
value = login.value,
onValueChange = ::setLoginField,
label = { Text("Label") },
placeholder = { Text("Placeholder") },
leadingIcon = { Icon(Icons.Default.Email, null) },
trailingIcon = { Icon(Icons.Default.Clear, null) },
)
}
Output:
TextField customization
Now let's assume that we need to make a password field. However, we don't want other people to see the content of this field. Jetpack Compose developers have already taken care of this for us by adding the visualTransformation parameter of the TextField composable. In this case, we can use the existing PasswordVisualTransformation() class that inherits from the VisualTransformation interface and, under the hood, replace the text with "•" (char code '\u2022').
@Composable
fun PasswordField() {
OutlinedTextField(
value = password.value,
onValueChange = ::setPasswordField,
leadingIcon = { Icon(Icons.Default.Lock, null) },
visualTransformation = PasswordVisualTransformation()
)
}
Output:
In addition, we can change the appearance of the TextField, namely its colors and shapes, using the parameters of this composable with the same name. By default, colors of type TextFieldColors take the value from the TextFieldDefaults object. Using the named parameters of the called textFieldColors() or outlinedTextFieldColors() of this object, we can subtly change individual values without having to respecify all other colors. For example, you can change the color of leadingIcon like this:
@Composable
fun PasswordField() {
OutlinedTextField(
…
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedLeadingIconColor = Color(0xFF007BFF),
unfocusedLeadingIconColor = Color(0xFF007BFF)
)
)
}
Output:
In order to change the shape of a TextField, you can use such functions as RoundedCornerShape(), CutCornerShape(), or others, specifying the size in DP for each corner separately or for all corners together. The DP (Density Independent Pixel) is a virtual unit based on the physical density of the screen, similar to the SP unit, but it doesn't depend on the preferred font size set by the user in the device settings. So, we can change the shape of TextField as follows:
@Composable
fun PasswordField() {
OutlinedTextField(
…
shape = RoundedCornerShape(15.dp)
)
}
Output:
It's worth noting that an important element when creating a TextField is the correct configuration of the keyboardOptions and keyboardActions parameters. But you'll learn about this in a separate topic. We recommend you familiarize yourself with the remaining parameters and the possibilities they represent and try them yourself. Some of them will already be familiar to you from the topic on the Text composable.
Conclusion
So, let's sum it up:
- in this topic, we have covered the main features of the
TextFieldandOutlinedTextFieldcomposable; - identified the benefits of
TextFieldValueover using a regularStringvalue; - got acquainted with the
MutableStateinterface, the use of which is necessary to work with mutable values in Jetpack Compose; - explored ways to change the appearance of a
TextFieldand its elements.
Now, let's consolidate the material covered with some practice. Good luck!