CHRIS ARRIOLA: With Jetpack Compose,
you describe your UI in Kotlin as functions.
No more XML needed.
Let's dive deeper into these functions
and how you can build UI with them.
[UPBEAT MUSIC]
Let's see how we can build a single-choice question
screen of jetsurvey, one of our compose samples.
Earlier, we saw that a single answer in the survey
can be written and composed as a function containing
a row with an image, text, and a radio button.
To create a UI component in Compose,
we must annotate a function with @Composable annotation.
This annotation tells the Compose compiler
that this function is intended to convert data
into UI, so an answer into UI.
Functions with this annotation are also
called composable functions, or composables for short.
These functions are the building blocks of UI in Compose.
It's quick and easy to add this annotation,
encouraging you to break your UI into a library
of reusable elements.
For example, if we wanted to implement
a list of possible answers to select from,
we can define a new function called SingleChoiceQuestion
that takes in a list of answers and then call the SurveyAnswer
function that we have just defined.
This composable function accepts parameters,
which allows it to be configured by the app's logic.
In this case, it accepts a list of possible answers
so that it can display these options to the UI.
This function doesn't return anything,
but instead it emits UI.
Here, it emits the column layout composable function
that's part of the Compose toolkit, which arranges items
vertically.
Within this column, it emits a survey answer composable
function for each answer.
Composables are also immutable.
You can't hold a reference to them
like holding a reference to a single answer
and later update its contents.
You need to pass any and all information
as parameters when you call it.
Notice that since this function is in Kotlin,
we can use the full Kotlin syntax and control flow
to produce our UI.
Here, we're using forEach to iterate through each answer
and call SurveyAnswer to display them.
If we want to conditionally display something else,
it's as simple as using an if statement.
No view.visibility equals gone or invisible needed.
You just call the composable if it needs to be displayed.
This function is also fast and free of side effects.
It must behave the same way when called multiple times
with the same argument.
And it shouldn't modify properties or global variables.
We say that functions with this property are item potent.
This property is necessary for all composable functions
so that UI can be emitted correctly when functions
are re-invoked with new values.
Notice that the parameters provided to the function
entirely control the UI.
This is what we mean by transforming state into UI.
The logic in the function will guarantee that the UI can never
get out of sync.
If the list of answers changes, then a new UI
is generated from the new list of answers
by executing this function again and redrawing
the UI if necessary.
This is called recomposition.
Recomposition happens when a composable
is re-invoked with different function parameters.
But it can also happen when internal state in the function
changes.
For example, say the SurveyAnswer composable
accepts a parameter if the answer is selected or not.
Initially, no answer is selected.
In the view world, interacting by tapping on one of the answer
UI elements would visually toggle it.
But in Compose, since we passed False to all SurveyAnswer
composables, all answers would remain unselected
even when tapped.
We need to make the composable recompose whenever
the user interacts with the UI.
To do that, we need a variable that holds the selected answer.
But this isn't enough.
We also need to be able to tell Compose
that when this variable changes, recomposition
should happen as well.
To do that, we need to wrap the answer in a mutable state
object.
A mutable state is an observable type
that is integrated within the Compose runtime.
Any changes to the state will automatically
schedule a recomposition for any composable functions
that read it.
To create a mutable state, we can
call the mutableStateOf method.
Since no answer will be selected initially,
we initialize this to null.
We also need to update the value of isSelected
to compare the current answer to the selected answer.
Since selected answer is of type MutableState,
we have to use the value property
to get the selected answer.
When this value changes, Compose will automatically
re-execute the SurveyAnswer composable so that the selected
answer is highlighted.
Creating a state object also requires
that it be called within a call to remember.
This guarantees that the value is remembered and not reset
when the composable recomposes.
With what we have now, while the UI survives recomposition,
it won't survive configuration changes.
To remember values across config changes,
we can use rememberSaveable.
Right now, the type of the variable is a MutableState.
But we can use Kotlin's delegated property syntax
by using the by keyword.
Doing so changes the type of our variable to an optional answer.
This syntax is nice, as we can work directly
with the value of the underlying state.
No more calls to the value property on a MutableState
object.
With our newly introduced state, we
can pass a lambda function for the onAnswerSelected parameter
so we can perform an action when the user makes a selection.
In the definition of this lambda,
we can set the value of selected answer to the new one.
If you recall from the previous videos,
events are the mechanism for how state is updated in Compose.
Here, the event onAnswerSelected would
be invoked when the user interacts by tapping an answer.
The Compose runtime automatically
keeps track of where state reads occur
so that it can intelligently recompose composables
that depend on that state.
As a result, you don't need to explicitly observe state
or manually update the UI.
There are some other behavioral properties
of composable functions you should
be aware of Because of these behaviors,
it's important that your composable functions are
side-effect free and behave the same when called multiple times
with the same argument.
First, composable functions can execute in any order.
Looking at the code for a composable,
you might assume that the code runs in order.
But this isn't necessarily true.
Compose can recognize that some UI elements are
higher priority than others.
So those elements might be drawn first.
Say, for example, you have code that draws
three screens in a tab layout.
You might assume that the Start screen is executed first.
However, these executions can happen in any order.
Composable functions can also run in parallel.
This lets Compose take advantage of multiple cores, which
improves the rendering performance of a screen.
In this code snippet, the code runs side-effect free
and transforms the input list to UI.
But if the function writes to a local variable
such as in the snippet, this code
is no longer considered side-effect free and may result
in odd behaviors in your UI.
Recomposition skips as much as possible.
Compose does its best to recompose
only portions of the UI that need to be updated.
If a composable doesn't use the state that
triggered recomposition, then it will be skipped.
In the snippet, if the name string changes,
the header and footer composables
will not be re-executed since it doesn't depend on that state.
Recomposition is optimistic.
This means that Compose expects to finish recomposition
before the parameters change again.
If a parameter does change before recomposition finishes,
Compose might cancel the recomposition
and restart it with a new parameter.
Lastly, composable functions might run frequently.
This might be the case if your composable function contains
an animation that needs to be executed for every frame.
This is why it's important to make
sure your composable functions are
fast to avoid dropped frames.
We covered a lot.
So let's do a quick summary.
You can create composable functions
by using the @Composable annotation.
It's quick and easy to create composables,
encouraging you to break your UI into a library
of reusable components.
Composables can and should accept parameters
to configure their behavior.
MutableState, remember, and rememberSaveable
can be used to store a component state
and have Compose automatically track and recompose changes.
And composables should be side effect free.
We also learned about some interesting properties
of composable functions.
Composables can execute in any order, run in parallel,
be skipped, and run frequently.
The Compose toolkit provides a lot
of foundational and powerful composables
that help you build beautiful apps.
That's what we'll cover next.
If you want to jump ahead, check out the links
in the description below, otherwise
make sure to hit Subscribe so you don't miss the next one.
Stay tuned.
[UPBEAT MUSIC]