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
}
)
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.
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()
}
}
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 ""
}
)
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 = "%"
)
)
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
}
)
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
)
)