BasicTextField2: A TextField of Dreams [2/2] | by Alejandra Stamato | Nov, 2023
In part 1️⃣ we’ve covered how to:
- manage state with the new
BasicTextField2
APIs to prevent state synchronicity issues - do basic styling including using decorator and line limits
- solve for common scenarios when observing state and applying business rules
- edit the value of the text field programmatically with
TextFieldBuffer
Find part 1 here: BasicTextField2: A TextField of Dreams [1/2]
Filtering | InputTransformation
Let’s say we’re implementing a text field for a verification code that accepts digits only.
To filter the user’s input e.g. accept digits only or leave out special characters, you define an InputTransformation
. This will modify the user input before saving it to the text field state. It is a non reversible operation, so you will lose the input that doesn’t match your transformation. That’s why we call them “filters”.
The InputTransformation
API shape is as follows:
fun interface InputTransformation {val keyboardOptions: KeyboardOptions? get() = null
fun transformInput(
originalValue: TextFieldCharSequence,
valueWithChanges: TextFieldBuffer
)
}
The transformInput
method contains the original typed text and a value with the changes in the form of a TextFieldBuffer
, described in part 1 of this blog. TextFieldBuffer
API provides a list of changes of type ChangeList
.
class TextFieldBuffer {
// other fields and methodsval changes: ChangeList get()
interface ChangeList {
val changeCount: Int
fun getRange(changeIndex: Int): TextRange
fun getOriginalRange(changeIndex: Int): TextRange
}
}
You could do anything with the changes, including discarding them all.
object DigitsOnlyTransformation : InputTransformation {
override val keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)override fun transformInput(
originalValue: TextFieldCharSequence,
valueWithChanges: TextFieldBuffer
) {
if (!valueWithChanges.asCharSequence().isDigitsOnly()) {
valueWithChanges.revertAllChanges()
}
}
}
// Compose
BasicTextField2(
state = state,
inputTransformation = DigitsOnlyFilter
)
We define an object that implements the InputTransformation
interface. First, we need to implement the transformInput
method.
In our example, we check the changes from the TextFieldBuffer
. If they contain digits only, we keep the changes. If the characters are not digits, we revert those changes. It is very simple, as the diff is done internally for us.
Note that we’re also setting the corresponding keyboard type, to be Number
.
Because InputTransformation
is a functional interface, you could pass a lambda where you describe your transformation directly to the BasicTextField2
composable, as follows:
BasicTextField2(
state = state,
inputTransformation = { originalValue, valueWithChanges ->
if (!valueWithChanges.asCharSequence().isDigitsOnly()) {
valueWithChanges.revertAllChanges()
}
},
// in this case pass the keyboardOptions to the BFT2 directly, which
// overrides the one from the inputTransformation
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number
)
This works just fine if you have one text field that needs this particular transformation. If you have more than one, it makes sense to extract the transformation to its own object.
Next, we have to build a verification code field that is max 6 chars in length and all caps.
We have some built in input transformations for such common use cases: maxLengthInChars
to limit the field length andallCaps
to make the text uppercased.
We can write the verification code like this:
BasicTextField2(
state = state,
inputTransformation = InputTransformation.maxLengthInChars(6)
.then(InputTransformation.allCaps(Locale.current)),
)
We’re using then
to chain input transformations, and filters are applied sequentially in order.
Visual transformation | OutputTransformation ⚠️
As of early November 2023: ⚠️ OutputTransformation API is under construction. You can check progress here.
In our verification code text field example, now we want to replace the characters the user hasn’t typed yet with dots and group them in triplets adding a space in between, as follows:
To solve this and other cases in which you need to format the text field content, like formatting a phone or credit card number, you define an OutputTransformation
. It will format the internal state when displaying it in the UI. Note that unlike InputTransformation
where the result of applying it is saved to the text field state, the result of applying the OutputTransformation
changes are not stored.
The OutputTransformation
API shape is as follows:
fun interface OutputTransformation {fun transformOutput(buffer: TextFieldBuffer)
}
The great benefit of the new API is that we don’t need to provide the offset mapping between the original raw text and transformed text. The text field handles this for us implicitly.
In this example, we define an object that implements the OutputTransformation
interface and implement transformOutput
:
object VerificationCodeOutputTransformation : OutputTransformation {override fun transformOutput(buffer: TextFieldBuffer) {
// Pad the text with placeholder chars if too short
// ··· ···
val padCount = 6 - buffer.length
repeat(padCount) {
buffer.append('·')
}
// 123 456
if (buffer.length > 3) buffer.insert(3, " ")
}
}
First, we call append on text field buffer to insert “dots” for any not yet typed characters. Then we call insert
method to add a space between triplets of characters. That’s it. No offset mappings that in old API was a great source of confusion and crashes. Pretty sweet right?
SecureTextField
Let’s build the password field in our Sign up screen. Writing a password field is such a common use case that there is a new composable altogether for this, built on top of BasicTextField2
called BasicSecureTextField
:
val password = rememberTextFieldState()
BasicSecureTextField(
state = password
textObfuscationMode = TextObfuscationMode.RevealLastTyped
)
There are 3 useful modes for textObfuscationMode
. RevealLastTyped
which is default and matches what EditText
in the view system does when configuring input type as textPassword
. With this behaviour you briefly see the last character typed before it times out or you type the next character. Then you have Hidden
, in which case you never see the character typed, and Visible
, useful to temporarily make the password value visible.
Having BasicSecureTextField
as a separate composable is very powerful. It allows the team to optimise for security under the hood, making sure the field content is not persisted in memory more than it should, avoiding things like memory spoofing. It comes with predefined UI with mask and textObfuscationModes
, and also explicit behaviours that come with it, for instance the text toolbar modifications (you cannot cut or copy the contents of a password field).
And more…
There’s plenty to talk about, but I’ll pick just 3 more highlights.
The new BasicTextField2
allows you to access internal scroll state.
Hoist the scroll state like you would with any other scrollable composable, like LazyLayout
. Pass it to BasicTextField2
and now you can scroll the field programmatically through another composable, for example a vertical Slider
acting as scroll bars for the text field:
val scrollState = rememberScrollState()BasicTextField2(
state = state,
scrollState = scrollState,
// ...
)
Slider(
value = scrollState.value.toFloat(),
onValueChange = {
coroutineScope.launch { scrollState.scrollTo(it.roundToInt()) }
},
valueRange = 0f..scrollState.maxValue.toFloat()
)
The team added support for more gestures, like double tap to select word.
Lastly, TextFieldState
gives you access to the UndoState
class. This class holds historical values of the state, and useful methods to undo
or redo
edit changes, built on top of TextFieldBuffer
‘s ChangeList
.
In very few lines of code you can implement undo/redo support like this:
val state: TextFieldState = rememberTextFieldState()Button(
onClick = { state.undoState.undo() },
enabled = state.undoState.canUndo
) {
Text("Undo")
}
Button(
onClick = { state.undoState.clearHistory() },
enabled = state.undoState.canUndo || state.undoState.canRedo
) {
Text("Clear History")
}
Very powerful API, and all out of the box 😊🎉
Related Posts
Leave a Reply Cancel reply
Categories
- ! Без рубрики (1)
- ++PU (1)
- 1 (1)
- 1w (1)
- 1win Brazil (1)
- 1win India (1)
- 1WIN Official In Russia (1)
- 1win Turkiye (1)
- 1xbet egypt (1)
- 2ankarafayansustasi.net_may (1)
- ankarafayansustasi.netsiteai apr (1)
- Artificial intelligence (1)
- Arts & Entertainment, Photography (1)
- belugasitesi_mAY (1)
- BH_TOPsitesi apr (1)
- BHsitesy_may (2)
- Blog (3)
- Bookkeeping (14)
- Bootcamp de programação (2)
- Bootcamp de programación (2)
- BT_TOPsitesi apr (1)
- casino (5)
- casinom-hub (1)
- casinom-hub.comsitesi apr (3)
- colombian mail order brides (1)
- Cryptocurrency exchange (2)
- Dinamobet_next (1)
- Disease & Illness, Colon Cancer (1)
- Dumanbet (1)
- Dumanbet_next (1)
- Finance, Insurance (1)
- FinTech (5)
- Forex Trading (11)
- Galabet (1)
- Health & Fitness, Fitness Equipment (1)
- Hitbet (1)
- Home & Family, Crafts (1)
- Home & Family, Gardening (1)
- Internet Business, Audio-Video Streaming (1)
- Internet Business, Ecommerce (1)
- Internet Business, Email Marketing (1)
- Internet Business, Internet Marketing (1)
- IT Вакансії (1)
- IT Образование (5)
- IT Освіта (1)
- latin women dating (1)
- mail order bride (1)
- Mars bahis (2)
- Matadorbet (1)
- minimiri.comsitesi apr (3)
- Mobile App Development (771)
- Mostbet Russia (1)
- New Post (1)
- News (12)
- PB_TOPsitesi apr (1)
- PBsitesi_may (1)
- Pusulabet (1)
- redmirepool.bizsitesi apr (2)
- redmirepoolsitesi_may (1)
- Reference & Education, College (1)
- Reference & Education, Sociology (1)
- Rokusitesi apr (1)
- Sober living (6)
- Society, Divorce (1)
- Software development (7)
- Superbetin (1)
- Tempobet_next (1)
- thelongeststride.comsitesi apr (1)
- tipobet-turkiyesitesi apr (1)
- Ultrabet (1)
- Uncategorized (1)
- Игра (2)
- казино (1)
- Криптовалюты (1)
- Новости Криптовалют (1)
- Финтех (7)
- Форекс Брокеры (9)
- Форекс обучение (2)