Detect text overflow in Jetpack Compose

Sinan Kozak
Published in
3 min readJan 7, 2022

--

Texts are complex UI elements. When they don't fit the screen, it is possible to show overflow dots but then part of the information is lost. The user needs to be informed about data that don't fit the screen. We can show ‘Show more’ button, dialog or toast. There is no best practice to show hidden text.

In the case of our real estate app, we have an UI element that shows selected districts of the current search. When there are more elements that the view can display, it needs to show overflow dots and a count badge for how many other items are not visible to the user.

The previous View-based UI element is not smart about badge count. We didn't invest time to create a custom view or a dynamic logic for calculating count.

The view simply showed the first two districts and hide the rest of the districts statically. The badge showed the total number of districts minus two. That implementation was far from ideal but it worked for most of the device sizes, except tablets, small phones, landscape and more…

The initial solution was not adaptive. Implementing a custom view was always possible but testing and verifying the layout without running the application wasn't easily possible.

Now it is time for us to rewrite the same component in Compose and Compose lets us create reusable custom UI elements much easier than the legacy view system.

As a reusable view component, I expect this UI element could calculate badge count depending on where overflow starts. Badge logic is not business logic but rather a view layer logic. If the screen has more width, the view should handle itself and update the badge without any external help.

Text Composable has onTextLayout callback that is called with the result of text rendering details.

Since compose is way more flexible and customizable, instead of having to write a custom view group we can slightly modify the Text composable and hook it up into the onTextLayout callback.

TextLayoutResult hasgetLineEnd method that returns the end index of rendered text.

textLayoutResult.getLineEnd(lineIndex = 0, visibleEnd = true)

Calling method with visibleEnd = true returns the end index of rendered text. Default is false and by default, this method returns the last index of the passed text value, not the visible end result.

var badgeCount by remember { mutableStateOf(0) }Text(
text = locations,
modifier = Modifier.fillMaxWidth(),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.hasVisualOverflow) {
val lineEndIndex = textLayoutResult.getLineEnd(
lineIndex = 0,
visibleEnd = true
)
badgeCount = locations
.substring(lineEndIndex)
.count { it == ',' }
}
},
)
if (badgeCount != 0) {
CounterBadge(badgeCount)
}

In onTextLayout callback, we can do the desired calculation and update the badgeCount state. badgeCount can be used to render the Badge dynamically.

This code block is reusable and will use all available space on the screen. If there is not enough space, it will show text-overflow and an information badge about how many other selections are hidden because of overflow.

Full code for badge logic is available at https://gist.github.com/kozaxinan/77dfbc9a0e1adf729e16a984d5af0da5

Special thanks to Anna Morgiel, Konstantin Otte, Fabian Shallari, Szymon Kamycki, Oleksi Ustenko and Andrii Dmytrenko for their feedbacks.

--

--

Staff Android Engineer at Delivery Hero in Infra team. Making sure food delivery applications won't crash and run butter smooth.