One Off the Slack

These articles summarize the discussion on a particular thread in the #compose channel of JetBrains’ Slack workspace for Kotlin developers. If you would like to read the original thread, you will need access to that workspace — sign up at https://slack.kotlinlang.org!

When Do We Use rememberSaveable()?

Marko Novakovic asked:

remember {} saves X for recomposition but when composable leaves the screen X is forgotten. if I want to save X even when composable leaves the screen I should use rememberSaveable {} right? it seems weird because concept of rememberSaveable {} is for surviving recreating but composable leaving the screen is not recreation. conceptually not the same. should I use rememberSaveable {} or there is some other way to handle this use case? (edited)

knthmn suggested state hoisting, where you track the state at the highest necessary point in your composable hierarchy. Marko countered:

I understand and I agree. BUT. what if you don’t need/want that state hoisted? let’s say you have list of items you want to select. yes, you probably need to do something with those items inside ViewModel or something and in that case you can make composables(items) in the list stateless (implementation differs from use case to use case) but what if you don’t need these items inside ViewModel? maybe your use case is so simple and you would introduce ViewModel just for saving checked state? this is all maybe and probably not good example but I hope you get a point

Other discussion indicated that Marko’s scenario centered around a LazyColumn(), where state was lost when rows scrolled off the screen.

Google’s Adam Powell pointed out:

all the viewModel composable is doing is hoisting your state into LocalViewModelStoreOwner.current for you

the lazy layouts do indeed have items leave the composition entirely when they’re no longer visible, so yes, if you don’t hoist state somewhere, it won’t stick around.

Google’s Andrey Kulikov indicated that rememberSaveable() is a reasonable choice here:

rememberSaveable can also be used inside LazyColumn items for not loosing the local state when the item is scrolled off the screen(for example for checked state as you mentioned). so activity recreation is just one of the use cases of rememberSaveable

Personally, that makes me nervous, as I worry that we are now saving state well outside its necessary scope. But, I have not rummaged through the implementation of rememberSaveable(), and perhaps it is safer than I am imagining. The fact that Andrey recommended it, and Adam did not counter, suggests that indeed perhaps this is an acceptable pattern.

Posted 2021-04-13, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1617607217197800


What Replaces ColorStateList?

Eric Schrag asked:

I’m trying to figure out how to replicate some of the old drawable state list background color functionality, where the background color changed on press (with no ripple), and really struggling. any resources on how to accomplish this?

i just want to replicate the old state list behavior. if button is pressed, button background color is blue, otherwise it’s teal. no ripple at all.

Colton Idle offered:

Can’t you just switch on state and change the color accordingly?

Like background = if (PRESSED) Color.Blue else Color.Red

However, Eric pointed out that ButtonColors does not offer arbitrary states, just enabled.

Louis suggested forking Button(), which is going to be a typical design system solution:

At this point it is probably easier just to build your own button, since ripple is a core part of Material components.

Zach Klippenstein offered up the eventual solution:

You probably want to implement your own Indication. https://developer.android.com/reference/kotlin/androidx/compose/foundation/Indication

That in turn could be passed through the LocalIndication portion of the CompositionLocal system, so Button() or other interactive elements in some branch of the composition tree can take that Indication into account. Or, you can apply it to an individual Button() if you prefer, as Eric did:

I figured out that my issue was simply that I wasn’t connecting my interactionSource to the button with interactionSource = interactionSource, in the Composable call

Posted 2021-04-04, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1617300108053600


When Do We Clip?

alorma asked:

What is the current way to add a click to a card… that clips the ripple into the card shape?

Basically, alorma had a button with rounded corners, but the ripple animation was square, not rounded.

Marco Romano suggested a clip() modifier to match the corner rounding:

modifier = Modifier.clip(RoundedCornerShape(4.dp))

Ashar Khan suggested moving the clickable() modifier inside the rounded rectangle:

Card {
    Box(modifier = Modifier.clickable())
}

Google’s Sean McQuillan offered:

Adding a bit more context, if you want to extend this to arbitrary shapes / clips you can move the indicator inside the surface like Button does

This is more relevant to a reusable composable, and clip is fine if you’re comfortable doing a specific design.

The way to think about this is that .clickable() “wraps” the composable it’s passed to, so passing it to surface is effectively:

Clickable { // indicator here
   Surface { // clipping here
   }
}

Inverting that makes it work as expected:

Surface { // clipping here
    Clickable { // indicator here
    }
}

Posted 2021-03-28, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616607027067900


More!

Older “One Off the Slack” articles can be found in the archives.