PinnableContainer and LazyColumn
How to use PinnableContainer in a LazyColumn
In the What’s new in the Jetpack Compose March ’23 release article on the Android Developers blog there is a note about how PinnableContainer can help when using TextFields inside a scrollable Column or LazyColumn.
Here is the description of PinnableContainer from the Compose documentation:
Represents a container which can be pinned when the content of this container is important.
For example, each item of lazy list represents one PinnableContainer, and if this container is pinned, this item will not be disposed when scrolled out of the viewport.
Pinning a currently focused item so the focus is not lost is one of the examples when this functionality can be useful.
But how do you actually use PinnableContainer inside a LazyColumn? Take a look at the following code sample. What it does is to pin every 10th item within the LazyColumn. It does so by using LocalPinnableContainer.current and then calling the pin() function.
@Composable
fun MyLazyList() {
LazyColumn {
items(100) { index ->
val isPinned = index % 10 == 0
if (isPinned) {
LocalPinnableContainer.current?.pin()
}
val df = SimpleDateFormat("hh:mm:ss", Locale.GERMAN)
val time = df.format(Date())
Text(
text = "Item: $index - $time",
fontWeight = if (isPinned) FontWeight.Bold else FontWeight.Normal
)
}
}
}
When you run this code, for example inside a Compose interactive preview, you will see that the timestamp of every 10th row will stay the same while you scroll up and down the list. The timestamp of all the other rows will update once the move out and back into the viewport because they are recomposed. As you can see, LocalPinnableContainer.current is nullable, but when accessed from inside a LazyColumn item it will return a value.
About LocalPinnableContainer
LocalPinnableContainer is a Compose container that can be used to pin a value in a certain scope, so that it can be accessed by other composables within that scope. Here's how you can use LocalPinnableContainer in Compose:
Import the LocalPinnableContainer class:
import androidx.compose.runtime.*
Define a key that will be used to identify the value you want to pin. For example, you might define a key for a String value:
private val myStringKey = LocalPinnableKey<String>()
Use the LocalPinnableContainer function to define the scope where you want to pin the value. This function takes two arguments: the key for the value, and a lambda that defines the content of the scope. Within the lambda, you can use the pinned value by calling the pinned function:
LocalPinnableContainer(myStringKey) {
val myString = remember { "Hello, world!" }
myString.pin()
// other composables that need to access myString can call myString.pinned()
}
In this example, the value "Hello, world!" is pinned to the myStringKey within the scope defined by LocalPinnableContainer. Other composables within this scope can access this value by calling myString.pinned().
Note that LocalPinnableContainer should be used with caution, as it can lead to tight coupling between composables and make it harder to reason about the flow of data in your app.