I'm trying make BasicTextField select all contents when user taps on it.
This happens automatically if user will long press on the contents of BasicTextField but I want it happening without the long press.
There's a thread Select all text of TextField in Jetpack Compose but it utilizes TextFieldValue object which is hidden in BasicTextField but can be passed in TextField composable.
You can use the TextFieldValue also with a BasicTextField.
Instead of using the onFocusChanged modifier you can use the interactionSource and change the selection range depending on it.
Something like:
var textFieldValue by remember { mutableStateOf(TextFieldValue("Custom text")) }
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
LaunchedEffect(isFocused) {
val endRange = if (isFocused) textFieldValue.text.length else 0
textFieldValue = textFieldValue.copy(
selection = TextRange(
start = 0,
end = endRange
)
)
}
BasicTextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
interactionSource = interactionSource
)
Related
I have a Box with textfield inside. i want to hide keyboard by touching on the Box and use modifier of Box for it:
.onFocusEvent { focusState ->
if (focusState.isFocused) {
keyboardController?.hide()
}
}
But this applies to the text field as well, so the keyboard doesn't show up at all.
How can i fix it?
You can use the InteractionSource to know if the TextField is focused.
Something like:
var text by remember { mutableStateOf("")}
val interactionSource = remember { MutableInteractionSource() }
val isFocused = interactionSource.collectIsFocusedAsState().value
val keyboardController = LocalSoftwareKeyboardController.current
Box(Modifier
.fillMaxWidth()
.clickable {
if (isFocused) keyboardController?.hide()
}
){
TextField(
text,
onValueChange = { text = it },
interactionSource = interactionSource
)
}
Many Android apps feature a search box in the app bar / toolbar, that looks somewhat like this screenshot:
How do I recreate something similar in Jetpack Compose + Material3? I would like the text field to have these features:
support showing a hint when the content string is empty (e.g. "Type your search...")
auto-focus on the first composition, so that the keyboard is opened
put the cursor at the end of the content string on the first composition
I tried to reproduce the same behavior in Jetpack Compose + Material3 by putting a TextField in the title of a CenterAlignedTopAppBar but the result does not look good. The text field uses the whole height of the app bar and has a grey background, and both of these things look odd.
I came up with a AppBarTextField after some engineering, see the code below. I had to use the lower-level BasicTextField since the normal TextField is not customizable enough. The code having to do with theming and color was copied directly from TextField's implementation, so that the theme's customizations apply normally to the components of the text field.
The parameters the AppBarTextField composable accepts are:
value: the content string to show in the text field
onValueChange: new values are passed here (remember to update value!)
hint: the hint to show when the text field is empty
modifier, keyboardOptions and keyboardActions: they are passed directly to BasicTextField and they behave the same as they would in a normal TextField. If you need to customize other TextField parameters just add them to the function signature and then pass them to BasicTextField.
The requested features are implemented:
the focus acquisition was achieved with a SideEffect, so that it would only happen on the first composition
putting the cursor at the end on the first composition required using a TextFieldValue
the strange-looking background is not present anymore, since no .background() modifier is present (while it is in the normal TextField)
the hint was added using by passing a placeholder to TextFieldDecorationBox in the decorationBox parameter (note that this was also possible with TextField)
TextFieldDecorationBox's padding is also now only 4dp. Padding was added here (and not with a modifier on BasicTextField) since otherwise the bottom line indicator (which is, instead, displayed using the .indicatorLine() modifier) would not be shown correctly.
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun AppBarTextField(
value: String,
onValueChange: (String) -> Unit,
hint: String,
modifier: Modifier = Modifier,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
) {
val interactionSource = remember { MutableInteractionSource() }
val textStyle = LocalTextStyle.current
// make sure there is no background color in the decoration box
val colors = TextFieldDefaults.textFieldColors(containerColor = Color.Unspecified)
// If color is not provided via the text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
MaterialTheme.colorScheme.onSurface
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor, lineHeight = 50.sp))
// request focus when this composable is first initialized
val focusRequester = FocusRequester()
SideEffect {
focusRequester.requestFocus()
}
// set the correct cursor position when this composable is first initialized
var textFieldValue by remember {
mutableStateOf(TextFieldValue(value, TextRange(value.length)))
}
textFieldValue = textFieldValue.copy(text = value) // make sure to keep the value updated
CompositionLocalProvider(
LocalTextSelectionColors provides LocalTextSelectionColors.current
) {
BasicTextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
// remove newlines to avoid strange layout issues, and also because singleLine=true
onValueChange(it.text.replace("\n", ""))
},
modifier = modifier
.fillMaxWidth()
.heightIn(32.dp)
.indicatorLine(
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors
)
.focusRequester(focusRequester),
textStyle = mergedTextStyle,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = true,
decorationBox = { innerTextField ->
// places text field with placeholder and appropriate bottom padding
TextFieldDefaults.TextFieldDecorationBox(
value = value,
visualTransformation = VisualTransformation.None,
innerTextField = innerTextField,
placeholder = { Text(text = hint) },
singleLine = true,
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors,
contentPadding = PaddingValues(bottom = 4.dp)
)
}
)
}
}
Here is an example usage:
var value by rememberSaveable { mutableStateOf("initial content") }
CenterAlignedTopAppBar(
title = {
AppBarTextField(
value = value,
onValueChange = { newValue -> value = newValue },
hint = "A hint..."
)
},
navigationIcon = /* the back icon */,
actions = /* the search icon */
)
I have a BasicTextField in my jetpack compose function. When i click(user has input some text into the textfield by now) on a button to navigate to another composable in my NavHost, and from that new view click on back to the composable which i came from which has the textfield, the textfield is empty. I want to keep the text that the user typed in before navigating, but I can't figure it out how. Have looked here but found no answer.
Suggestions?
Here is my code:
#Composable
fun SearchBar(
modifier: Modifier = Modifier,
onSearch: (String) -> Unit = {}
) {
var text by remember {
mutableStateOf("")
}
Box(modifier = modifier) {
BasicTextField(
value = text,
onValueChange = {
text = it
onSearch(it)
},
maxLines = 1,
singleLine = true,
textStyle = TextStyle(color = Color.Black),
modifier = Modifier
.fillMaxWidth()
.shadow(5.dp, CircleShape)
.background(Color.White, CircleShape)
)
}
}
remember saves the value over recomposition (i.e., when your state changes, your composable automatically recomposes with the new state).
As per the Restore UI State guide, you can replace remember with rememberSaveable to save your state across:
Configuration changes
Process death and recreation
Your composable being put on the NavHost back stack
As well as any other case where your SearchBar could be removed from composition and then re-added (such as if you were using in Accompanist's Pager or in a LazyColumn or LazyRow).
var text by rememberSaveable {
mutableStateOf("")
}
Traditional EditText on Android supports android:selectAllOnFocus attribute, which causes its content to be selected when user, for example, clicks on the EditText.
How can this behavior be achieved when using androidx.compose.material.TextField in Jetpack Compose?
You can collect the focus state from MutableInteractionSource and change the selection state depending on it:
var textFieldValue by remember { mutableStateOf(TextFieldValue("Lorem ipsum")) }
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
LaunchedEffect(isFocused) {
textFieldValue = textFieldValue.copy(
selection = if (isFocused) {
TextRange(
start = 0,
end = textFieldValue.text.length
)
} else {
TextRange.Zero,
}
)
}
TextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
interactionSource = interactionSource,
)
I have a composable function with TextField:
val focusManager = LocalFocusManager.current
TextField(
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
}
)
)
and I need to show keyboard from inside of composable function as well as outside of it when I click on other button which is not part of composable content. Basically I wanna call hideKeyboard() from my fragment.
I tried to use livedata inside composable:
val shouldShowKeyBoard by shouldShowSearchKeyBoard.observeAsState()
and I can do focusManager.clearFocus() to hide keyboard but I'm not sure how to show it programmatically for specific compose TextField
What's the "compose" way to manage hide/show keyboard?
You can perform some action on state changes and you can do it using the side effects.
For example you can use the LaunchedEffect function, where as a key you can pass a state you want to listen.
LaunchedEffect(booleanValue) {
//...do something
}
You can use a ViewModel to set a boolean value and something like:
// initialize focus reference to be able to request focus programmatically
val focusRequester = remember { FocusRequester() }
LaunchedEffect(viewModel.showKeyboard) {
focusRequester.requestFocus()
}
TextField(
value = text,
onValueChange = {
text = it },
modifier = Modifier
// add focusRequester modifier
.focusRequester(focusRequester)
)
Just a note:
to hide the keyboard you can also use:
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
//...
keyboardActions = KeyboardActions(
onSearch = { keyboardController?.hide() }
)
Use the method focusManager.clearFocus() to dismiss the keyboard and clear the focus.
to add on Gabriele's answer, you will need val focusRequester = remember { FocusRequester() }
or you will get an exception