How to add uneditable postfix (suffix) to TextField in Jetpack Compose? - android

How can I add a suffix to TextField input that flows (moves) with the user input text?

With M3 starting from the 1.1.0-alpha06 you can use the suffix attribute:
TextField(
value = text,
onValueChange = { text = it },
suffix = { Text ("€") }
)
Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation attribute.
Something like:
TextField(
value = text,
onValueChange = { text = it },
singleLine = true,
visualTransformation = SuffixTransformation(" €"),
)
class SuffixTransformation(val suffix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val result = text + AnnotatedString(suffix)
val textWithSuffixMapping = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return offset
}
override fun transformedToOriginal(offset: Int): Int {
if (text.isEmpty()) return 0
if (offset >= text.length) return text.length return offset
}
}
return TransformedText(result, textWithSuffixMapping )
}
}
If you have the placeholder you can put a condition in the visualTransformation attribute.
Something like:
TextField(
value = text,
onValueChange = { text = it },
singleLine = true,
visualTransformation = if (text.isEmpty())
VisualTransformation.None
else
SuffixTransformation(" €"),
placeholder = { Text("Placeholder") }
)

I found Gabriele Mariotti's answer buggy. Needed to change transformedToOriginal function to this:
override fun transformedToOriginal(offset: Int): Int {
if (offset > text.length) return text.length
return offset
}

This is easily done in Compose like this:
const val SUFFIX = " $"
#Composable
fun SuffixedText() {
var text by remember { mutableStateOf("") }
TextField(
text,
singleLine = true,
visualTransformation = SuffixTransformer(SUFFIX),
onValueChange = { text = it }
)
}
class SuffixTransformer(val suffix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val result = text + AnnotatedString(suffix)
return TransformedText(result, OffsetMapping.Identity)
}
}
The above component probably can be used in traditional Views too. See this post
Also, see the following:
How to add a prefix to a TextField in Compose?
GitHub issue to add flowing suffix in Views in Material components for Android
Put constant text inside EditText in View which should be non-editable

Related

Equivalent of "expandedHintEnabled" in Jetpack Compose TextField

Is there any way to put OutlinedTextField's label on top (expanded) and still have the placeholder, when OutlinedTextField is not in focus?
In xml layouts we have TextInputLayout that has expandedHintEnabled attribute that does the expanding.
Currently the placeholder applies an alpha modifier with this condition InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f and there isn't a parameter to achieve the same behaviour of expandedHintEnabled.
A workaround can be to use a visualTransformation to display a placeholder when the text is empty, removing the placeholder parameter.
Something like:
val textColor = if (text.isEmpty())
MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
else
LocalContentColor.current.copy(LocalContentAlpha.current)
val textStyle = if (text.isEmpty())
LocalTextStyle.current.merge(MaterialTheme.typography.subtitle1)
else
LocalTextStyle.current
TextField(
value = text,
onValueChange = { text = it },
//placeholder = { Text("Placeholder") },
label = { Text("Label") },
visualTransformation = if (text.isEmpty())
PlaceholderTransformation("Placeholder")
else VisualTransformation.None,
textStyle = textStyle,
colors = TextFieldDefaults.textFieldColors(
textColor = textColor
)
)
with:
class PlaceholderTransformation(val placeholder: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return PlaceholderFilter(text, placeholder)
}
}
fun PlaceholderFilter(text: AnnotatedString, placeholder: String): TransformedText {
var out = placeholder
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return 0
}
override fun transformedToOriginal(offset: Int): Int {
return 0
}
}
return TransformedText(AnnotatedString(placeholder), numberOffsetTranslator)
}

Get the value from VisualTransformation Jetpack Compose TextField

How can I get the transformed value from VisualTransformation in Jetpack compose TextField? since the only thing that is changing is the visual text not the actual input/buffered text?
class CurrencyAmountInputVisualTransformation(
private val currencySymbol: String,
private val unbufferedValueChange: (String) -> Unit
) : VisualTransformation {
private val symbols = DecimalFormat().decimalFormatSymbols
private val numberOfDecimals: Int = 2
override fun filter(text: AnnotatedString): TransformedText {
val zero = symbols.zeroDigit
val inputText = text.text
val intPart = inputText
.dropLast(numberOfDecimals)
.reversed()
.chunked(3)
.joinToString(symbols.groupingSeparator.toString())
.reversed()
.ifEmpty {
zero.toString()
}
val fractionPart = inputText.takeLast(numberOfDecimals).let {
if (it.length != numberOfDecimals) {
List(numberOfDecimals - it.length) {
zero
}.joinToString("") + it
} else {
it
}
}
val formattedNumber = intPart + symbols.decimalSeparator.toString() + fractionPart
val value = inputText.dropLast(numberOfDecimals)
unbufferedValueChange("$value.$fractionPart")
val newText = AnnotatedString(
text = "$currencySymbol $formattedNumber",
spanStyles = text.spanStyles,
paragraphStyles = text.paragraphStyles
)
...
return TransformedText(newText, ...)
}
I'm getting duplicate re-composition because of this approach since I have 2 mutable state, one for the input and one for the value that I'm getting from a lambda callback I passed to the VisualTransformation.
#Composable
internal fun CurrencyField(
) {
val pattern = remember { Regex("^\\d*\\.?\\d*\$") }
var input by remember { mutableStateOf("") }
var amountInput by remember { mutableStateOf(0.00) }
OutlinedTextInputField(
text = input,
onTextChanged = {
if (it.isEmpty() || it.matches(pattern)) {
input = it
}
},
keyboardType = KeyboardType.NumberPassword,
visualTransformation = CurrencyAmountInputVisualTransformation("PHP") {
amountInput = it.toDouble()
}
)
}
Output:
Input screenshot
Is there any way I can get
232323.23
without using a callback in the VisualTransformation class?
Thanks.
You have to apply the same filter used by the VisualTransformation
var text by remember { mutableStateOf("") }
val visualTransformation = MyVisualTransformation()
TextField(
value = text,
onValueChange = { text = it },
visualTransformation = visualTransformation
)
val transformedText = remember(text, visualTransformation) {
visualTransformation.filter(AnnotatedString(text))
}.text.text

Variable doesn't update inside composable using jetpack compose

I'm trying out jetpack compose and have a basic textfield composable that accepts a boolean variable for validation.
However it doesn't update properly and only works at initialisation.
class RegistrationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var name = "zzz"
BasicField(
title = "Last Name",
value = name,
onValueChange = {name = it},
placeholder = "Enter Last Name",
validation = (name.length>2&&name.contains("zzz"))
)
}
}}
composable:
#Composable
fun BasicField(title: String,
value: String,
onValueChange: (String) -> Unit,
placeholder: String,
maxLines: Int = 1,
validation: Boolean ) {
var fieldValue by remember { mutableStateOf(TextFieldValue(value)) }
BasicTextField(
maxLines = maxLines,
value = fieldValue,
onValueChange = {
fieldValue = it
if(fieldValue.text.length>5){//this part works
Log.e("error","valid string")
}else{
Log.e("error","invalid string")
}
if(validation){//this part doesnt work
Log.e("error","custom validation works")
}else{
Log.e("error","custom validation failed")
}
},
) }
I have a basic validation inside the composable which checks for string length which works, but when the logic is from outside it doesn't work. I appreciate any help or hint thanks!
You need to make your name as state in compose. Here we have used 2 things.
name is defined as state. And it is remembered in composition. So whenever this composable function goes into re-composition, this name will not be re-assigned.
Used LaunchedEffect to execute your logs statements. So LaunchedEffect will start with a key and when they key changes, this effect will restart.
class RegistrationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 1
val name = remember { mutableStateOf("") }
BasicField(
title = "Last Name",
value = name.value,
onValueChange = {name.value = it},
placeholder = "Enter Last Name",
validation = (name.value.length>2&&name.value.contains("zzz"))
)
}
}}
#Composable
fun BasicField(title: String,
value: String,
onValueChange: (String) -> Unit,
placeholder: String,
maxLines: Int = 1,
validation: Boolean ) {
// 2
LaunchedEffect(key = value) {
if(value.length>5){
Log.e("error","valid string")
}else{
Log.e("error","invalid string")
}
if(validation){
Log.e("error","custom validation works")
}else{
Log.e("error","custom validation failed")
}
}
BasicTextField(
maxLines = maxLines,
value = value,
onValueChange = {
onValueChange(it)
},
)
}

How to return value in Jetpack Compose

I have a Composable with a Boxand some text and it also returns a value
How to use that value
#Composable
fun dummyAndUselessUI(String:String) : String{
val text = remember { mutableStateOf("") }
Box(modifier = Modifier.size(100.dp)){ Text(String) }
return text.value
}
You don't need a function that return a value, in Compose you handle State
#Composable
fun dummyScreen() {
var text by rememberSaveable { mutableStateOf("") }
dummyAndUselessUI(text = text, onNameChange = { text = it })
}
#Composable
fun dummyAndUselessUI(text: String, onTextChange: (String) -> Unit) {
Box(modifier = Modifier.size(100.dp)){
OutlinedTextField(
value = text,
onValueChange = onTextChange,
label = { Text("Name") }
)
}
}

How to prefix country code in Textfield using Jetpack Compose

I want my Textfield to be prefixed with a country code (+91), which can't be changed by the user.
How do I achieve this?
With M3 TextField,starting from 1.1.0-alpha06 you can use the prefix attribute:
//androidx.compose.material3
TextField(
value = text,
onValueChange = { text = it },
prefix = { Text ("+91") }
)
Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation property:
Something like:
TextField(
value = text,
onValueChange = { text = it},
visualTransformation = PrefixTransformation("(+91)")
)
with:
class PrefixTransformation(val prefix: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return PrefixFilter(text, prefix)
}
}
fun PrefixFilter(number: AnnotatedString, prefix: String): TransformedText {
var out = prefix + number.text
val prefixOffset = prefix.length
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return offset + prefixOffset
}
override fun transformedToOriginal(offset: Int): Int {
if (offset < prefixOffset) return 0
return offset - prefixOffset
}
}
return TransformedText(AnnotatedString(out), numberOffsetTranslator)
}
You can simply add Text() inside leadingIcon parameter inside textField
OutlinedTextField(
value = text,
onValueChange = onTextChange,
maxLines = 1,
leadingIcon =
{
Text(
text = prefixText,
style = textStyle,
color = Color.Black,
modifier = Modifier.padding(start = 24.dp, end = 8.dp)
)
}
)
Another option you could use as well as Gabriele's answer is using the leadingIcon property of TextField
TextField(
value = text,
onValueChange = { text = it},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_pound_symbol),
contentDescription = null,
tint = colorResourceFromAttr(id = R.attr.colorOnSurface)
)
}
)
Which gives you this:

Categories

Resources