How to show keyboard with Jetpack Compose? - android-jetpack-compose

How can I slide in the keyboard?
I tried:
val keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current
keyboardController?.show()
But it does not work. What am I missing? Maybe some Manifest flags?

To show keyboard in Compose:
val showKeyboard = remember { mutableStateOf(true) }
val focusRequester = remember { FocusRequester() }
val keyboard = LocalSoftwareKeyboardController.current
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
value = value,
textStyle = MaterialTheme.typography.body2,
onValueChange = { onValueChange(it)},
label = { Text(label) }
)
// LaunchedEffect prevents endless focus request
LaunchedEffect(focusRequester) {
if (showKeyboard.equals(true)) {
focusRequester.requestFocus()
delay(100) // Make sure you have delay here
keyboard?.show()
}
}

Locked for 3 hours. There are disputes about this answer’s content being resolved at this time. It is not currently accepting new interactions.
What's the issue with the official method?
fun showSoftKeyboard(view: View) {
if (view.requestFocus()) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
}
Read more here.
This method works if you just use it like this:
showSoftKeyboard(AndroidView(context))
You could also try eliminating the parameter entirely by placing the AndroidView in the function's body instead.

Related

Manually move accessibility focus in Jetpack Compose

With Android View, I'm able to move the focus to a View like so:
fun View.requestAccessibilityFocus() {
requestFocus()
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
How do I achieve this in Jetpack Compose?
I tried using FocusRequester but it doesn't seem to do anything:
val lifecycleOwner = LocalLifecycleOwner.current
val requester = FocusRequester()
Box {
...
Image(
...
contentDescription = "My heading",
modifier = Modifier
...
.focusRequester(requester)
)
}
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
requester.requestFocus()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
I had to add .focusable() and must do so after .focusRequester(focusRequester).
Box {
...
Image(
...
contentDescription = "My heading",
modifier = Modifier
...
.focusRequester(requester)
.focusable()
)
}
This happens because DiposableEffect might be calling during recomposition. You should not request focus during composition. The solution I have found is to request a focus right after the composition. Like this
LaunchedEffect(Unit) {
this.coroutineContext.job.invokeOnCompletion {
focusRequester.requestFocus()
}
}
This makes sure your code will run when the LauchedEffect leaves the composition because it(the coroutine) either got canceled or completed.

Jetpack compose manage soft keyboard events

so I have a OutlinedTextField and I want to have a undeletable prefix in it.
here is my OutlinedTextField:
OutlinedTextField(
modifier = Modifier
.focusRequester(focusRequester),
value = viewModel.phoneNumber.collectAsState().value,
onValueChange = { viewModel.onPhoneNumberChange(it) },
label = { Text("phone number") },
singleLine = true
)
and here is my viewModel:
private val _phoneNumber = MutableStateFlow("+")
val phoneNumber: StateFlow<String> = _phoneNumber
fun onPhoneNumberChange(
value: String
) {
viewModelScope.launch {
if (_phoneNumber.value.length == 1 && value == "") {
return#launch
} else
_phoneNumber.emit(value.take(phoneNumberMaxLength))
}
}
as you can see I managed to not emit new value when its delete event and it works fine.
but the problem is the coursor goes behind of the prefix when I press back space button
sothe question is how can i subscribe on soft keyboard with jetpack compose and manage back space button event?

I have a composable not setting button text as expected; wondering why. Have a reproducible example

this started as a new compose project
with the following code the intent is to change the text to the picked time. The code is commented where the behavior occurs
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTestTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
TimeCardButton(id = 1, symbol ="In", enabled=true,modifier = Modifier) { entry ->
Log.d("click", "$entry result")
}
}
}
}
}
}
data class TimeCardEntry(val id: Int = -1, var entry: String = "")
#Composable
fun TimeCardButton(
id: Int,
symbol: String,
enabled: Boolean = false,
modifier: Modifier,
onValueChange: (TimeCardEntry) -> Unit = {},
) {
// Value for storing time as a string
val timeState = remember {
mutableStateOf(TimeCardEntry(id, symbol))
}
val validState = remember {
timeState.value.entry.trim().isNotEmpty()
}
val mTime = remember { mutableStateOf(symbol) }
if (enabled) {
// Fetching local context
val mContext = LocalContext.current
// Declaring and initializing a calendar
val mCalendar = Calendar.getInstance()
val mHour = mCalendar[Calendar.HOUR_OF_DAY]
val mMinute = mCalendar[Calendar.MINUTE]
// Creating a TimePicker dialog
val mTimePickerDialog = TimePickerDialog(
mContext,
{ _, mHour: Int, mMinute: Int ->
timeState.value.entry = "$mHour:$mMinute"
mTime.value = "$mHour:$mMinute"
onValueChange(timeState.value)
}, mHour, mMinute, false
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(CircleShape)
.then(modifier)
) {
TextButton(onClick = { mTimePickerDialog.show() }.also {
Log.d("click", "id $id clicked!") }) {
Column() {
// if I use just this it works [in changes to the time picked]
//Text(text = mTime.value)
// if i use both of these BOTH are set when the date picker is invoked
// if I just use the second one alone, the text never changes
Text(text = timeState.value.entry)
}
}
}
} else {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(CircleShape)
.then(modifier)
) {
Text(text = symbol, color =
MaterialTheme.colors.onBackground)
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
MyApplicationTestTheme {
}
}
First of all how to fix it:
Your problem basically is this. The easiest way to fix it would be to reassign the whole value of TimeState, not just entry by calling
timeState.value = timeState.value.copy(entry = "$mHour:$mMinute")
The reason it doesn't work with only the second one is that the change of a property doesn't trigger recomposition, even if the variable containing it is a mutableState. To fix (as outlined in the answers to the question linked above) this you either have to reassign the whole variable or make the parameter you want to observe observable (for example changing the String to State<String>)
PS: if you use by with mutableStateOf (i.e. val timeState = remember { mutableStateOf(TimeCardEntry(id, symbol)) }) you don't have to use .value every time. I find that a lot cleaner and more readable

How to perform a haptic feedback in Jetpack Compose

Using jetpack compose, for a clickevent how to perform haptic feedback. I am new to jetpack compose.
This is what i tried -
val hapticFeedback = LocalHapticFeedback
#Composable
fun Tab() {
Row() {
Icon(imageVector = icon, contentDescription = text)
if (selected) {
// i tried both the following ways, none are working.
hapticFeedback.current.performHapticFeedback(
HapticFeedbackType(10)
)
hapticFeedback.current.performHapticFeedback(HapticFeedbackType.TextHandleMove)
....
Spacer(Modifier.width(12.dp))
Text(text.uppercase(Locale.getDefault()))
}
}
}
I am able to see the text when it is getting selected, but not getting a subtle vibrating feedback.
In version rc-01 of Compose you can use only two types of Haptic Feedback: HapticFeedbackType.LongPress or HapticFeedbackType.TextHandleMove.
val haptic = LocalHapticFeedback.current
val context = LocalContext.current
Row(
Modifier.clickable {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
)
Currently (Compose UI 1.1.0-beta03) only LongPress and TextHandleMove are supported via
val haptic = LocalHapticFeedback.current
Button(onClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}) { ... }
as #nglauber answer said.
I guess it is because of the multi-platform support for Compose Desktop.
There's another way, however, if you are on Android and you do not need Compose Desktop compatibility, it's fairly easy to use it:
val view = LocalView.current
Button(onClick = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}) { ... }
The HapticFeedbackConstants class has a lot of constants.
On my phone (and other devices I've tested on) neither LongPress nor TextHandleMove makes the phone vibrate.
We worked around this before we moved to Compose like this:
import android.content.Context
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.accessibility.AccessibilityManager
fun View.vibrate() = reallyPerformHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
fun View.vibrateStrong() = reallyPerformHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
private fun View.reallyPerformHapticFeedback(feedbackConstant: Int) {
if (context.isTouchExplorationEnabled()) {
// Don't mess with a blind person's vibrations
return
}
// Either this needs to be set to true, or android:hapticFeedbackEnabled="true" needs to be set in XML
isHapticFeedbackEnabled = true
// Most of the constants are off by default: for example, clicking on a button doesn't cause the phone to vibrate anymore
// if we still want to access this vibration, we'll have to ignore the global settings on that.
performHapticFeedback(feedbackConstant, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
}
private fun Context.isTouchExplorationEnabled(): Boolean {
// can be null during unit tests
val accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?
return accessibilityManager?.isTouchExplorationEnabled ?: false
}
For now we still have to use this code and access it from Compose like in Daniele Segato's answer:
#Composable
fun VibratingButton() {
val view = LocalView.current
Button(onClick = {
view.vibrate()
}) { ... }
}
For me, this worked well when the user presses down the button. Using Compose 1.2.0-beta01
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val hapticFeedback = LocalHapticFeedback.current
LaunchedEffect(key1 = isPressed) {
if (isPressed) {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
Add the feedback logic inside the Modifier.clickable {...} applied to the Row

LazyColumn does not scroll if using TextFields as child

#Composable
fun init() {
LazyColumn(Modifier.fillMaxSize()) {
for (i in 0..10) {
item { Box(Modifier.padding(15.dp)) { TextField("Hallo$i", modifier = Modifier.fillMaxWidth(), onValueChange = {}) } }
}
}
}
If i have something simple as this list with textfields
then the textfields will not let me scroll down the column.
Only works if i scroll down next to the textfields.
Tried also with readonly/disabled textfield.
is there a way to overcome this behaviour?
maybe a way to disable focus on textfield if scrolled?
I am using jetbrains-compose for desktop version (0.5.0-build245)
but can also be the same as in the jetpack-compose for android (did not try)
for the moment because i don't find any other solution i will use this workaround
using a invisible box above the text field and change the state accordingly
#Composable
fun init() {
LazyColumn(Modifier.fillMaxSize()) {
for (i in 0..10) {
item {
val isfocused = remember { mutableStateOf(false) }
val focusRequester = FocusRequester()
Box(Modifier.padding(15.dp)) {
TextField("Hallo$i", modifier = Modifier.fillMaxWidth().focusRequester(focusRequester).onFocusChanged {
isfocused.value = it.isFocused
}, onValueChange = {})
if (!isfocused.value)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(onClick = {
isfocused.value = true
focusRequester.requestFocus()
}),
)
}
}
}
}
}

Resources