I want to build a dropdown menu with items that contain not only text, but also an image. The image should be loaded from an url. I'm using Dropdown Menu and Accompanist to load the image.
But when I try to open the Dropdown Menu, I get java.lang.IllegalStateException: Intrinsic measurements are not currently supported by SubcomposeLayout.
I've tried to play around with Intrinsics in my Composables like here https://developer.android.com/codelabs/jetpack-compose-layouts#10, but it didn't work. If I don't use CoilImage, but get a painter from resources with Image, everything works fine.
Is there a way to solve it?
#Composable
fun DropdownChildren(
items: List<ChildUiModel>,
chosenChild: ChildUiModel?,
onChildChosen: (ChildUiModel) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(0) }
Box(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.TopStart)) {
Row(modifier = Modifier
.fillMaxSize()
.clickable(onClick = { expanded = true })) {
ChildrenDropdownMenuItem(
imageUrl = "https://oneyearwithjesus.files.wordpress.com/2014/09/shutterstock_20317516.jpg",
text = items[selectedIndex].name?: "No name",
chosen = false)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.fillMaxWidth()
.requiredSizeIn(maxHeight = 500.dp)
) {
items.forEachIndexed { index, child ->
DropdownMenuItem(
modifier = Modifier
.background(color = if (child.id == chosenChild?.id) appColors.secondary else appColors.primary),
onClick = {
expanded = false
selectedIndex = index
onChildChosen(items[selectedIndex])
}) {
ChildrenDropdownMenuItem(
imageUrl = "https://oneyearwithjesus.files.wordpress.com/2014/09/shutterstock_20317516.jpg",
text = child.name,
chosen = child.id == chosenChild?.id)
}
}
}
}
}
#Composable
fun ChildrenDropdownMenuItem(
imageUrl: String,
text: String,
chosen: Boolean
){
Row(){
Avatar(url = imageUrl)
Text(text = text,
style = AppTheme.typography.h4,
color = if (chosen) appColors.primary else appColors.secondary,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically))
}
}
#Composable
fun Avatar(
url: String
){
val contentPadding = PaddingValues(8.dp, 8.dp, 12.dp, 8.dp)
CoilImage(
data = url,
) { imageState ->
when (imageState) {
is ImageLoadState.Success -> {
MaterialLoadingImage(
result = imageState,
contentDescription = "avatar",
modifier = Modifier
.padding(contentPadding)
.clip(CircleShape)
.size(48.dp),
contentScale = ContentScale.Crop,
fadeInEnabled = true,
fadeInDurationMs = 600,
)
}
is ImageLoadState.Error -> CoilImage(
data = "https://www.padtinc.com/blog/wp-content/uploads/2020/09/plc-errors.jpg",
contentDescription = "error",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(contentPadding)
.clip(CircleShape)
.size(48.dp)
)
ImageLoadState.Loading -> CircularProgressIndicator()
ImageLoadState.Empty -> {}
}
}
}
CoilImage is now Deprecated,
use this
Image(
painter = rememberCoilPainter("https://picsum.photos/300/300"),
contentDescription = stringResource(R.string.image_content_desc),
previewPlaceholder = R.drawable.placeholder,
)
As you mentioned on the bug, this has already been fixed in v0.8.0 of Accompanist. You need to use the new rememberCoilPainter() though, and not the deprecated CoilImage().
Related
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 + ""
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.
I am creating Heterogenous list using jetpack compose. I have an issue with recomposition when one of the item is clicked.
My Idea is to update the list with new item when an item clicked, I am receiving update list but list is not recomposed.
Please find below code:
//Initial function that prepared screen where content of list is wrapped in Scafold
#Composable fun PrepareOverViewScreen(overViewList: List<OverViewListItem>) {
val infoList = remember {mutableStateOf(overViewList) }
Scaffold(topBar = { TopBar("OverView") },
content = { DisplayOverViewScreen(overViewList = overViewList) },
backgroundColor = Color(0xFFf2f2f2)
)
}
//Displays list using LazyColumn when in itemType OverViewHeaderItem I am trying to recompose list with latest data , but not succesful.
#Composable fun DisplayOverViewScreen(
modifier: Modifier = Modifier, overViewList: List<OverViewListItem>
) {
val infoList = remember {mutableStateOf(overViewList) }
LazyColumn(modifier = modifier) {
items(infoList.value) { data ->
when (data) {
is OverViewHeaderItem -> {
HeaderItem(data,infoList.value,onClick = { newValue ->
println("New Value is $newValue")
infoList.value = newValue.toMutableList()
})
}
is OverViewChildItem -> {
ChildListBasedOnListType(data)
}
}
}
}
}
//In this function the onClick is executed when clicked on Image()
#Composable fun HeaderItem(overViewHeaderItem: OverViewHeaderItem,overViewList: List<OverViewListItem>,onClick: (List<OverViewListItem>) -> Unit) {
var angle by remember {
mutableStateOf(0f)
}
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
overViewList.map {
when (it) {
is OverViewChildItem -> {
if (it.listType == ItemType.HISTORY_YESTERDAY_CHILD) {
it.canShowHistoryChild = true
}
}
else -> {}
}
}
onClick(overViewList)
},
contentDescription = "Expandable Image"
)
}
}
Please help me resolve this issue.
#Composable fun ChildListBasedOnListType(overViewChildItem: OverViewChildItem){
when(overViewChildItem.listType){
ItemType.HISTORY_YESTERDAY_CHILD -> {
if(overViewChildItem.canShowHistoryChild){
ChildListItem(overViewChildItem)
Divider(color = Color(0xffd7d7d7), thickness = 1.dp)
}
}
else -> {
ChildListItem(overViewChildItem)
Divider(color = Color(0xffd7d7d7), thickness = 1.dp)
}
}
}
#Composable fun ChildListItem(overViewChildItem: OverViewChildItem) {
ConstraintLayout(
constraintSet = getConstraints(),
modifier = Modifier
.fillMaxSize()
.height(60.dp)
.background(color = Color(0xffffffff))
) {
Text(
text = overViewChildItem.article.articleName, Modifier
.layoutId("articleLabel")
.padding(start = 16.dp)
.fillMaxWidth()
.wrapContentHeight()
)
Text(
text = overViewChildItem.article.description, Modifier
.layoutId("desc")
.padding(start = 16.dp)
.fillMaxWidth()
.wrapContentHeight()
)
Image(
painter = painterResource(id = R.drawable.ic_baseline_chevron_right_24),
modifier = Modifier.layoutId("detail"), contentDescription = "Detail Image"
)
}
}
#Composable fun getConstraints(): ConstraintSet {
return ConstraintSet {
val articleLabel = createRefFor("articleLabel")
val articleDesc = createRefFor("desc")
val detailImage = createRefFor("detail")
createVerticalChain(articleLabel, articleDesc, chainStyle = ChainStyle.Packed)
constrain(articleLabel) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
constrain(articleDesc) {
top.linkTo(articleLabel.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
constrain(detailImage){
top.linkTo(parent.top)
end.linkTo(parent.end,5.dp)
bottom.linkTo(parent.bottom)
}
}
}
I'm praticing Compose navigation. I made an App that displays a ListItem with images, on what I call HomeScreen, when a particular item is clicked it navigates to a destination containing more information/ Details, on what I called HomeInfoScreen. And to do this I used Navigation compose Arugments. I basically did this by benchmarking the Android Developers Rally Compose.
But the problem is whenever I click on any of the rows it takes me to the details of the first Item, no matter which of them I click.
I believe the problem is from coming from HomeScreen (remember, I said this was the destination I'm navigating from)
I've previously researched, and got an Idea of passing my model object as a parameter.
But I think I did something wrong, because I get errors, and I don't know how to fix it.
Please understand that I arranged the code, in multiple composables, in this format;
SecondBodyElement -> SecondBodyGrid ------> HomeContentScreen
And Finally I call HomeContentScreen on the HomeScreen.
SecondBodyElement;
#Composable
fun SecondBodyElement(
#StringRes text: Int,
#DrawableRes drawable: Int,
modifier: Modifier = Modifier,
onHomeCardClick: (Int) -> Unit
) {
Surface(
shape = MaterialTheme.shapes.small,
elevation = 10.dp,
modifier = modifier
.padding(horizontal = 12.dp, vertical = 12.dp)
.clip(shape = RoundedCornerShape(corner = CornerSize(8.dp)))
.clickable { onHomeCardClick(drawable) }
) {
Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.h3,
maxLines = 3,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 16.dp)
)
}
}
}
SecondyBodyGrid;
#Composable
fun SecondBodyGrid(
onHomeCardClick: (Int) -> Unit = {},
) {
LazyVerticalGrid(
columns = GridCells.Fixed(1),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.height(1450.dp)
//.disabledVerticalPointerInputScroll()
) {
items(SecondBodyData) { item ->
SecondBodyElement(
onHomeCardClick = onHomeCardClick,
drawable = item.drawable,
text = item.text,
modifier = Modifier
.height(380.dp)
.clickable { onHomeCardClick(item.drawable + item.text) }
)
}
}
}
HomeContentScreen;
#Composable
fun HomeContentScreen(
modifier: Modifier = Modifier,
onHomeCardClick: (String) -> Unit,
accountType: String? = HomeInfoModel.homeInfoModelList.first().title
) {
val homeInfo = remember(accountType) { HomeInfoModel.getHomeInfo(accountType) }
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)
) {
HomeQuote()
HomeTitleSection(title = R.string.favorite_collections) {
SecondBodyGrid { onHomeCardClick(homeInfo.title) }
}
}
}
And Finally HomeScreen;
#Composable
fun HomeScreen(onHomeCardClick: (String) -> Unit) {
HomeContentScreen(onHomeCardClick = onHomeCardClick)
}
Please like I said I'm practicing, I don't know what you are going to need. But I'm going to add my NavHost (Nav Graph) and the Model file, just incase, If you need any other thing I'm more than happy to provide.
Model, HomeInfoModel;
data class HomeInfoData(
val id: Int,
val title: String,
val sex: String,
val age: Int,
val description: String,
val homeInfoImageId: Int = 0
)
object HomeInfoModel {
val homeInfoModelList = listOf(
HomeInfoData(
id = 1,
title = "There's Hope",
sex = "Male",
age = 14,
description = "Monty enjoys chicken treats and cuddling while watching Seinfeld.",
homeInfoImageId = R.drawable.ab1_inversions
),
....
)
fun getHomeInfo(accountName: String?): HomeInfoData {
return homeInfoModelList.first { it.title == accountName }
}
}
My NavHost (Nav Graph);
....
composable(route = Home.route) {
HomeScreen(
onHomeCardClick = { accountType ->
navController.navigateToHomeInfoScreen(accountType)
}
)
}
composable(
route = HomeInfoDestination.routeWithArgs,
arguments = HomeInfoDestination.arguments,
) { navBackStackEntry ->
// Retrieve the passed argument
val accountType =
navBackStackEntry.arguments?.getString(HomeInfoDestination.accountTypeArg)
// Pass accountType
HomeInfoScreen(accountType)
}
....
}
}
....
private fun NavHostController.navigateToHomeInfoScreen(accountType: String) {
this.navigateSingleTopTo("${HomeInfoDestination.route}/$accountType")
}
I think the problem is from the Homescreen but I don't really know, So I'm going to Add the HomeInfoScreen, Sorry making this question any longer.
HomeInfoScreen;
#Composable
fun HomeInfoScreen(
accountType: String? = HomeInfoModel.homeInfoModelList.first().title
) {
DisplayHomeInfo(accountType)
}
#Composable
fun WelcomeText() {
Text(
text = "Welcome, to Home Information",
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 18.dp)
)
}
#Composable
fun HomeInfoDetails(
accountType: String? = HomeInfoModel.homeInfoModelList.first().title
) {
val homeInfo = remember(accountType) { HomeInfoModel.getHomeInfo(accountType) }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Image(
painter = painterResource(id = homeInfo.homeInfoImageId),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(topEnd = 4.dp, bottomEnd = 4.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.title,
style = MaterialTheme.typography.h3
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.description,
style = MaterialTheme.typography.h5
)
}
}
// Step: Home screen - Scrolling
#Composable
fun DisplayHomeInfo(
accountType: String? = HomeInfoModel.homeInfoModelList.first().title
) {
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)
) {
WelcomeText()
HomeInfoDetails(accountType)
}
}
For Clarity Sake; How can I navigate to the exact item when it is clicked on the SuccessScreen.
I'll sinerely be greatful for any help. Thanks a lot in advance for your help.
In case somebody needs this, I've been able to do this. I followed a website's Developer's Breach tutorial.
I need to implement multiselect for LazyList, which will also change appBar content when list items are long clicked.
For ListView we could do that with just setting choiceMode to CHOICE_MODE_MULTIPLE_MODAL and setting MultiChoiceModeListener.
Is there a way to do this using Compose?
Since you're in control of the whole state in jetpack compose, you can easily create your own multi-select mode. This is an example.
First I created a ViewModel
val dirs: LiveData<DirViewState> = {
DirViewState.Content(dirRepository.targetFolders)}.asFlow().asLiveData()
val all: LiveData<DirViewState> = {
DirViewState.Content(dirRepository.allFolders)
}.asFlow().asLiveData()
val createFolder = mutableStateOf(false)
val refresh = mutableStateOf(false)
val enterSelectMode = mutableStateOf(false)
val selectedAll = mutableStateOf(false)
val selectedList = mutableStateOf(mutableListOf<String>())
fun updateList(path: String){
if(path in selectedList.value){
selectedList.value.remove(path)
}else{
selectedList.value.add(path)
}
}
}
Usage
Card(modifier = Modifier
.width(100.dp)
.height(120.dp)
.padding(8.dp)
.pointerInput(Unit) {
detectTapGestures(
onLongPress = { **viewModel.enterSelectMode.value = true** },
onTap = {
if (viewModel.enterSelectMode.value) {
viewModel.enterSelectMode.value = false
}
}
)
}
,
shape = MaterialTheme.shapes.medium
) {
Image(painter = if (dirModel.dirCover != "") painter
else painterResource(id = R.drawable.thumbnail),
contentDescription = "dirThumbnail",
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
contentScale = ContentScale.FillHeight)
**AnimatedVisibility(
visible = viewModel.enterSelectMode.value,
enter = expandIn(),
exit = shrinkOut()
){
Row(verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.Start) {
Checkbox(checked = isSelected.value, onCheckedChange = {
isSelected.value = !isSelected.value
viewModel.updateList(dirModel.dirPath)
})
}
}**
}
Add selected field to some class representing the item. Then just compose proper code based on that field. In compose you don't have to look for some LazyColumn flag or anything like that. You are in control of the whole state of the list.
The same can be said about the AppBar, you can do a simple if there, like if (items.any { it.selected }) // display button
You can simply handle this by the below code:
Fore example This is your list:
LazyColumn {
items(items = filters) { item ->
FilterItem(item)
}
}
and this is your list item View:
#Composable
fun FilterItem(filter: FilterModel) {
val (selected, onSelected) = remember { mutableStateOf(false) }
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.padding(horizontal = 8.dp)
.border(
width = 1.dp,
colorResource(
if (selected)
R.color.black
else
R.color.white
),
RoundedCornerShape(8.dp)
)) {
Text(
modifier = Modifier
.toggleable(
value =
selected,
onValueChange =
onSelected
)
.padding(8.dp),
text = filter.text)
}
}
and that's it use tooggleable on your click component modifier like here I did on text.