Jetpack compose manage soft keyboard events - android-jetpack-compose

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?

Related

How to prevent accessibility focus from moving to controls behind ExposedDropdownMenuBox

I am having an issue where the accessibility focus is going to controls behind the PopUp Window when using a ExposedDropdownMenuBox
If there is a single ExposedDropdownMenuBox everything works as expected, but when I add a second ExposedDropdownMenuBox or another control the focus goes to the second ExposedDropdownMenuBox before going to the PopUp Window.
GIF of single dropdown behavior
https://giphy.com/gifs/gapy0XK1CGmbyltJxU
GIF of two dropdowns on the same screen
https://giphy.com/gifs/WkL5TcMWlumfcGHPmD
Source
#Composable
fun Screen() {
Column (
modifier = Modifier
.wrapContentSize(Alignment.TopCenter)
.padding(top = 48.dp)
) {
Text(
text = stringResource(id = R.string.greeting),
fontSize = 30.sp,
modifier = Modifier.padding(bottom = 24.dp)
)
LocaleDropdownMenu()
Spacer(modifier = Modifier.height(8.dp))
// LocaleDropdownMenu()
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun LocaleDropdownMenu() {
val localeOptions = mapOf(
R.string.en to "en",
R.string.fr to "fr",
R.string.hi to "hi",
R.string.ja to "ja"
).mapKeys { stringResource(it.key) }
// boilerplate: https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#ExposedDropdownMenuBox(kotlin.Boolean,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
readOnly = true,
value = stringResource(R.string.language),
onValueChange = { },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
}
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
localeOptions.keys.forEach { selectionLocale ->
DropdownMenuItem(
onClick = {
expanded = false
// set app locale given the user's selected locale
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(
localeOptions[selectionLocale]
)
)
},
content = { Text(selectionLocale) }
)
}
}
}
}
A repository that reproduces this issue is here:
https://github.com/dazza5000/ExposedDropdownMenuBox-accessibility-issue

How to add and delete items in a lazyVerticalGrid Column in jetpack compose

I'm new to jetpack compose and I am just looking for a way to add and delete items from a lazyVerticalGrid using jetpack compose from a pop-up menu. Nothing complicated, just simple code to make me easily understand what I have to do
This is pretty easy as other LazyLists. You need to have a list that you can trigger recomposition after delete, add or update and use unique keys to not recompose your entire list and limit recomposition to a range of items.
val list = remember { mutableStateListOf<Snack>() }
You can remove or add inside you menu by changing this list
Adding, removing items or replacing any items with new one, to update you need to pass a new instance of object, will trigger recomposition
#Composable
private fun GridExample() {
val scrollState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val list = remember { mutableStateListOf<Snack>() }
Column {
LazyVerticalGrid(
contentPadding = PaddingValues(12.dp),
modifier = Modifier
.weight(1f)
.background(backgroundColor),
columns = GridCells.Fixed(3),
content = {
items(items = list,
key = { snack: Snack ->
snack.name
}) { snack: Snack ->
GridSnackCard(snack = snack)
}
}
)
Row(
modifier = Modifier.padding( 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
modifier = Modifier.weight(1f),
onClick = {
if (list.size < snacks.size) {
list.add(snacks[list.size])
}
},
shape = RoundedCornerShape(8.dp)
) {
Text(text = "Add")
}
Spacer(modifier = Modifier.width(10.dp))
Button(
modifier = Modifier.weight(1f),
onClick = {
if (list.size > 0) {
list.removeLast()
}
},
shape = RoundedCornerShape(8.dp)
) {
Text(text = "Remove")
}
}
}
}

How to show keyboard with 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.

Updating LazyColumn In Card Button Click

I am developing a Question/Answer Quiz App in JetPack Compose. I have the Card as below
Card(modifier = Modifier
.wrapContentHeight(Alignment.CenterVertically)
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(10.dp)
.width(300.dp)
.height(600.dp)
.clip(RoundedCornerShape(8.dp)),
elevation = 10.dp,
backgroundColor = Color.White
)
{
Column(
modifier = Modifier
.wrapContentHeight(Alignment.CenterVertically)
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(8.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
){
Text(
text = "Question : " + query.question_id,
style = Typography.h1
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = query.question,
style = Typography.subtitle1
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "Options",
style = Typography.h1
)
Spacer(modifier = Modifier.height(2.dp))
OptionsDetailsList(lstOptions = lstOptions)
Spacer(modifier = Modifier.height(10.dp))
Button(onClick = {
}
) {
Text(text = "View Answer")
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
The Answer List is a LazyColumn as below
#Composable
fun OptionsDetailsList(lstOptions: List<CertAnswers>){
Log.d("ListOptions Count" , lstOptions.size.toString())
LazyColumn(){
item {
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
items(lstOptions){
item ->
Text(text = item.answer,
modifier=Modifier.padding(3.dp),
style = Typography.subtitle1,
)
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
}
}
My Answers DataModel is as Below
data class CertAnswers(
#PrimaryKey (autoGenerate = true)
val id : Int,
var question_id : Int,
val ans_title: String,
val answer : String,
var isSolution: Boolean
)
With isSolution I can get whether an option is right answer.
But I want to show only on the button click and update LazyColumn to show the relevant option in different color.
I tried to call the OptionsDetailsList method again on button click but its not working.
How can i update the LazyColumn on Button Click within CardView.
You need to store a state value in your CardView indicating wether the button was tapped. rememberSaveable will make sure it's saved during recompositions and scrolling. I pass query.question_id as a key: in case it'll change most probably the value should be reinitialized. Check out more about state in compose in documentation
var answerRevealed by rememberSaveable(query.question_id) { mutableStateOf(false) }
You can change the background of the lazy column elements depending on this state. p.s. I suggest you use Modifier as the last argument, so you don't need a comma at the end and you can add/remove/reorder modifiers easily:
#Composable
fun OptionsDetailsList(answerRevealed: Boolean, lstOptions: List<CertAnswers>) {
Log.d("ListOptions Count", lstOptions.size.toString())
LazyColumn() {
item {
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
items(lstOptions) { item ->
Text(
text = item.answer,
style = Typography().subtitle1,
modifier = Modifier
.padding(3.dp)
.background(
if (answerRevealed) {
if (item.isSolution) {
Color.Green
} else {
Color.Red
}
} else {
Color.Transparent
}
)
)
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
}
}
In your button just set this state to true. You can also hide the button once it's tapped, or change the text.
// hide after onClick
if (!answerRevealed) {
Button(onClick = {
answerRevealed = true
}
) {
Text(text = "View Answer")
}
}
// or change text
Button(onClick = {
answerRevealed = true
}
) {
Text(text = if (answerRevealed) "Hide Answer" else "View Answer")
}

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