How to convert text field input to integer in Jetpack compose? [duplicate] - android

This question already has answers here:
How to convert String to Int in Kotlin?
(11 answers)
Closed 1 year ago.
fun Add() {
var num1 by remember { mutableStateOf("") }
var num2 by remember { mutableStateOf("") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Arrangement.CenterHorizontally,
) {
TextField(
value = num1,
onValueChange = { num1 = it })
TextField (value = num2, onValueChange = { num2 = it })
}
remember { mutableStateOf("") }
val sum = num1 + num2
Column(
modifier = Modifier.fillMaxsize(), verticalAlignment = Alignment.End
) {
Button(onClick = {
Toast.maketext(
context,
"result:$sum",
Toast.LENGTH_SHORT
).show()
}) { Text("output") }
}
}
I'm getting output as concatenated values eg: 5 + 5 = 55 but I need out put as 5 + 5 = 10 sum of numbers.

This is no different than any other string to number conversion, instead of
val sum = num1 + num2
do
val sum = (num1.toInt() + num2.toInt()).toString()

Use use num1.toInt() and num2.toInt() to add the numbers up.

Related

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

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
}
}

Android Jetpack Compose - ChipGroup with limited number of rows

There is requirements to create ChipGroup with maximum lines/rows allowed, for all items that are not shown there should be last chip with text like "+3 more items".
I have tried FlowRow from accompanist, which works fine, but number of rows cannot be limited there (or I don't know how to :))
Another option I have tried is to implement custom layout. I have managed to limit number of rows (implementation similar to FlowRow with additional conditions), but I'm not sure how to append last item from layout(...placementBlock: Placeable.PlacementScope.() -> Unit)
Any help is appreciated
In such cases SubcomposeLayout should be used: it allows you to insert a composable depending on already measured views.
Applying this to FlowRow would take more time, because of many other arguments, so I've took my own simplified variant as the base.
#Composable
fun ChipVerticalGrid(
modifier: Modifier = Modifier,
spacing: Dp,
moreItemsView: #Composable (Int) -> Unit,
content: #Composable () -> Unit,
) {
SubcomposeLayout(
modifier = modifier
) { constraints ->
val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0)
var currentRow = 0
var currentOrigin = IntOffset.Zero
val spacingValue = spacing.toPx().toInt()
val mainMeasurables = subcompose("content", content)
val placeables = mutableListOf<Pair<Placeable, IntOffset>>()
for (i in mainMeasurables.indices) {
val measurable = mainMeasurables[i]
val placeable = measurable.measure(contentConstraints)
fun Placeable.didOverflowWidth() =
currentOrigin.x > 0f && currentOrigin.x + width > contentConstraints.maxWidth
if (placeable.didOverflowWidth()) {
currentRow += 1
val nextRowOffset = currentOrigin.y + placeable.height + spacingValue
if (nextRowOffset + placeable.height > contentConstraints.maxHeight) {
var morePlaceable: Placeable
do {
val itemsLeft = mainMeasurables.count() - placeables.count()
morePlaceable = subcompose(itemsLeft) {
moreItemsView(itemsLeft)
}[0].measure(contentConstraints)
val didOverflowWidth = morePlaceable.didOverflowWidth()
if (didOverflowWidth) {
val removed = placeables.removeLast()
currentOrigin = removed.second
}
} while (didOverflowWidth)
placeables.add(morePlaceable to currentOrigin)
break
}
currentOrigin = currentOrigin.copy(x = 0, y = nextRowOffset)
}
placeables.add(placeable to currentOrigin)
currentOrigin = currentOrigin.copy(x = currentOrigin.x + placeable.width + spacingValue)
}
layout(
width = maxOf(constraints.minWidth, placeables.maxOfOrNull { it.first.width + it.second.x } ?: 0),
height = maxOf(constraints.minHeight, placeables.lastOrNull()?.run { first.height + second.y } ?: 0),
) {
placeables.forEach {
val (placeable, origin) = it
placeable.place(origin.x, origin.y)
}
}
}
}
Usage:
val words = LoremIpsum().values.first().split(" ").map { it.filter { it.isLetter()} }
val itemView = #Composable { text: String ->
Text(
text,
modifier = Modifier
.background(color = Color.Gray, shape = CircleShape)
.padding(vertical = 3.dp, horizontal = 5.dp)
)
}
ChipVerticalGrid(
spacing = 7.dp,
moreItemsView = {
itemView("$it more items")
},
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(7.dp)
) {
words.forEach { word ->
itemView(word)
}
}
Result:

Formatting numbers in compose TextField

I am trying to create a reusable NumberField component:
#Composable
fun NumberField(
value: Number?,
onNumberChange: (Number) -> Unit,
) {
TextField(
value = value?.toString() ?: "",
onValueChange = {
it.toDoubleOrNull()?.let { value ->
if (value % 1.0 == 0.0) {
onNumberChange(value.toInt())
} else {
onNumberChange(value)
}
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
To be used as:
#Composable
fun NumberContent() {
val number = remember { mutableStateOf<Number?>(null) }
NumberField(value = number.value) {
number.value = it
}
}
I would like the number to be an Int or Double depending on what the user is typing. What I have above works until you try to enter a decimal number, as it seems "5.", does not parse as double. I want to allow the user to type 5. and then fill in rest. As such I don't want to add a zero after decimal automatically because that might not be the next number they want to enter. Is this the best way to go about it? I know that I can just accept any text and then try to format the text they entered later as an int or double and make them fix it then, just thought it would be nice to bundle it all in the composable.
You can use something like:
TextField(
value = text,
onValueChange = {
if (it.isEmpty()){
text = it
} else {
text = when (it.toDoubleOrNull()) {
null -> text //old value
else -> it //new value
}
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
Here is an implementation that handles all stated conditions while also exposing the state to parents.
#Composable
fun NumberField(
value: Number?,
onNumberChange: (Number?) -> Unit,
) {
val number = remember { mutableStateOf(value) }
val textValue = remember(value != number.value) {
number.value = value
mutableStateOf(value?.toDouble()?.let {
if (it % 1.0 == 0.0) {
it.toInt().toString()
} else {
it.toString()
}
} ?: "")
}
val numberRegex = remember { "[-]?[\\d]*[.]?[\\d]*".toRegex() }
// for no negative numbers use "[\d]*[.]?[\d]*"
TextField(
value = textValue.value,
onValueChange = {
if (numberRegex.matches(it)) {
textValue.value = it
number.value = it.toDoubleOrNull()
onNumberChange(number.value)
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
An example usage is shown below.
#Composable
fun DemoUsage() {
Column {
val number = remember { mutableStateOf<Number?>(null) }
NumberField(value = number.value) {
number.value = it
}
Button(onClick = { number.value = number.value?.toDouble()?.plus(1) }) {
Text("Increment")
}
}
}

Jetpack compose single number text field

I am trying to create a phone verification screen where the user must enter 5 numbers each in their own text field like below.
I have two questions:
Is there a way to limit a TextField to 1 character. I can set single line and max lines, but don't see a way to limit to character length like 'Ms' from the view system. I can easily limit the character length in code by ignoring characters after the first one, but this still lets the user 'scroll' to the left and right even though there is only 1 character.
Is there a way to wrap the width to the 1 character? Currently the only way I have found to limit the width is to set it specifically, but then if the system text size is changed it could break.
Here is some code incase it helps, this is some very jumbled together solution so apologies if something is incorrect:
#Composable
fun CodeTextFields(
modifier: Modifier = Modifier,
length: Int = 5,
onFilled: (code: String) -> Unit
) {
var code: List<Char> by remember {
mutableStateOf(listOf())
}
val focusRequesters: List<FocusRequester> = remember {
val temp = mutableListOf<FocusRequester>()
repeat(length) {
temp.add(FocusRequester())
}
temp
}
Row(modifier = modifier) {
(0 until length).forEach { index ->
OutlinedTextField(
modifier = Modifier
.weight(1f)
.padding(vertical = 2.dp)
.focusRequester(focusRequesters[index]),
textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
singleLine = true,
value = code.getOrNull(index)?.takeIf { it.isDigit() }?.toString() ?: "",
onValueChange = { value: String ->
if (focusRequesters[index].freeFocus()) { //For some reason this fixes the issue of focusrequestor causing on value changed to call twice
val temp = code.toMutableList()
if (value == "") {
if (temp.size > index) {
temp.removeAt(index)
code = temp
focusRequesters.getOrNull(index - 1)?.requestFocus()
}
} else {
if (code.size > index) {
temp[index] = value.getOrNull(0) ?: ' '
} else if (value.getOrNull(0)?.isDigit() == true) {
temp.add(value.getOrNull(0) ?: ' ')
code = temp
focusRequesters.getOrNull(index + 1)?.requestFocus() ?: onFilled(
code.joinToString(separator = "")
)
}
}
}
},
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
)
Spacer(modifier = Modifier.width(16.dp))
}
}
}
To solve this usecase you can use decoration box in BasicTextfield.
#Composable
fun InputField(
modifier: Modifier = Modifier,
text: String = "",
length: Int = 5,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onFocusChanged: () -> Unit = {},
onTextChange: (String) -> Unit
) {
val BoxHeight = 40.dp
val BoxWidth = 40.dp
var textValue by remember { mutableStateOf(text) }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
var isFocused by remember { mutableStateOf(false) }
if (text.length == length) {
textValue = text
}
BasicTextField(
modifier = modifier
.focusRequester(focusRequester)
.onFocusChanged {
isFocused = it.isFocused
onFocusChanged(it)
},
value = textValue,
singleLine = true,
onValueChange = {
textValue = it
if (it.length <= length) {
onTextChange.invoke(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
decorationBox = {
Row(Modifier.fillMaxWidth()) {
repeat(length) { index ->
Text(
text = textValue,
modifier = Modifier
.size(
width =
BoxWidth,
height = BoxHeight
)
.clip(RoundedCornerShape(4.dp))
,
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
)
if (acquireFocus && textValue.length != length) {
focusRequester.requestFocus()
}
}
This will create a boxes as shown below length number of times(5 here). This is just a simple TextField with decoration as multiple boxes. You can use this text in viewModel.
TextField Examples: https://developer.android.com/jetpack/compose/text
To limit to 1 number you can use something like:
#Composable
fun Field (modifier: Modifier = Modifier,
onValueChange: (String, String) -> String = { _, new -> new }){
val state = rememberSaveable { mutableStateOf("") }
OutlinedTextField(
modifier = modifier.requiredWidth(75.dp),
singleLine = true,
value = state.value,
onValueChange = {
val value = onValueChange(state.value, it)
state.value = value
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next),
)
}
and then use:
Field(onValueChange = { old, new ->
if (new.length > 1 || new.any { !it.isDigit() }) old else new
})
set your keyboard option number password type see the below code
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
below the keyboard output
It might be late but hopefully, this will help someone.
I came here to find a solution but found #Nikhil code which gave me an idea of how to do it (But failed to do it). So based on his answer I have improved the composable and fixed the issues. Have not heavily tested it yet but it acts the same as you want:
#Composable
fun DecoratedTextField(
value: String,
length: Int,
modifier: Modifier = Modifier,
boxWidth: Dp = 38.dp,
boxHeight: Dp = 38.dp,
enabled: Boolean = true,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions: KeyboardActions = KeyboardActions(),
onValueChange: (String) -> Unit,
) {
val spaceBetweenBoxes = 8.dp
BasicTextField(modifier = modifier,
value = value,
singleLine = true,
onValueChange = {
if (it.length <= length) {
onValueChange(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
decorationBox = {
Row(
Modifier.size(width = (boxWidth + spaceBetweenBoxes) * length, height = boxHeight),
horizontalArrangement = Arrangement.spacedBy(spaceBetweenBoxes),
) {
repeat(length) { index ->
Box(
modifier = Modifier
.size(boxWidth, boxHeight)
.border(
1.dp,
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(4.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = value.getOrNull(index)?.toString() ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6
)
}
}
}
})
}
Here is what it looks like:
You can customize the size of the font + the border color to your liking.
The only drawback is that you don't have a cursor and cannot edit each box separately. Will try to fix these issues and if I do, I will update my answer

Categories

Resources