Skip to content

Simple Text Field

The simple text field represents a key value field and only supports single-line editable text. This field also supports setting helper, placeholder, or error texts.

Using the Simple Text Field

The simple text field can be used in your app like any traditional composable function:

var textValueState by rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name"
    ),
    trailingIcon = FioriTextFieldIcon(
        FioriIcon(
            iconType = IconType.RESOURCE,
            resId = R.drawable.ic_photo_camera_black_24dp,
            contentDescription = ""
        )
    ),
    value = textValueState,
    onValueChange = {
        textValueState = it
    }
)

var textValueState by rememberSaveable { mutableStateOf("") }
var textFieldValueState by remember { mutableStateOf(TextFieldValue(textValueState)) }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name"
    ),
    trailingIcon = FioriTextFieldIcon(
        FioriIcon(
            iconType = IconType.RESOURCE,
            resId = R.drawable.ic_photo_camera_black_24dp,
            contentDescription = ""
        )
    ),
    value = textFieldValueState,
    onValueChange = {
        textValueState = it.text
        textFieldValueState = it
    }
)

To specify the passed-in value parameter as androidx.compose.ui.text.input.TextFieldValue, you can control the cursor position.

Editable and Non-Editable Modes

This simple text field can be set to be editable or non-editable. When it is set to non-editable, the value field on the text field cannot be edited.

By default, the simple text field is editable. You can control the editability of the text field using the readOnly parameter of the text field function. Unlike editable mode, the simple text field supports multiple lines in non-editable mode. You can also use the readOnlyMaxLines parameter to control it:

var textValueState by rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name"
    ),
    readOnly = true,
    readOnlyMaxLines = 3,
    value = textValueState,
    onValueChange = {
        textValueState = it
    }
)

Helper, Placeholder, and Error Texts

To set the helper, placeholder, or error texts on the text field, use the helperText, placeholder, or errorMessage parameter, respectively in FioriTextFieldContent. To enable the error text and set the text field to be in error state, set the isError parameter to true. When errors are enabled, an error icon is also added to the end of the value field. You should therefore listen for the text value change and update the mutable state value for the isError parameter and the errorMessage parameter (See Listening for Text Value Changes):

var isError by rememberSaveable { mutableStateOf(false) }
var errorMsg by rememberSaveable { mutableStateOf("") }
var textValueState by rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name",
        required = true,
        errorMessage = errorMsg
    ),
    isError = isError,
    value = textValueState,
    onValueChange = {
        textValueState = it
        isError = textValueState.isEmpty()
        errorMsg = if (isError) "Required Field" else ""
    }
)

FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Email",
        placeholder = "Sample@email.com",
        helperText = "Email address"
    ),
    value = textValueState,
    onValueChange = {
        textValueState = it
    }
)

Helper, Placeholder, and Error Texts

Selectable Text

The simple text field allows you to enable or disable the ability to select the value text in non-editable states using the textSelectable parameter. By default, this is true and is only used for the non-editable model. The parameter will be ignored if the readOnly parameter is false.

Listening for Text Value Changes

Similar to the traditional composable function to listen for the change of the text field value, the onValueChange callback is used. You can get the changed text value and update the passed-in mutable state text to let the text field recompose:

var textValueState by rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name"
    ),
    value = textValueState,
    onValueChange = {
        textValueState = it
    }
)

Trailing Icon

The simple text field allows you to attach an icon at the end of the text value field (as a trailing icon) by specifying the trailingIcon parameter of the text field function:

FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name"
    ),
    trailingIcon = FioriTextFieldIcon(
        FioriIcon(
            iconType = IconType.RESOURCE,
            resId = R.drawable.ic_photo_camera_black_24dp,
            contentDescription = ""
        ),
        onClick = { }
    )
)

You can construct the FioriTextFieldIcon object using an icon resources ID or android.graphics.Bitmap. This trailing icon is also clickable when you specify the non-empty onClick or onValueChange callback. The icon will be treated as a toggle if the onValueChange callback is not empty. Otherwise, it will treat it as a button (for accessibility) if the onClick callback is not empty.

Use the interactionState parameter to control the trailing icon behavior when the text field is in error or focus state. You can set this parameter to FioriTextFieldInteractionState.NORMAL or FioriTextFieldInteractionState.INTERACTIVE. FioriTextFieldInteractionState.NORMAL is the default value, where the customized trailing icon will be replaced by an error icon when the text field is in error state, or, when the text field gets focus and has a non-empty text value, it will be replaced by a Clear icon button, which, when clicked, clears the entire input. With the FioriTextFieldInteractionState.INTERACTIVE value, the customized trailing icon has high priority to be displayed in the above two scenarios. See Password text field for additional information.

Special Simple Text Field

There are two special sub-types of the simple text field for password and barcode scanner scenarios.

Password Text Field

The password text field provides password visibility toggle functionality to protect any sensitive information that is required to be entered, such as a password. A visibility toggle button is displayed with the password text field, which, when clicked, toggles the text in the value field to be shown as plain text if it was disguised or, if it was in plain text, to be hidden:

val textValue = rememberSaveable { mutableStateOf("") }
FioriPasswordTextField(
    value = textValue.value,
    onValueChange = { textValue.value = it }
)

val textValue = rememberSaveable { mutableStateOf("") }
FioriPasswordTextField(
    value = textValue.value,
    onValueChange = { textValue.value = it },
    content = FioriTextFieldContent(
        label = "System Passcode",
        placeholder = "Enter your passcode"
    ),
    interactionState = FioriTextFieldInteractionState.NORMAL
)

Without the customized label, the word Password is displayed as the text field label. However, you can construct a FioriTextFieldContent object with a specified label parameter and set it as the value of the content parameter.

Also, by default, the password text field uses FioriTextFieldInteractionState.INTERACTIVE as the interactionState. However, you can set it as FioriTextFieldInteractionState.NORMAL to cause the Clear icon to be displayed when the non-empty text field gets focus. See Trailing Icon for additional information.

Password text field

By default, the password text field will use the default icon as the visibility toggle icon. Use LocalFioriTextFieldSupplier to allow you to use your own icons:

CompositionLocalProvider(LocalFioriTextFieldSupplier provides FioriTextFieldSupplier(
    passwordVisibilityTrailingIcon = FioriTextFieldIcon(
        FioriIcon(iconType = IconType.RESOURCE, resId = R.drawable.ic_view_module_black_24dp, contentDescription = "desc")
    ),
    passwordVisibilityOffTrailingIcon = FioriTextFieldIcon(
        FioriIcon(iconType = IconType.RESOURCE, resId = R.drawable.ic_view_module_black_24dp, contentDescription = "desc")
    )
)) {
    val textValue = rememberSaveable { mutableStateOf("") }
    FioriPasswordTextField(
        value = textValue.value,
        onValueChange = { textValue.value = it }
    )
}

Barcode Scanner Text Field

The barcode scanner text field supports scanning a barcode to acquire relevant information provided by the barcode as input:

val textValue = rememberSaveable { mutableStateOf("") }
FioriBarcodeScannerTextField(
    value = textValue.value,
    onValueChange = { textValue.value = it },
    content = FioriTextFieldContent(
        label = "Barcode",
        placeholder = "Scan a barcode to get a value",
        helperText = "Scan a barcode to get a value"
    )
)

When the user taps the Scan button, a scanning dialog is launched. The scanner detects the barcode and the extracted information is set as the value of the barcode scanner text field.

By default, the barcode scanner text field provides two trailing icons. Setting useAlternativeIcon to true will cause the barcode scanner text field to use the alternate trailing icon. Otherwise, the primary trailing icon is used. You can also specify your own FioriTextFieldIcon object using the trailingIcon parameter and the onClick or onValueChange callback.

The barcode scanner also supports laser scanning on special devices, such as Zebra and Honeywell devices. In these cases, the device laser scanner will be used when the user taps the Scan button and the laser scanner is available. Otherwise, the device camera is used for scanning. You can also use the camera or attach a listener to listen to the barcode result when the device's physical scan button is clicked by configuring the LocalFioriBarcodeScannerManager:

LocalFioriBarcodeScannerManager.current.alwaysUseCamera = true
LocalFioriBarcodeScannerManager.current..scanResultListener = object : FioriBarcodeScanResultListener {
        override fun onResult(barcode: String?, bluetoothScanInput: String?) {
            // Put your logic to receive the barcode here
            }
}

Bluetooth Scanner Support

You can integrate the Bluetooth scanner into your app by attaching a listener by configuring the LocalFioriBarcodeScannerManager. You need to use the FioriBluetoothBarcodeScanner API, which can be retrieved from the LocalFioriBarcodeScannerManager using the bluetoothScanner parameter to request scanning. You can then listen to the barcode result using the attached listener.

Currently, only Bluetooth scanners connected as a keyboard are supported. The API can detect whether the keyboard input is from a Bluetooth scanner or a real keyboard, and will return the scan result to the client code via a callback function.

The API supports two scanning detection modes: TIME_INTERVAL mode and PREFIX_SUFFIX mode.

  • For TIME_INTERVAL mode, the client code can specify an interval in milliseconds. If the time between two consecutive keyboard inputs is shorter than the interval, the API will treat the input as a Bluetooth scan.
  • For PREFIX_SUFFIX mode, the client code can specify a prefix and a suffix for a barcode, and an interval between the prefix and suffix. If the API detects the prefix and suffix from the keyboard input, and the time between the prefix and suffix is shorter than the specified interval, the API will treat the input as a Bluetooth scan.

When using the FioriBluetoothBarcodeScanner class to perform a Bluetooth scan, you can configure the scan mode and the detailed properties for each scan mode using the FioriBluetoothScannerConfig class.

    // New instance of FioriBluetoothScannerConfig.
    val config1 = FioriBluetoothScannerConfig.Builder()
        // Set up the scan mode
        .setScanMode(ScanMode.TIME_INTERVAL)
        // Set up the scan interval for each keyboard input
        .setDetectionInterval(100)
        .build()
    // Set the config to FioriBluetoothScannerConfig.
    LocalFioriBarcodeScannerManager.current.bluetoothScanner.config(config1)

    val config2 = FioriBluetoothScannerConfig.Builder()
        // Set up the scan mode
        .setScanMode(ScanMode.PREFIX_SUFFIX)
        // Set the prefix
        .setPrefix("$")
        // Set the suffix
        .setSuffix("#")
        // Set the interval between the input of the prefix and suffix
        .setMaxPrefixSuffixInterval(5000)
        .build()
    // Set the config to FioriBluetoothScannerConfig.
    LocalFioriBarcodeScannerManager.current.bluetoothScanner.config(config2)

Note

FioriBluetoothScannerConfig is optional. The FioriBluetoothScannerConfig provides a default configuration.

The FioriBluetoothBarcodeScanner.requestScan(event: KeyEvent) API allows you to request barcode scanning. To support this functionality, you must first attach a listener to listen to the barcode result by configuring the LocalFioriBarcodeScannerManager.

In general, you should call the FioriBluetoothBarcodeScanner.requestScan(event: KeyEvent) API in the Modifier.onPreviewKeyEvent() method of a Column, Row, or Box etc. compose function, and always let them focus to make sure the onPreviewKeyEvent method can be fired by the system. You should put the Barcode Scanner Text Field or Simple Text Field into the Column, Row, or Box etc. compose function as a child.

var detectedBarcode by rememberSaveable { mutableStateOf("") }
var wrappedBarcodeValue by rememberSaveable { mutableStateOf("") }
var keyboardInputBarcodeValue by rememberSaveable { mutableStateOf("") }
val fioriBarcodeScannerManager = LocalFioriBarcodeScannerManager.current

LaunchedEffect(fioriBarcodeScannerManager) {
    // Attache the reslut listener to list the barcode scan result
    fioriBarcodeScannerManager.scanResultListener = object: FioriBarcodeScanResultListener{
        override fun onResult(barcode: String?, bluetoothScanInput: String?) {
            MainScope().launch {
                barcode?.let {
                    detectedBarcode = it
                    wrappedBarcodeValue = "Code-".plus(it)
                }
                bluetoothScanInput?.let {
                    keyboardInputBarcodeValue = bluetoothScanInput
                }
            }
        }
    }
}

val focusRequester = remember { FocusRequester() }
var containerHasFocus by remember { mutableStateOf(false) }

 Column(
    modifier = Modifier
        .focusRequester(focusRequester)
        .onFocusChanged {
            containerHasFocus = it.hasFocus
        }
        .focusable()
        .onPreviewKeyEvent { event ->
            // Request the barcode scanning
            fioriBarcodeScannerManager.bluetoothScanner.requestScan(event)
             false
        }
)
{
    FioriBarcodeScannerTextField(
        value = wrappedBarcodeValue,
        onValueChange = { textFieldValue ->
            wrappedBarcodeValue = textFieldValue
        },
        interactionState = FioriTextFieldInteractionState.NORMAL,
        content = FioriTextFieldContent(
            label = "Wrapped Bluetooth Barcode",
            required = false,
            placeholder = "Scan a barcode with Bluetooth Scanner",
            helperText = "The detected barcode with bluetooth scanner listener was wrapped and start with 'Code-'"
        )
    )

    FioriSimpleTextField(
        value = keyboardInputBarcodeValue,
        onValueChange = { textFieldValue ->
            keyboardInputBarcodeValue = textFieldValue
        },
        content = FioriTextFieldContent(
                        label = "Bluetooth Keyboard Input Barcode",
            required = false,
            placeholder = "Scan a barcode with Bluetooth Scanner"
        )
    )

    Spacer(modifier = Modifier.height(6.dp))

    Row(
        modifier = Modifier.padding(
            horizontal = if (isTablet()) 24.dp else 16.dp,
            vertical = 32.dp
        ),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            text = "Detected Barcode Text: ",
            style = MaterialTheme.fioriHorizonAttributes.textAppearanceSubtitle2
        )

        Text(
            text = detectedBarcode,
            style = MaterialTheme.fioriHorizonAttributes.textAppearanceBody1
        )
    }
}

if (!containerHasFocus) {
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

Barcode scanner text field

Required Field

You may mark a field as a required field by setting required = true, when you construct the FioriTextFieldContent object. This adds an asterisk (*) at the end of the key, which indicates to the user that a value has to be entered. Please note, though, that this is simply a visual indicator, and you have to add your own validation to it. See Listening for Text Value Changes for additional information.

var isError by rememberSaveable { mutableStateOf(false) }
var errorMsg by rememberSaveable { mutableStateOf("") }
var textValueState by rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Name",
        required = true,
        errorMessage = errorMsg
    ),
    isError = isError,
    value = textValueState,
    onValueChange = {
        textValueState = it
        isError = textValueState.isEmpty()
        errorMsg = if (isError) "Required Field" else ""
    }
)

Required Field

Prefix and Suffix

You can also add a prefix or suffix to the value using the prefixText or suffixText attributes in the FioriTextFieldContent object.

val textValue = rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    value = textValue.value,
    onValueChange = { textValue.value = it },
    content = FioriTextFieldContent(
        label = "Sample Prefix and Suffix",
        prefixText = "$",
        suffixText = "%"
    )
)

Prefix Suffix

Character Counter

You can also show the character counter beside the helper text using the maxCharacterCounter parameter in FioriTextFieldContent. It should be a maximum of four digits. The character counter is used to track the length of the text.

By default, users can continue typing beyond the maximum number of characters set using the maxCharacterCounter parameter. As they type, the character counter increments the counting number, and the character counter is displayed as long as the user is typing. You can listen to the onValueChange callback for the change of the text field value and display an error message when the character maximum is exceeded.

You also can let the simple text field stop receiving the typed characters and make the character counter stop counting once the character maximum is met using the allowAlwaysTyping parameter in FioriTextFieldContent.

FioriSimpleTextField(
    content = FioriTextFieldContent(
        label = "Email",
        placeholder = "Sample@email.com",
        helperText = "Email address",
        maxCharacterCounter = 20,
        allowAlwaysTyping = false
    ),
    value = textValueState,
    onValueChange = {
        textValueState = it
    }
)

Character Counter

Customization

The simple text field offers a several methods for customization using FioriTextFieldDefaults. You can use it to customize the text field's colors, text styles, height and width, etc.

For example, if you wanted to apply a special color and text font style to an individual simple text field, or you don't want start/end padding when your app is running on a phone and tablet, you can customize the text field using FioriTextFieldDefaults:

val textValue = rememberSaveable { mutableStateOf("") }
FioriSimpleTextField(
    value = textValue.value,
    onValueChange = { textValue.value = it },
    content = FioriTextFieldContent(
        label = "Customized Text Field"
    ),
    colors = FioriTextFieldDefaults.colors(
        labelColor = Color.Black,
        focusedLabelColor = Color.Green,
        readOnlyLabelColor = Color.Red,
    ),
    textStyles = FioriTextFieldDefaults.textStyles(
        labelTextStyle = MaterialTheme.typography.caption
    ),
    styles = FioriTextFieldDefaults.styles(
        startPaddingOnPhone = 0.dp,
        endPaddingOnPhone = 0.dp,
        startPaddingOnTablet = 0.dp,
        endPaddingOnTablet = 0.dp
    )
)

Last update: February 29, 2024