I want to create textfield with hint text in jetpackcompose. Any example how create textfield using jectpack? Thanks
compose_version = '1.0.0-beta07'
Use parameter placeholder to show hint
TextField(value = "", onValueChange = {}, placeholder = { Text("Enter Email") })
Use parameter label to show floating label
TextField(value = "", onValueChange = {}, label = { Text("Enter Email") })
You can use
the label parameter in the TextField composable to display a floating label
the placeholder parameter to display a placeholder when the text field is in focus and the input text is empty.
You can also use them together.
Something like:
var text by remember { mutableStateOf("text") }
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
label = {
Text("Label")
}
)
or
TextField(
value = text,
onValueChange = {
text = it
},
label = {
Text("Label")
},
placeholder = {
Text("Placeholder")
}
)
You can create hintTextField in jetpackCompose like below code:
#Composable
fun HintEditText(hintText: #Composable() () -> Unit) {
val state = state { "" } // The unary plus is no longer needed. +state{""}
val inputField = #Composable {
TextField(
value = state.value,
onValueChange = { state.value = it }
)
}
if (state.value.isNotEmpty()) {
inputField()
} else {
Layout(inputField, hintText) { measurable, constraints ->
val inputfieldPlacable = measurable[inputField].first().measure(constraints)
val hintTextPlacable = measurable[hintText].first().measure(constraints)
layout(inputfieldPlacable.width, inputfieldPlacable.height) {
inputfieldPlacable.place(0.ipx, 0.ipx)
hintTextPlacable.place(0.ipx, 0.ipx)
} }
}
}
Call #Compose function like below:
HintEditText #Composable {
Text(
text = "Enter Email",
style = TextStyle(
color = Color.White,
fontSize = 18.sp
)
)
}
Jetpack compose version: dev08
The benefit of compose is that we can easily create our widgets by composing current composable functions.
We can just create a function with all parameters of the current TextField and add a
hint: String parameter.
#Composable
fun TextFieldWithHint(
value: String,
modifier: Modifier = Modifier.None,
hint: String,
onValueChange: (String) -> Unit,
textStyle: TextStyle = currentTextStyle(),
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified,
onFocus: () -> Unit = {},
onBlur: () -> Unit = {},
focusIdentifier: String? = null,
onImeActionPerformed: (ImeAction) -> Unit = {},
visualTransformation: VisualTransformation? = null,
onTextLayout: (TextLayoutResult) -> Unit = {}
) {
Stack(Modifier.weight(1f)) {
TextField(value = value,
modifier = modifier,
onValueChange = onValueChange,
textStyle = textStyle,
keyboardType = keyboardType,
imeAction = imeAction,
onFocus = onFocus,
onBlur = onBlur,
focusIdentifier = focusIdentifier,
onImeActionPerformed = onImeActionPerformed,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout)
if (value.isEmpty()) Text(hint)
}
}
We can use it like this:
#Model
object model { var text: String = "" }
TextFieldWithHint(value = model.text, onValueChange = { data -> model.text = data },
hint= "Type book name or author")
The pitfall of this approach is we are passing the hint as a string so if we want to style the hint we should add extra parameters to the TextFieldWithHint (e.g hintStyle, hintModifier, hintSoftWrap, ...)
The better approach is to accept a composable lambda instead of string:
#Composable
fun TextFieldWithHint(
value: String,
modifier: Modifier = Modifier.None,
hint: #Composable() () -> Unit,
onValueChange: (String) -> Unit,
textStyle: TextStyle = currentTextStyle(),
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified,
onFocus: () -> Unit = {},
onBlur: () -> Unit = {},
focusIdentifier: String? = null,
onImeActionPerformed: (ImeAction) -> Unit = {},
visualTransformation: VisualTransformation? = null,
onTextLayout: (TextLayoutResult) -> Unit = {}
) {
Stack(Modifier.weight(1f)) {
TextField(value = value,
modifier = modifier,
onValueChange = onValueChange,
textStyle = textStyle,
keyboardType = keyboardType,
imeAction = imeAction,
onFocus = onFocus,
onBlur = onBlur,
focusIdentifier = focusIdentifier,
onImeActionPerformed = onImeActionPerformed,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout)
if (value.isEmpty()) hint()
}
}
We can use it like this:
#Model
object model { var text: String = "" }
TextFieldWithHint(value = model.text, onValueChange = { data -> model.text = data },
hint= { Text("Type book name or author", style = TextStyle(color = Color(0xFFC7C7C7))) })
var textState by remember { mutableStateOf(TextFieldValue()) }
var errorState by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf("") }
TextField(
value = textState,
onValueChange = {
textState = it
when {
textState.text.isEmpty() -> {
errorState = true
errorMessage = "Please Enter Site Code"
}
else -> {
errorState = false
errorMessage = ""
}
}
},
isError = errorState,
label = {
Text(
text = if (errorState) errorMessage
else "You Hint"
)
},
modifier = Modifier
.padding(top = 20.dp, start = 30.dp, end = 30.dp)
.fillMaxWidth())
If you want to create a text field with hint text and custom color here is the code for that.
#Composable
fun PhoneOrEmail() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Phone/email", style = TextStyle(color =
Color.Red)) }
)//End of TextField
}//End of Function
Here's what works for me (I think it's a bit simpler than what Anas posted since it's using the same component:
#Composable
fun TextBox(
loginInput: LoginInput,
hint: String = "enter value",
color: Color = Color.LightGray,
height: Dp = 50.dp
) {
val state = +state { "" }
state.value = if (loginInput.usernameEntered) loginInput.username else hint
Surface(color = color) {
Row {
Container(modifier = Expanded, height = height) {
Clip(shape = RoundedCornerShape(15.dp)) {
Padding(padding = 15.dp) {
TextField(
value = state.value,
keyboardType = KeyboardType.Text,
onFocus = {
if (!loginInput.usernameEntered)
state.value = ""
},
onValueChange = {
loginInput.usernameEntered = true
loginInput.username = it
state.value = loginInput.username
}
)
}
}
}
}
}
}
the label parameter will be displayed as text if text is empty and moves above the textfield (as label) on typing input:
#Composable
fun SearchField() {
val (text, setText) = remember { mutableStateOf(TextFieldValue("")) }
Box(modifier = Modifier.width(180.dp).padding(2.dp)) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { setText(it) },
label = { Text("quick do:") },
)
}
}
Related
I'm trying to implement ExposedDropdownMenu - which I want to be displayed underneath TextField - when I set height of dropdown to max. 20 dp then everything is okay. But for any greater value it is always displayed above. Do you know what could be the issue here?
How it looks like:
My code:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun TextFieldWithDropdown(
modifier: Modifier = Modifier,
textFieldState: TextFieldState,
textCallback: (String) -> Unit,
list: List<String>,
keyboardOptions: KeyboardOptions,
textStyle: TextStyle
) {
// .align(Alignment.CenterStart)
val dropDownOptions = remember { mutableStateOf(listOf<String>()) }
val textFieldValue = remember { mutableStateOf(TextFieldValue()) }
val dropDownExpanded = remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = modifier,
expanded = dropDownExpanded.value, onExpandedChange = {
dropDownExpanded.value = !dropDownExpanded.value
}) {
TextField(
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused)
dropDownExpanded.value = false
},
value = textFieldState.text.value,
onValueChange = {
textFieldState.text.value = it
textCallback.invoke(it)
dropDownOptions.value =
list.filter { it.startsWith(textFieldState.text.value) && it != textFieldState.text.value }
.take(3)
dropDownExpanded.value = dropDownOptions.value.isNotEmpty()
},
singleLine = true,
maxLines = 1,
textStyle = textStyle,
)
ExposedDropdownMenu(
expanded = dropDownExpanded.value,
onDismissRequest = {
dropDownExpanded.value = false
},
) {
dropDownOptions.value.forEach { text ->
DropdownMenuItem(text = {
Text(text = text)
}, onClick = {
textFieldState.text.value = text
dropDownExpanded.value = false
})
}
}
}
}
I keep getting this error whenever I try to use a textfield in Compose, I have tried both Textfield implementations, ie one with a String value and TextFieldValue arguments but still get the error, I have also tried using
var text = rememberSaveable{mutableStateOf("")}
and
var text by remember {mutableStateOf("")}. I have also tried hoisting the State which is what I wanted to do in the first place but still get the error
Here's the code
fun SearchAppBar(
query: String,
onQueryChanged: (String) -> Unit,
onExecuteSearch: () -> Unit,
scrollPosition: Int,
selectedCategory: FoodCategory?,
onSelectedCategoryChanged: (String) -> Unit,
onCategoryChangePosition: (Int) -> Unit,
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color.White,
elevation = 8.dp,
) {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
) {
TextField(
modifier = Modifier
.fillMaxWidth(.9f)
.padding(8.dp),
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done,
),
leadingIcon = {
Icon(Icons.Filled.Search)
},
onImeActionPerformed = { action, softKeyboardController ->
if (action == ImeAction.Done) {
onExecuteSearch()
softKeyboardController?.hideSoftwareKeyboard()
}
},
textStyle = TextStyle(color = MaterialTheme.colors.onSurface),
backgroundColor = MaterialTheme.colors.surface
)
}
val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, bottom = 8.dp)
.horizontalScroll(scrollState)
) {
scope.launch {
scrollState.scrollTo(
scrollPosition
)
}
for (category in getAllFoodCategories()) {
FoodCategoryChip(
category = category.value,
isSelected = selectedCategory == category,
onSelectedCategoryChanged = {
onSelectedCategoryChanged(it)
onCategoryChangePosition(
getAllFoodCategories().indexOf(selectedCategory)
)
},
onExecuteSearch = {
onExecuteSearch()
}
)
}
}
}
}
}
You can only use the combination of parameters contained in only one of the implementations. You can't, for example, use keyboardOptions alongside onImeActionPerformed.
Turns out the backgroundColor property isnt valid anymore, use colors:. Also as pointed out by Richard Onslow Roper in his answer, look through the constructor of the TextField and see which properties are not part of your the Version of TextField you want to use
Don't do
TextField(
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
backgroundColor = MaterialTheme.colors.surface
)
do this instead
TextField(
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
colors= TextFieldDefaults.textFieldColors(
backgroundColor = MaterialTheme.colors.surface
)
)
I've developed a textInput composable with a trailing icon, and I'd like to clear the textInput when the icon is clicked. How can I access the textInput value, so that I can clear it?
#Composable
fun TextInput(
myVal: String,
label: String,
placeholder: String="",
helperText: String="",
errorText: String="",
onValueChange : (String) -> Unit){
val hasError = !errorText.isNullOrEmpty()
val helperColor = if (hasError)
Color(0xFFfe392f)
else
Color(0xFF5c6a79)
Row() {
Column() {
TextField(
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
textColor = Color(0xFF011e41),
cursorColor = Color(0xFF011e41),
focusedLabelColor = Color(0xFF011e41),
unfocusedLabelColor = Color(0xFF011e41),
unfocusedIndicatorColor = Color(0xFFebeced),
focusedIndicatorColor = Color(0xFF011e41),
errorCursorColor = Color(0xFFfe392f),
errorIndicatorColor = Color(0xFFfe392f),
errorLabelColor = Color(0xFFfe392f)
),
value = myVal,
onValueChange = onValueChange,
label = { Text(label) },
placeholder = { Text(placeholder) },
isError = hasError,
trailingIcon = {Icon(Icons.Filled.Email, contentDescription = "sdsd", modifier = Modifier.offset(x= 10.dp).clickable {
//What should I do here?
})}
)
Text(
modifier = Modifier.padding(8.dp),
text = if (hasError) errorText else helperText,
fontSize = 12.sp,
color = helperColor,
)
}
}
}
it's used like this:
var text by remember { mutableStateOf("") }
TextInput(myVal = text, label = "label", helperText = "", errorText = "my error") {text = it}
You can use the trailingIcon attribute with a custom clickable modifier.
Something like:
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
trailingIcon = {
Icon(Icons.Default.Clear,
contentDescription = "clear text",
modifier = Modifier
.clickable {
text = ""
}
)
}
)
If you are using a TextFieldValue:
val content = "content"
var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(content))
}
TextField(
value = text,
onValueChange = { text = it },
trailingIcon = {
Icon(Icons.Default.Clear,
contentDescription = "clear text",
modifier = Modifier
.clickable {
text = TextFieldValue("")
}
)
}
)
the click handler of your trailing icon has to call the TextField's onValueChange with an empty string:
...
trailingIcon = {
Icon(
Icons.Filled.Email,
contentDescription = "sdsd",
modifier = Modifier
.offset(x= 10.dp)
.clickable {
//just send an update that the field is now empty
onValueChange("")
}
)
}
...
Use trailingIcon Composable property with IconButton to match the background selector of the icon with the rest of the theme. You can also put empty condition to display it only when there is some input in the text field.
Below is sample code snippet:
var text by remember { mutableStateOf ("") }
TextField(
trailingIcon = {
when {
text.isNotEmpty() -> IconButton(onClick = {
text = ""
}) {
Icon(
imageVector = Icons.Filled.Clear,
contentDescription = "Clear"
)
}
}
}
)
This shall achieve this
//Caller
val text by remember { mutableStateOf (...) }
TextInput(text, ..., ...,)
//Composable
#Composable
fun TextInput(text, ..., ...){
val textState by remember { mutableStateOf (text) }
TextField(
value = textState,
trailingIcon = {
Icon(..., Modifier.clickable { textState = "" })
}
}
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
I have a dialog with and an editable textfield. Everything is working fine but I cant't change cursor positon when I have already started editing the field. I have to press the back button and focus it again in correct spot. Any ideas?
#Composable
inline fun DialogInputText(display: MutableState<Boolean>, text: String, title: String, noinline onTextSet: (String)->Unit){
BaseDialog(display = display ) {
var text by remember { mutableStateOf(TextFieldValue(text)) }
DialogHeader(title = title)
EditText(text, { text = it}, modifier = Modifier.padding(8.dp).height(30.dp))
DialogButtons {
TextButton(onClick = {display.value = false} ) {
Text(text = "ZPĚT")
}
TextButton(onClick = {onTextSet.invoke(text.text)}) {
Text(text = "POKRAČOVAT")
}
}
}
}
#Composable
fun EditText(
text: TextFieldValue,
onValueChange: (TextFieldValue)->Unit,
validate: (TextFieldValue)->Boolean = {true},
modifier: Modifier = Modifier,
label: String = "",
textStyle:TextStyle = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Bold
),
keyboardType: KeyboardType = KeyboardType.Text,
color: Color = Colors.ChipGray
){
val valid = validate(text)
val color = if (valid) color else Colors.NotCompleted
Stack(modifier.background(color, shape = RoundedCornerShape(50)), alignment = Alignment.Center) {
if (text.text.isEmpty())
Text(text = label)
BaseTextField(
value = text,
onValueChange = onValueChange,
textStyle = textStyle,
modifier = Modifier.padding(start = 8.dp, end = 8.dp).fillMaxWidth(),
keyboardType = keyboardType,
)
}
}
It was discussed here. But I couldn't get working solution out of it.
Change Cursor Position and force SingleLine of TextField in Jetpack Compose