Skip to content

Movies UI - Lists

Wrapup

Now we have some nice list support in our application.

Note that some of our functions are gathering quite a few parameters. There are a few ways to simplify this that we won't be digging into in this class.

Create a State holder

You can group multiple parameters using the "Parameter Object" pattern, into a "State holder". This is a class that is @Stable or @Immutable and contains multiple state and event functions to simplify parameter passing.

The "Parameter Object" pattern also helps reduce changes needed when parameters are added or removed.

As a quick example, suppose we had a chain of composables that use a custom scaffold we've created. The custom Scaffold takes a list of Action objects that describe top-bar actions.

class Action(
    icon: ImageVector,
    description: String,
    onClick: () -> Unit
)

@Composable
fun PersonListScreen(
    title: String,
    actions: List<Action>,
    personList: List<Person>,
    ...
) {
    ListScaffold(title, actions, personList, ...)
}

@Composable
fun <T> ListScaffold(
    title: String,
    actions: List<Action>,
    items: List<T>,
    ...
) {
    CustomScaffold(title, actions) {
        // display items in a LazyColumn
    }
}

@Composable
fun CustomScaffold(
    title: String,
    actions: List<Action>,
    content: @Composable (PaddingValues) -> Unit
) {
    Scaffold(
        topBar = { /* set up title and actions */ }
        ...
        content = content,
    )
}

Note how we need to pass mutiple parameters down just to get them to the CustomScaffold.

Think about what happens when we need to add or remove another parameter for CustomScaffold; we'd need to pass it down or remove it as well.

By grouping parameters into a Parameter object, we can alleviate this:

class Action(
    icon: ImageVector,
    description: String,
    onClick: () -> Unit
)

class CustomScaffoldState(
    title: String,
    actions: List<Action>,
)

@Composable
fun PersonListScreen(
    scaffoldState: CustomScaffoldState,
    personList: List<Person>,
    ...
) {
    ListScaffold(scaffoldState, personList, ...)
}

@Composable
fun <T> ListScaffold(
    scaffoldState: CustomScaffoldState,
    items: List<T>,
    ...
) {
    CustomScaffold(scaffoldState) {
        // display items in a LazyColumn
    }
}

@Composable
fun CustomScaffold(
    scaffoldState: CustomScaffoldState,
    content: @Composable (PaddingValues) -> Unit
) {
    Scaffold(
        topBar = { /* set up title and actions from scaffoldState */ }
        ...
        content = content,
    )
}

Now we can add/remove parameters to/from CustomScaffoldState without affecting the intermediate functions.

CustomScaffoldState is known as a "State holder" in Compose terms.

Composition Locals

Alternatively, you can define a Composition Local to scope data around composable calls.

Warning

I feel this is generally a bad idea, as it makes composable functions more difficult to reason about and test. In general, passing parameters down makes your composable functions much more clear.

There are some uses of it in Compose, such as MaterialTheme, LocalContext and LocalDensity that are useful, but I would much rather have had a context object that all functions take that accesses these data.

The above link gives a great description of Composition Locals, but please don't use them, or use them only with great responsibility.