IconButton change position when click in jetpack compose - android

I am using IconButton in my project. I am opening Dialog when I am click on IconButton. Whenever I clicked on IconButton the dialog opens and IconButton little bit shifts other side and back to position when Dialog close. You can see in this video. So what is the problem of this? I want to avoid this..
OptionItemStateFul
#Composable
fun OptionItemStateFul() {
val isOptionItemClickAction = remember { mutableStateOf(false) }
OptionItemStateLess(isOptionItemClickAction) {
DialogOptionsView(
openDialogCustom = isOptionItemClickAction,
dialogOptionsData = DialogOptionsData(headerText = "Hi There", itemsList = listOf(1, 2)),
)
}
}
OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction, onOptionItemClickAction)
}
}
OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
painter = painterResource(R.drawable.ic_menu),
contentDescription = null,
tint = Aqua,
)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
PreviewOptionItemStateFul
#Preview(showBackground = true)
#Composable
fun PreviewOptionItemStateFul() {
OptionItemStateFul()
}
Thanks

This change is coming because the dialog is in the row, so the dialog should be kept outside the row. Like this
updated - OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
updated - OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
Icons.Default.Menu,
contentDescription = null,
)
}
}

Related

Nested Column doesn't Recompose

I have nested column, when I click add button the goal is add another text field and when I click delete button (which still hidden because first index) the goal is remove its text field. It seems doesn't recompose but the list size is changed.
I have tried using LazyColumn and foreach inside leads to force close, still no luck.
Any help appreciated, thank you!
My current code :
#Composable
fun ProblemScreen() {
val list = remember {
mutableStateListOf<MutableList<String>>()
}
LaunchedEffect(key1 = Unit, block = {
repeat(3) {
val listDesc = mutableListOf<String>()
repeat(1) {
listDesc.add("")
}
list.add(listDesc)
}
})
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
list.forEachIndexed { indexParent, parent ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Parent ${indexParent + 1}", fontSize = 18.sp)
Spacer(modifier = Modifier.weight(1f))
Button(onClick = {
parent.add("")
println("PARENT SIZE : ${parent.size}")
}) {
Icon(imageVector = Icons.Rounded.Add, contentDescription = "Add")
}
}
parent.forEachIndexed { indexChild, child ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = "",
onValueChange = {
},
colors = TextFieldDefaults.textFieldColors(),
maxLines = 1,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
Button(
onClick = {
parent.removeAt(indexChild)
},
modifier = Modifier.alpha(if (indexChild != 0) 1f else 0f)
) {
Icon(
imageVector = Icons.Rounded.Delete,
contentDescription = "Delete"
)
}
}
}
}
}
}
}
As said in docs, mutable objects that are not observable, such as mutableListOf(), are not observable by Compose and don't trigger a recomposition.
So instead of
val list = remember {
mutableStateListOf<MutableList<String>>()
}
Use:
val list = remember {
mutableStateListOf<List<String>>()
}
And when you need to update the List, create a new one:
//parent.add("")
list[indexParent] = parent + ""

Align row item in jetpack compose

I want to make row like this
Expected Output
AND
I tried this piece of code
DividerWithItem
#Composable
fun DividerWithItem(
modifier: Modifier = Modifier,
index: () -> Int,
itemName: String,
lastIndex: () -> Int,
moreRowContent: #Composable RowScope.() -> Unit,
) {
Column {
if (index() == 0) {
Divider(color = Cloudy, thickness = dimensionResource(R.dimen.separator_height_width))
}
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = itemName,
modifier = Modifier.padding(vertical = 12.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
moreRowContent()
}
if (index() <= lastIndex()) {
Divider(color = Cloudy, thickness = 1.dp)
}
}
}
BpmOptionsLabel
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun LazyItemScope.BpmOptionsLabel(
index: () -> Int,
optionName: String,
lastIndex: () -> Int
) {
DividerWithItem(
modifier = Modifier
.fillMaxSize()
.animateItemPlacement(),
index = index,
itemName = optionName,
lastIndex = lastIndex
) {
Image(
modifier = Modifier.weight(.3f),
painter = painterResource(R.drawable.ic_menu),
contentDescription = null,
)
}
}
BpmOptionsLabelPreview
#Preview(showBackground = true)
#Composable
fun BpmOptionsLabelPreview() {
LazyColumn {
item {
BpmOptionsLabel(
index = { 0 },
"Item item item item item m 1",
lastIndex = { 1 }
)
}
}
}
Actual Output
Only problem is Text and Image item is not in proper place
In the DividerWithItem apply the weight(1f) to the Text
Text(
text = itemName,
modifier = Modifier.padding(vertical = 12.dp).weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
moreRowContent()
and in the LazyItemScope.BpmOptionsLabel remove the weight modifier from the Image:
Image(
//modifier = Modifier.weight(.3f),
painter = painterResource(R.drawable.ic_menu_gallery),
contentDescription = null
)
If you want to increase the space occupied by the Image, use a padding modifier:
Image(
//...
modifier = Modifier.padding(horizontal = 20.dp)
)
You can try this, just add weight to your text, and the image will follow the remaining size that it needs depending on its size.
#Preview(showBackground = true)
#Composable
fun TextOverflow(){
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
modifier = Modifier.weight(1f),
text = "This is should be long-long text that ever you see today, I even don't know how far this text will be ended, so just enjoy reading while you code.",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Icon(imageVector = Icons.Rounded.MoreHoriz, contentDescription = "")
}
}
The Result:

Open ModalSheetLayout on TextField focus instead of Keyboard

I am working with Jetpack Compose and I have a page with several TextFields, where I want that, in several of them, when I click on the input, instead of appearing the keyboard, a ModalSheetLayout appears with different layouts that I have. Is this possible? I'm still not able to do this, all I can do is simple things when the focus changes. Can anyone help me?
The below sample should give a basic idea of how to do this.
Code
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun BottomSheetSelectionDemo() {
val coroutineScope: CoroutineScope = rememberCoroutineScope()
val modalBottomSheetState: ModalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
val colors = arrayListOf("Red", "Green", "Blue", "White", "Black")
val (value, setValue) = remember {
mutableStateOf(colors[0])
}
val toggleModalBottomSheetState = {
coroutineScope.launch {
if (!modalBottomSheetState.isAnimationRunning) {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.hide()
} else {
modalBottomSheetState.show()
}
}
}
}
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
LazyColumn {
items(colors) {
Text(
text = it,
modifier = Modifier
.fillMaxWidth()
.clickable {
setValue(it)
toggleModalBottomSheetState()
}
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
)
}
}
},
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize(),
) {
MyReadOnlyTextField(
value = value,
label = "Select a color",
onClick = {
toggleModalBottomSheetState()
},
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 4.dp,
),
)
}
}
}
#Composable
fun MyReadOnlyTextField(
modifier: Modifier = Modifier,
value: String,
label: String,
onClick: () -> Unit,
) {
Box(
modifier = modifier,
) {
androidx.compose.material3.OutlinedTextField(
value = value,
onValueChange = {},
modifier = Modifier
.fillMaxWidth(),
label = {
Text(
text = label,
)
},
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(
onClick = onClick,
),
)
}
}

Update LazyColumn after remove opertation in KMM JetpackCompose app

I was deveoping an Kotlin Multiplatform app, where I develop the UI using Jetpack Compose framework, where I recover some data from network using graphql, to keep it into a local database build with SQLDelight.
My poblem comes when I try to implement a remove operation into my localDatabase, due to the LazyColumn component doesn't update after remove some item.
I saw there are some solution using SnapshotStateList, but I don't see how to implemented, and I try to do it using a mutableState of a boolean variable, which tells me if there some change which need to be update, but the Composable doesn't recompose neither.
this is my main component where I added the list:
#Composable
fun MyCharactersView(viewModel: MainViewModel, bottomBar: #Composable () -> Unit, popBack: () -> Unit, navController: NavController, characterSelect: (character: CharacterModel) -> Unit) {
val isLoading = viewModel.isLoading.collectAsState()
val loadError = viewModel.loadError.collectAsState()
val charactersBought = viewModel.characterBought.collectAsState()
val charactersBoughtSnapShot : SnapshotStateList<List<CharacterModel>> = SnapshotStateList(charactersBought.value)
LaunchedEffect(key1 = Unit){
viewModel.getAllBoughtCharacters().also {
Log.d("info", "Lanza el getAllBoughtCharacters:count: ${charactersBought.value.size}")
}
}
when {
isLoading.value -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier
.fillMaxWidth(0.4f)
.fillMaxHeight(0.4f)
.align(Alignment.Center)
)
}
}
loadError.value != "" -> {
Text(
text = "Se ha producido un error",
color = Color.Red,
textAlign = TextAlign.Center
)
}
else -> {
if(viewModel.updateList.value){
viewModel.getAllBoughtCharacters().also {
Log.d("info", "Llama al viewModel.updateList.value: ${charactersBought.value}")
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(
text = "MyCharacters",
style = TextStyle(
color = Color.White,
fontStyle = FontStyle.Italic,
textAlign = TextAlign.Center
)
)},
navigationIcon = {
IconButton(onClick = { popBack() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
}
}
)
},
bottomBar = bottomBar)
{ paddingValues ->
Surface(
modifier = Modifier
.background(
Color.Transparent
)
) {
if(charactersBought.value.isEmpty()){
Text(
text = "You don't have any character jet.\nTry to buy some one",
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = (LocalConfiguration.current.screenWidthDp/4).dp, end = 0.dp, top = (LocalConfiguration.current.screenHeightDp/4).dp)
)
}else{
LazyColumn(
contentPadding = paddingValues,
modifier = Modifier
.background(Color.Transparent)
) {
Log.d("info", "CharacterResponse in items function: ${charactersBought.value}")
items(charactersBought.value) { character ->
MyCharactersListRowView(
navController = navController,
viewModel = viewModel,
characterModel = character!!,
characterSelected = characterSelect
)
}
}
}
}
}
}
}
}
And this is my ListRow component:
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun MyCharactersListRowView(
navController: NavController,
viewModel: MainViewModel,
characterModel: CharacterModel,
characterSelected: (character : CharacterModel) -> Unit
) {
val openDialog = remember { mutableStateOf(false) }
if(openDialog.value) {
SimpleAlertDialog(
show = true,
onConfirm = { viewModel.deleteCharacter(characterModel.id.toLong().also {
if(viewModel.rowAffected.value.toInt() != 0) {
Toast.makeText(appContext, "${characterModel.name} remove correctly !", Toast.LENGTH_LONG).show().also {
openDialog.value = false
viewModel.updateList.value = true
}
}
}) },
onDismiss = { openDialog.value = false },
textDescription = "Would you like to remove ${characterModel.name} from your characters?",
textTittle = "Remove"
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { characterSelected(characterModel) })
.padding(vertical = 8.dp, horizontal = 16.dp)
.combinedClickable(
onLongClick = {
openDialog.value = true
},
onClick = {
navController.navigate(Screens.CharacterDetailsScreen.route + "/${characterModel.id}")
}
),
verticalAlignment = Alignment.CenterVertically
){
AsyncImage(
model = characterModel.image,
contentDescription = characterModel.name,
contentScale = ContentScale.Fit,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
)
Column(modifier = Modifier.weight(1F)) {
Text(
text = characterModel.name,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold
)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Text(
text = "Seen in ${characterModel.numberEpisodes} episodes, most frequently in ${characterModel.location}",
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Thin
)
}
}
}
Divider()
}
So if you knows some way to force to recompose a composable, (which I think is not possible), or a working solution using the SnapShotStateList, and can share, take thanks in advance !

compile time error: #Composable invocations can only happen from the context of a #Composable function

I have a compile time error as shown in the title
(#Composable invocations can only happen from the context of a #Composable function),
I have one card in this code so I want to show An AlertDialog after clicking the card clickable,
I know that this problem is exist in the platform,
So how can i solve this problem?
#Composable
fun Screen_A_with_WithTopBar(navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Top App Bar")
},
navigationIcon = {
IconButton(onClick = {
navController.navigate(Screen.Home.route)
}) {
Icon(Icons.Filled.Menu, "backIcon")
}
},
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.White,
elevation = 10.dp
)
}, content = {
Screen_A(navController)
}
)
}
#Composable
fun Screen_A(navController: NavController) {
val context = LocalContext.current
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
//.background(ScreenColor)
) {
Text(
text = "Dooro",
fontSize = 42.sp
)
Spacer(modifier = Modifier.height(33.dp))
Row(
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
) {
Card(
modifier = Modifier
.width(150.dp)
.height(80.dp)
.clickable {
Alert()
},
RoundedCornerShape(7.dp),
elevation = 7.dp
//.padding(40.dp)
) {
Text(
text = "None",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
} // END Row
}
}
#Composable
fun Alert() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { openDialog.value = false }
) {
Text("Dismiss")
}
}
}
)
}
}
Here you can use mutableStateOf to be able to show your Alert.
on Screen_A, you should add the following
var showAlert by mutableStateOf(false)
#Composable
private fun ShowAlert() {
if (showAlert)
Alert()
}
Finally in your Card, you will change the value of showAlert field to true.
Card(
modifier = Modifier
.width(150.dp)
.height(80.dp)
.clickable {
showAlert = true
},
RoundedCornerShape(7.dp),
elevation = 7.dp
//.padding(40.dp)
)

Categories

Resources