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!

Visibility and Declarative Thinking

Alex Gherschon asked:

Is there an equivalent to View.setVisibility on Composables? I’d like to hide a Composable when needed but not just change its opacity because this means it still catches onClicks!

And, later, after some explanation, he concluded:

Yes, it seems that 10 years of non-declarative UI molded my brain to think in a certain way 😂

Android developers are used to views having state, such as “am I visible?”, and to change the visual effect we change that state.

Compose is “declarative”, which in this context means “we describe the UI that we want”. So, if this time we do not want this particular composable to be seen, we simply do not include it this time in the composable tree that our functions build up. So long as something triggers the recomposition, Compose gets the new tree and figures out how to modify what we have drawn on the screen to reflect the difference between the old and the new tree.

If you have used RecyclerView and its take on ListAdapter, ListAdapter offers a precursor to this sort of approach. We just hand the ListAdapter our new set of data. ListAdapter, along with a “differ” object, determine which items in the RecyclerView have changed as a result of our data change, and ListAdapter then tells RecyclerView to re-render those items. We do not do that low-level work ourselves: we declare “this is the new set of data”, and ListAdapter figures out how to do that as efficiently as possible.

Or, consider git. In the end, git works off of patch sets, describing which lines in text files need to be replaced by other lines. However, as developers, we do not work with those patch sets directly very much. Instead, we commit changes to a source tree, and git figures out a patch set that represents the difference betwen what we had before and this new set of files. While patch sets represent commands for how to change the text, we instead “declare” that this is our new code, and we rely on git to figure out how to represent that change efficiently.

In terms of Compose, our composables do not know or care what our previous UI rendering was. Instead, we say “this is what we want rendered now”, and we let the Compose runtime (plus code-generated assistance code) figure out how to do that efficiently. So, instead of having a Button property or variable that we manipulate visibility based on input (e.g., some isTheButtonNeeded boolean value), we use that input to branch in our code:

if (isTheButtonNeeded) {
  Button(...) {
    // TODO
  }
} else {
  // never mind, we do not need the button
}

Or, as Brandon McAnsh put it:

Composable’s have the ability to recompose by design. If you don’t need the view to be present simply don’t add it to the tree

It will take time for developers coming from a classic Android View background to get used to this approach. Declarative UIs require a slightly different way of thinking about the problem than what we used before.

Posted 2020-08-09, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1596889657430500


The Changing State of State

In a discussion about state management, Google’s Leland Richardson indicated that:

Zach Klippenstein then asked about savedInstanceState(), and Leland made the analogy:

just a reminder that state : savedInstanceState :: remember : rememberSavedInstanceState

Zach also expressed concern over the demise of state():

TBH, even state seems so common and small that I’m a little disappointed to see it go away, although I completely understand the desire to minimize the amount of magic and the API surface, and this is probably a lot simpler to educate.

As Leland pointed out, the problem is that remember() is the real fundamental API, teaching Compose what to… well, remember. state() is simply a convenience function for remembering a mutableStateOf(), but one that hides both the remember() and the mutableStateOf() facets, and those are useful individually as well as in combination.

Part of this is simply to make sure that future developers can understand the composables created by today’s developers. Or, as Leland put it:

i think for a composable function to be understandable, its somewhat important to be able to identify which objects are “remembered” across recompositions and which ones aren’t

Posted 2020-08-02, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1595971428034200


Compose and Performance

Some Android Developer asked:

Is Compose prone to problems similar to which a deeply nested Android ViewGroup suffers? I mean double layout taxation, slow measure/layout phases, etc. On Android, we always tried to build a flat view hierarchy, without deep nesting or weights. And what with Compose?

The original historical reason for the flat view hierarchy was to avoid running into a StackOverflowError — if the view hierarchy got to 10+ layers deep, we might literally run out of stack space. Fortunately, we have a bigger stack nowadays, so that particular concern has fallen away.

As Zach Klippenstein noted, Compose uses a single-pass layout system. Or, as Google’ Sean McQuillan noted in an earlier Slack thread:

It helped me build a mental model for compose layout to realize that it’s intended to be single pass and it works by modifying the (min, max) constraints passed to children… This takes some getting used to coming from Android views. The main advantage you get is that there’s no way to express non-performant O(2^n) UIs using wrap_content or layout_weight in Compose.

This does not mean that all performance problems are solved by magic. As Zach put it:

A slow layout is going to be slow no matter what, so if you have a really complex ConstraintLayout, it will be slower than a simple Row.

Or, as Google’s Romain Guy wrote:

Deep nesting means deeper call stacks, more code to execute, more state to track, potentially more complex drawing trees, more allocations, etc. No matter what, doing less is always going to beat doing more 🙂

The fact that Compose is function-based means that we can sometimes leverage inline functions to improve performance. dev16 should see inline versions of Row and Column, according to Google’s Leland Richardson, with significant gains as a result:

We saw around 6x improvement in a number of our benchmarks.

Posted 2020-07-26, based on https://kotlinlang.slack.com/archives/CJLTWPH7S/p1595363777120000


More!

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