TextField in Jetpack Compose [duplicate] - android

Is there any out of the box solution for limiting the character size in TextField's ?
I don't see any maxLength parameter like we had in XML.

With 1.x.y there isn't a built-in parameter.
You can use something like:
var text by remember { mutableStateOf(TextFieldValue("")) }
val maxChar = 5
TextField(
singleLine = true,
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
}
)
To display the counter text you can use something like:
val maxChar = 5
Column(){
TextField(
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
},
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "${text.length} / $maxChar",
textAlign = TextAlign.End,
style = MaterialTheme.typography.caption,
modifier = Modifier.fillMaxWidth().padding(end = 16.dp)
)
}

The first answer to this question works fine, but it´s true that in some cases there is an error that when exceeding the number of characters allowed, the value of the texfield is cleared. This failure seems to be due to predictive text, because if predictive text is disabled in android, it does not happen. One solution I found for now as a workaround is to use focusManager to "limit writing".
First, we need to get the focus manager to control the focus on the screen.
We can do this, by adding this line inside our composable function:
val focusManager = LocalFocusManager.current
Then, in our TextField we can use the focusManager to avoid the user to write more than the maxChar limit. We can move the focus to the next element, clear the focus when the maxChar limit is exceeded or receive a lambda function and perform the action we want . That depends on us.
var text by remember { mutableStateOf(TextFieldValue("")) }
val maxChar = 10
TextField(
singleLine = true,
value = text,
onValueChange = {
// This line will take (in case the user try to paste a text from the clipboard) only the allowed amount of characters
text = it.take(maxChar)
if (it.length > maxChar){
focusManager.moveFocus(FocusDirection.Down) // Or receive a lambda function
}
}
)
In this way the user could never write more characters than what is established by the limit. Obviously, this is an alternative solution, which in my case solved my problem, now we have to wait to see if they add it natively

Trim the most recently inserted character according to selection, if the new string exceeds the length.
fun TextFieldValue.ofMaxLength(maxLength: Int): TextFieldValue {
val overLength = text.length - maxLength
return if (overLength > 0) {
val headIndex = selection.end - overLength
val trailIndex = selection.end
// Under normal conditions, headIndex >= 0
if (headIndex >= 0) {
copy(
text = text.substring(0, headIndex) + text.substring(trailIndex, text.length),
selection = TextRange(headIndex)
)
} else {
// exceptional
copy(text.take(maxLength), selection = TextRange(maxLength))
}
} else {
this
}
}
Usage:
val (phone, setPhone) = remember {
mutableStateOf(TextFieldValue())
}
PaddingTextField(
value = phone,
onValueChange = { newPhone ->
setPhone(newPhone.ofMaxLength(11))
}
)

You can use take function - here documentation
onValueChange = { onYearChanged(it.take(limitNum)) })
For example, if you will use it in function.
const val limitNum = 4
#Composable
fun YearRow(
modifier: Modifier = Modifier,
year: Int,
onYearChanged: (String) -> Unit,
) {
OutlinedTextField(
modifier = modifier,
value = if (year == 0) "" else "$year",
onValueChange = { onYearChanged(it.take(limitNum)) },
)
}

Another way to do this that could be considered more flexible is something like:
Text(
text = "A string with a lot of charsssssssssssssssssssssssssss"
modifier = Modifier.fillMaxWidth(.5f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
this will constraint the width with the fillMaxWidth bit and the height with the maxLines part. If both of those constraints are hit the text will overflow and the behavior for overflow can be specified
in this case once the text occupied half of the view or went more than one line it would end up something like A string with a lot of charsssss...

Related

Cannot set a character count limit on Textfield Android Jetpack Compose

I'm developing an Android app using Jetpack Compose.
I'd like to set a character count limit on Textfield, but if I try this code, users can input maximum 1201 characters(maxChar+1), and if users input 1201 characters(exceed maxChar), they cannot delete any letters.
#Composable
fun PostEdit(navController: NavController, content: String, id: String) {
var editedContent = remember { mutableStateOf("$content")}
val maxChar = 1200
...
OutlinedTextField(
value = editedContent.value,
onValueChange = { newValue: String ->
if (editedContent.value.length <= maxChar) {
editedContent.value = newValue
}
},
label = { Text("Content", fontSize = 20.sp, color = Brown) },
...
On a different view, I set a character count limit, too, and it is successful. The difference is, this time, there is no parameter in "var intro". This is the code.
#Composable
fun Signup(navController: NavController)
var intro by remember { mutableStateOf("") }
val maxChar = 100
...
OutlinedTextField(
value = intro,
onValueChange = {
if (it.length <= maxChar) {
intro = it
}
},
label = { Text("Introduction", fontSize = 20.sp, color = Brown) },
...
Could someone help me? Thank you.
The second one (intro) you are using the implicit it parameter not the intro state variable
onValueChange = { // <-- implicit `it`
if (it.length <= maxChar) {
intro = it
}
}
while on the first (editedContent) one you are using the state variable instead so when editedContent's length reaches 1200 and you typed another character it will still satisfy your onValueChange condition <=, making it 1201, and when you attempt to delete, the condition won't be satisfied anymore, no changes will happen to editedContent
onValueChange = { newValue: String -> // <-- use this
if (editedContent.value.length <= maxChar) {
editedContent.value = newValue
}
}
so do what you did on the second one to your first one and it will work
onValueChange = { newValue: String ->
if (newValue.length <= maxChar) {
editedContent.value = newValue
}
}

Get Button Text

I have a Button composable that I need to get the text value when clicked.
Button(
onClick = {// Get the "TheText" from below },
) {
Text(
modifier = Modifier.padding(8.dp),
text = "TheText",
style = TextStyle(fontSize = 15.sp)
)
}
I am creating a type of quiz where the buttons text matches the correct answer.
I thin I may need to create a custom compposabe that takes the text as a parameter and also a callback function that will pass that text back up to my main program where I can Check for a correct answer.
I assume you want to input text instead of just displaying one.
#Composable
fun example() {
var text by remember { mutableStateOf("TheText") }
Column {
Button(
onClick = {
val useThisString = text
},
) { Text(text = text) } // Probably wanna put "Copy" here
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Optinal Label") }
)
}
}
Otherwise, if you just want the label of the button:
val buttonText = "TheText"
Button(
onClick = {
// Use variable here
},
) {
Text(
modifier = Modifier.padding(8.dp),
text = buttonText,
style = TextStyle(fontSize = 15.sp)
)
}
Ok here's how to store it in a variable
var retrievedValue by mutableStateOf("") //Assuming you must use it somewhere else as state
var currentValue by rememberSaveable { mutableStateOf("The Text") }
Button(
onClick = { retrievedValue = currentValue },
) {
Text(
modifier = Modifier.padding(8.dp),
text = currentValue,
style = TextStyle(fontSize = 15.sp)
)
}
Now you can use it anywhere you want. Changing the retrievedValue would trigger recompositions in any composable that reads it.
This should solve your problem. Use the retrievedValue as the value returned by a callback. In compose, for such stuff, you do not need callbacks. MutableState objects, whenever are modified, trigger a recomposition on the composables reading them.
Anyway, here when you click the button, you will get the value of the text, whatever it may be, you will always get the current value.

Jetpack Compose Number Input in to TextField

I am currently unable to capture user input in to a textfield when the KeyboardType of the keyboard is set to KeyboardType.Number.
If the keyboard is set to KeyboardType.Text, the Textfield updates as expected, however when set to KeyboardType.Number, the Textfield fails to update.
Why is this? and how can I change my code so that when the Textfield is clicked, a Number Keyboard is displayed ,and, when numbers are pressed, the relevant numbers are updated in the Textfield.
The following code DOES NOT update the textfield (When set to KeyboardType.Number)...
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
value.value = it
}
TextField(
value = text.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
onValueChange = change
)
}
The following code does update the textfield (When set to KeyboardType.Text)...
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
text.value = it
}
TextField(
value = value.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
onValueChange = change
)
}
Many Thanks
You are supposed to update text.value, not value.value there is a typo in your code change it to this.
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
value.value = it // you have this which is not correct and I don't think it even compiled
text.value = it // it is supposed to be this
}
TextField(
value = text.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
onValueChange = change
)
}

Do Assistive Text, Error Message, Character Counter exist for Jetpack Compose TextField?

In material design TextField page TextField has properties such as
Assistive elements provide additional detail about text entered into
text fields.
Helper text Helper text conveys additional guidance about the input field, such as how it will be used. It should only take up a single
line, being persistently visible or visible only on focus.
Error message When text input isn't accepted, an error message can display instructions on how to fix it. Error messages are displayed
below the input line, replacing helper text until fixed.
Icons Icons can be used to message alerts as well. Pair them with error messages to provide redundant alerts, which are useful when you
need to design for colorblind users.
Character counter Character or word counters should be used if there is a character or word limit. They display the ratio of
characters used and the total character limit.
Do these properties exist for Jetpack Compose TextField as of compose 1.0.0-alpha09?
With 1.0.x there aren't built-in properties to display an error message or the counter text.
However you can use a custom composable.
For the error message you can use something like:
var text by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
fun validate(text: String) {
isError = /* .... */
}
Column {
TextField(
value = text,
onValueChange = {
text = it
isError = false
},
trailingIcon = {
if (isError)
Icon(Icons.Filled.Error,"error", tint = MaterialTheme.colors.error)
},
singleLine = true,
isError = isError,
keyboardActions = KeyboardActions { validate(text) },
)
if (isError) {
Text(
text = "Error message",
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(start = 16.dp)
)
}
}
To display the counter text you can use something like:
val maxChar = 5
Column(){
TextField(
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
},
modifier = Modifier.fillMaxWidth()
)
Text(
text = "${text.length} / $maxChar",
textAlign = TextAlign.End,
style = MaterialTheme.typography.caption,
modifier = Modifier.fillMaxWidth().padding(end = 16.dp)
)
}
trailingIcon for the icon.
For the text at the bottom I just used
Text(
text = "Error message",
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(start = 16.dp)
)
Might be included in the future? Weirdly enough the documentation for isError says:
indicates if the text field's current value is in error. If set to
true, the label, bottom indicator and trailing icon by default will
be displayed in error color
What bottom indicator? There is no such thing. Weird.

TextField with Kotlin StateFlow

I'd like to have a TextField bound to a MutableStateFlow that comes from a view model. This is how I set it up:
#Composable
fun MyTextField(textFlow: MutableStateFlow<String>) {
val state = textFlow.collectAsState(initial = "")
TextField(
value = TextFieldValue(state.value),
onValueChange = { textFlow.value = it.text },
label = { Text(text = "Label") }
)
}
When I type something into the text field, it behaves really strangely. For example, if I type 'asd', it ends up with 'asdasa'. How can I update textFlow.value without messing up with the text field?
This error is caused by the usage of TextFieldValue with Flow.
To fix this, set the value of the TextField to just state.value and then on text change set the value with textFlow.value = it.
#Composable
fun MyTextField(textFlow: MutableStateFlow<String>) {
val state = textFlow.collectAsState(initial = "")
TextField(
value = state.value,
onValueChange = { textFlow.value = it },
label = { Text(text = "Label") }
)
}

Categories

Resources