skip to Main Content

You Shall Not Pass or Dispose

January 27, 20264 minute read

  

Displaying a list of items has always been a part of user interfaces. We can see it in almost every app, but it comes with some challenges to the user experience, like waiting for new items to load after scrolling or seeing a janky, unresponsive screen, which can lead to a poor experience for users. Handling these efficiently is essential for providing a great user experience.

Generated by ChatGPT

TL;DR
Nothing fancy, just one line.
val pinnableContainer = LocalPinnableContainer.current

The Problem

With the rise of generative AI, many chat apps now display assistant responses with character by character or word by word animations. This is great for showing the answer generation in real time (even some apps handle generation like animation on the client side). But, as mentioned earlier, displaying these animations in a list comes with UX challenges. Messages are usually shown in lazily loaded lists, which dispose of items when they scroll out from viewport. For example, if we mark a message as read when its animation finishes, scrolling it out of view and back will restart the animation, because the framework has disposed of the item.

Solutions

  1. So, we need items to stay active when user move out of the viewport. You can use Column with verticalScroll. In this setup, all items remain in memory and continue working even when they are not visible. The trade off is performance: for very large lists this can become inefficient. In most cases, LazyColumn is the better choice, since it only composes the items currently visible on screen.

https://medium.com/media/ef40a97349de563205da0c174289f03d/href

Displaying items with a simple for loop comes with another issue: item indexing. Imagine you have 100 items and you update or delete the one at position 45. In this case, all items from 45 to 99 will be recomposed, even though only a single item actually changed. The reason is that compose compiler uses execution order to decide what needs to be redrawn. To avoid these unnecessary recompositions, we can use the key composable. By assigning a stable identifier to each item, it helps the compose compiler to see them as distinct, so only the affected item gets recomposed.

https://medium.com/media/f33169f88e1580d54e8ac604852b9477/href

2. Using LazyColumn is usually the best practice since it only composes the items in the viewport and provides a keyparameter by default. But, it still disposes items once they move out of view, which can be a problem in some cases. To solve this, compose introduced PinnableContainer in version 1.4.0, allowing you to keep specific items pinned in memory instead of them being disposed. Also we should release the pin when the content is not important anymore.

https://medium.com/media/3008bbb3fbaae3d7b0eef191bbdd2e89/href

Typewriter animation was not disposed outside viewport by using PinnableContainer

Result

Using PinnableContainer requires just one line: pinnableContainer?.pin(). This keeps the item active even when it scrolls out of view, so animations and effects continue uninterrupted.

I also wanted to highlight the problem from other views and share my thoughts on alternative solutions. In the end, it’s just a one line solution. :D

References


You Shall Not Pass or Dispose was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.

 

Web Developer, Web Design, Web Builder, Project Manager, Business Analyst, .Net Developer

No Comments

This Post Has 0 Comments

Leave a Reply

Back To Top