Kotlin jetpack compose deleting and adding elements to mutableStateListOf - android

I have wrote hardcoded list of ToDo elements and made 2 composable functions to display data.
#Composable
fun TodoAppContent() {
val todos = remember { (DataProvider.listOfToDoEntries) }
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xff503d69))
) {
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
modifier = Modifier
.weight(1f)
) {
items(items = todos, itemContent = { CreateToDoListItem(todo = it) })
}
Button(
onClick = {},
modifier = Modifier
.size(60.dp)
.align(alignment = Alignment.CenterHorizontally),
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xffc1b6d1)),
shape = CircleShape
) {
Text(text = "+")
}
}
}
#Composable
fun CreateToDoListItem(todo: ToDo) {
Card(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 8.dp)
.fillMaxWidth(),
elevation = 2.dp,
backgroundColor = Color(0xff694598),
shape = RoundedCornerShape(
topStart = 30.dp,
topEnd = 30.dp,
bottomStart = 0.dp,
bottomEnd = 30.dp
)
) {
Row {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.align(Alignment.CenterVertically)
) {
Text(text = todo.title, style = typography.h6)
Text(text = todo.description, style = typography.caption)
OutlinedButton(
onClick = {},
modifier = Modifier
.size(40.dp)
.align(alignment = Alignment.End),
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xffc1b6d1)),
shape = CircleShape
) {
Text(text = "X")
}
}
}
}
}
It worked fine but when I wanted implement operations such as adding new entry to list and deleting it by changing
val todos = remember { mutableStateListOf(DataProvider.listOfToDoEntries) }
I'm no longer recieving single ToDo object but whole list. Any ideas how to recievie every single ToDo object of that mutableStateList?

Using mutableStateListOf you can create a mutable state list from some arguments.
So mutableStateListOf(DataProvider.listOfToDoEntries) will create a mutable list of lists, which is probably not what you want.
If you want to initialize a mutable state list with items from another list, you can use toMutableStateList:
val todos = remember { DataProvider.listOfToDoEntries.toMutableStateList() }

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 + ""

Jetpack compose - Lazy Column crashes on Recompose

Hi I am using Jetpack Compose to create a Heterogenous list. I was successful in implementing it. My requirement is when I try to click an Item in the list, I need to recompose the list. The app crashes when tried to refresh the list with below exception:
java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container
My code is below:
#Composable fun PrepareOverViewScreen(overViewList: List<OverViewListItem>) {
Scaffold(topBar = { TopBar("OverView") },
content = { DisplayOverViewScreen(overViewList = overViewList) },
backgroundColor = Color(0xFFf2f2f2)
)
}
#Composable fun DisplayOverViewScreen(
modifier: Modifier = Modifier, overViewList: List<OverViewListItem>
) {
LazyColumn(modifier = modifier) {
items(overViewList) { data ->
when (data) {
is OverViewHeaderItem -> {
HeaderItem(data,overViewList)
}
}
}
}
}
HeaderItem Composable Function is below :
#Composable fun HeaderItem(overViewHeaderItem: OverViewHeaderItem,overViewList: List<OverViewListItem>) { <br>
var angle by remember {
mutableStateOf(0f)
}
var canDisplayChild by remember {
mutableStateOf(false)
}
**if(canDisplayChild){
HandleHistoryTodayChild(canDisplayChild = true,overViewList)
}**
when (overViewHeaderItem.listType) {
ItemType.IN_PROGRESS_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffdc8633)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = "In Progress", color = Color.White,
modifier = Modifier.padding(start = 16.dp)
)
}
}
ItemType.HISTORY_TODAY_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = stringResource(R.string.history_label), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
}
}
else -> {
Row(modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.history_yesterday), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(Modifier.weight(1f))
**Image(
painter = painterResource(id = R.drawable.ic_expand_more),
modifier = Modifier
.padding(end = 5.dp)
.rotate(angle)
.clickable {
angle = (angle + 180) % 360f
canDisplayChild = !canDisplayChild
},
contentDescription = "Expandable Image"
)**
}
}
}
}
Handle History info where recomposition is called
#Composable
fun HandleHistoryTodayChild(canDisplayChild:Boolean,overViewList: List<OverViewListItem>) {
if(canDisplayChild){
**PrepareOverViewScreen(overViewList = overViewList)**
}
}
Your problem should be:
Spacer(Modifier.weight(1f))
Try to specify a fixed height and work your solution from there.

Manage visibility with Compose UI

i´m new in Compose and i´m trying to manage the visibility of the TopBar when i´m scrolling a list ( LazyColumn ). I´m not pretend to use the Scaffold with Material 3 because I want to learn a bit more about Compose and Animation.
So, first of all, this is my code and it works just fine ->
#Composable
fun FavouriteCompose(stateUi: FavouriteStateUi.ShowMovies) {
Box(Modifier.fillMaxSize()) {
val state = rememberLazyListState()
val firstVisible = remember { derivedStateOf { state.firstVisibleItemIndex } }
Column(
Modifier
.fillMaxSize()
.background(color = GreenB)
) {
AnimatedVisibility(visible = firstVisible.value == 0) {
Spacer(modifier = Modifier.height(20.dp))
Paragraphs.Paragraph(
modifier = Modifier.padding(10.dp),
stringRes = R.string.favourite,
color = Color.White,
paragraphSize = Paragraphs.ParagraphSize.PARAGRAPH_24_SP
)
Spacer(modifier = Modifier.height(20.dp))
}
if (stateUi.movies?.isEmpty() == true) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = 10.dp)
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.White),
) {
Image(
alignment = Alignment.Center,
modifier = Modifier.size(200.dp),
painter = painterResource(
id = R.drawable.ic_baseline_local_movies_24
),
contentDescription = ""
)
Paragraphs.Paragraph(
modifier = Modifier
.padding(top = 10.dp)
.align(Alignment.CenterHorizontally),
stringRes = R.string.add_more_movies,
paragraphSize = Paragraphs.ParagraphSize.PARAGRAPH_24_SP
)
}
} else {
LazyColumn(
state = state,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.White)
.padding(10.dp)
) {
items(
items = stateUi.movies.orEmpty(),
key = { cardModel -> cardModel.id }
) { item -> MovieCard(item) }
}
}
}
val snackBarModel = stateUi.snackBarModel
if (snackBarModel.addOrRemoveMovie == true) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(bottom = 10.dp)
) {
SnackBar(
stringRes = R.string.pelicula_removida,
icon = R.drawable.ic_baseline_local_movies_24,
backgroundColor = Color.Black,
textColor = LightGrey,
iconColor = Blue,
snackBarModel = snackBarModel
)
}
}
}
}
Each Time i swipe the list the topApp hides and viceversa, but i´m not sure if this is the best way to do this ( without using Scaffold with Material 3 ).
I´m creating more recompositons with this kind of solution ?, should I save the lazy state in a viewModel instead?
Thanks!.

How to make Row Scrollable with for loop | Jetpack Compose

I am making a project with Jetpack Compose. I want to display comments like there are in Instagram. There is an array that contains comments.
This is a code used to display comments:
val i : Int
for(i in 1..user.count) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image1(
painter = painterResource(id = user.pp),
contentDescription = "PP",
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
)
Spacer(modifier = Modifier.width(10.dp))
Column() {
Row() {
Text(text = user.name, color = Color.Black, fontSize = 20.sp)
Spacer(modifier = Modifier.width(10.dp))
}
Spacer(modifier = Modifier.width(10.dp))
Text(text = "Public", color = Color.DarkGray, fontSize = 13.sp)
}
}
IconButton(onClick = { /*TODO*/ }) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_more_vert_24),
contentDescription = "More"
)
}
}
Row() {
Spacer(modifier = Modifier.width(10.dp))
Text(
text = user.c[i-1],
color = Color.Black,
fontSize = 16.sp,
modifier = Modifier.padding(end = 10.dp)
)
}
Spacer(modifier = Modifier.height(10.dp))
Row() {
var isClicked by remember {
mutableStateOf(false)
}
Spacer(modifier = Modifier.width(10.dp))
Icon(
painter = painterResource(
id =
if (!isClicked) R.drawable.like_in_comments else R.drawable.like
),
contentDescription = "Like",
tint = Color.Blue,
modifier = Modifier
.size(25.dp)
.clickable { isClicked = !isClicked }
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "Like",
color = Color.DarkGray,
fontSize = 16.sp,
)
}
Spacer(modifier = Modifier.height(10.dp))
Divider()
}
I want to make it scrollable. I can use LazyRow. When I use it, I get some errors. How do I implement it? Please help.
You can make an ordinary Row scrollable by supplying its Modifier.horizontalScroll() with a scroll state like the following
val scrollState = rememberScrollState()
Row (modifier = Modifier.horizontalScroll(scrollState)) {
...
}
But for a simple LazyRow without having a unique key, you don't need to iterate each item via a loop construct (e.g a for-loop), you just have to simply call items and pass the list.
val itemList = listOf("Item1", "Item2")
LazyRow {
items(itemList) { item ->
// your composable here
}
}
For a LazyRow with a unique key (assuming you have a class with an "id" attribute)
val people = listOf(Person(1, "person1"), Person(2, "person2"))
LazyRow {
items(items = people, key = { item -> person.id }) { person->
// your composable here
}
}

Column weight distribution in Compose

I have a Column component (SettingsGraphicSelectComposeView.kt) which needs to have a weight of 1f (modifier.weight(weight = 1f)) so that multiple of this component in a container, would be distributed evenly. Problem is that when upgrading Compose from alpha02 to alpha06, assigning the above modifier to a Column is no longer a possibility.
Here is the components code:
#Composable
fun SettingsGraphicSelectComposeView(
modifier: Modifier = Modifier,
textStyles: TextStyles = KoinJavaComponent.inject(TextStyles::class.java).value,
viewModel: ItemViewModel.GraphicSelect
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
modifier = modifier.weight(weight = 1f) // <-- in alpha06 this gives an error
) {
Image(
asset = vectorResource(id = viewModel.imageId)
)
Text(
modifier = modifier
.padding(
start = dimensionResource(id = R.dimen.spacingL),
top = dimensionResource(id = R.dimen.spacingL),
end = dimensionResource(id = R.dimen.spacingL),
bottom = dimensionResource(id = R.dimen.spacingS)
),
text = viewModel.caption,
style = textStyles.TitleSmall.merge(TextStyle(color = colorResource(id = R.color.tint_secondary)))
)
RadioButton(
selected = viewModel.selected,
onClick = viewModel.action,
colors = RadioButtonConstants.defaultColors(
selectedColor = colorResource(R.color.brand),
unselectedColor = colorResource(R.color.tint_secondary)
)
)
}
}
This component is placed in the following compose view:
Surface(color = colorResource(id = R.color.background)) {
Column(
modifier = Modifier
.fillMaxHeight()
) {
items.value?.let { items ->
val switchers = items.filter { it.viewModel is ItemViewModel.Switcher }
val graphicSelects = items.filter { it.viewModel is ItemViewModel.GraphicSelect }
if (switchers.isNotEmpty()) {
val autoSwitcher = switchers[0]
SettingsSwitcherComposeView(viewModel = autoSwitcher.viewModel as ItemViewModel.Switcher)
}
Row(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
) {
if (graphicSelects.size > 1) {
val lightSelector = graphicSelects[0]
val darkSelector = graphicSelects[1]
Row(
modifier = Modifier
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.padding(vertical = dimensionResource(id = R.dimen.spacing2XL))
) {
Row {
SettingsGraphicSelectComposeView(
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
) // <-- This should have the weight 1f
}
}
}
}
}
}
}
Should look like this:
But without the weight it looks like this:
It seems like the weight modifier can only attributed to Column if this column is a child of a Row, so a team member suggested replacing the Row elements containing the SettingsGraphicSelectComposeView with Column and it made the weight attribute "legal" if you will, because we're in a RowScope, and that works.
Not sure what to think about that but it works and as Compose is still at alpha07 right now (new version since I made the post), things might change again in the future.
Here is the change:
Row(
modifier = modifier
.padding(horizontal = dimensionResource(id = R.dimen.spacing2XL))
.border(
width = 1.dp,
color = colorResource(R.color.highlight),
shape = RoundedCornerShape(
dimensionResource(R.dimen.content_corner_radius)
)
)
.fillMaxWidth()
) {
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.light.viewModel as ItemViewModel.GraphicSelect
)
}
Spacer(
modifier = Modifier
.padding(top = dimensionResource(id = R.dimen.spacing2XL))
.preferredWidth(1.dp)
.preferredHeight(160.dp) // TODO: Find a way to make this max AUTO height, not fixed
.background(color = colorResource(R.color.highlight))
)
Column(modifier = Modifier.weight(weight = 1f).padding(vertical = dimensionResource(id = R.dimen.spacing2XL))) {
SettingsGraphicSelectComposeView(
modifier = Modifier.align(Alignment.CenterHorizontally),
viewModel = viewModel.dark.viewModel as ItemViewModel.GraphicSelect
)
}
}
Try using Modifier.weight(1f) in your Row itself, so it takes Modifier.weight() from RowScope:
Row {
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = lightSelector.viewModel as ItemViewModel.GraphicSelect
)
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.preferredHeight(160.dp)
.background(color = colorResource(R.color.highlight))
)
SettingsGraphicSelectComposeView(
modifier = Modifier.weight(1f),
viewModel = darkSelector.viewModel as ItemViewModel.GraphicSelect
)
}
Here is the solution : Modifier --> "Arrangement.SpaceEvenly"
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
)
If you wants to give bigger space to any specific Row item, you can handle it dynamically by using your own condition :
For eg :
modifier=Modifier.then(Modifier.weight(if(myItem == index) 2f else 1f))
**'OR'**
modifier=Modifier.then(Modifier.weight(if(myItem .title=="GOLF") 1.5f else 1f))

Categories

Resources