I have this code, in MessageItem
is the Surface and also inside that Surface is the Text.
I tried with .wrapContentSize()
but it didn't work
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatScreen(
chatId: String?,
onBack: () -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = "Chat with Alice"
)
}
)
},
bottomBar = {
SendMessageBox()
}
) { innerPadding ->
ListOfMessages(modifier = Modifier.padding(innerPadding))
}
}
@Composable
fun ListOfMessages(modifier: Modifier = Modifier) {
LazyColumn(
modifier = modifier.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(getFakeMessages()) { message ->
MessageItem(message)
}
}
}
@Composable
fun MessageItem(message: Message) {
Row(
modifier = Modifier
.fillMaxWidth()
.then(if (message.isMine) Modifier.padding(start = 48.dp) else Modifier),
horizontalArrangement = if (message.isMine) Arrangement.End else Arrangement.Start
) {
if (!message.isMine) {
Avatar(
imageUrl = message.senderAvatar,
size = 40.dp,
contentDescription = "${message.senderName}'s avatar"
)
Spacer(modifier = Modifier.width(8.dp))
}
Column {
if (message.isMine) {
Spacer(modifier = Modifier.height(8.dp))
} else {
Text(
text = message.senderName,
fontWeight = FontWeight.Bold
)
}
when (val content = message.messageContent) {
is MessageContent.TextMessage -> {
Surface(
shape = RoundedCornerShape(8.dp),
color = if (message.isMine) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary
) {
Text(
text = content.message,
modifier = Modifier.padding(8.dp),
color = if (message.isMine) MaterialTheme.colorScheme.onPrimary else Color.White
)
}
}
is MessageContent.ImageMessage -> {
AsyncImage(
model = content.imageUrl,
contentDescription = content.contentDescription,
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
}
}
Text(
text = message.timestamp,
fontSize = 12.sp
)
}
}
}
Any idea what I'm doing wrong and why the Surface doesn't fit the size of the text?
This is a bug / not supported feature that is present since the beginnings of Jetpack Compose, as described in Issue #206039942 on the Google Issue Tracker.
There is a suggested workaround that you can use. The Text
Composable has a onTextLayout
callback which returns a TextLayoutResult
. The TextLayoutResult
holds information about the coordinates of each individual line displayed in the Text
Composable.
By then applying a layout
Modifier, you can alter the size of the Text Composable to match the measured width of the longest line inside of the Text
Composable.
You can create a WrappingText
Composable like this:
@Composable
fun WrappingText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
style: TextStyle = LocalTextStyle.current
) {
var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) }
Text(
text = text,
modifier = modifier
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val newTextLayoutResult = textLayoutResult!!
if (newTextLayoutResult.lineCount == 0) {
// Default behavior if there is no text
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
} else {
// get coordinate of line which goes the furthest to the left
val minX = (0 until newTextLayoutResult.lineCount).minOf(newTextLayoutResult::getLineLeft)
// get coordinate of line which goes the furthest to the right
val maxX = (0 until newTextLayoutResult.lineCount).maxOf(newTextLayoutResult::getLineRight)
// set width to match longest line
layout(ceil(maxX - minX).toInt(), placeable.height) {
placeable.placeRelative(-floor(minX).toInt(), 0)
}
}
},
onTextLayout = {
textLayoutResult = it
},
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
style = style
)
}
Then, use it in your MessageItem
Composable like this:
Surface(
modifier = Modifier.wrapContentSize(),
shape = RoundedCornerShape(8.dp),
color = MaterialTheme.colorScheme.primary
) {
WrappingText(
modifier = Modifier
.padding(8.dp)
.wrapContentSize(),
text = "Are you going to that Kotlin conference to Colorado next week, my dear friend?",
color = MaterialTheme.colorScheme.onPrimary,
textAlign = TextAlign.End
)
}
Output: