[MUSIC PLAYING]
TJ DAHUNSI: Hi, and welcome back.
In this episode of the MAD Skills,
you and I, much like the Ed Sheeran song,
are going to be talking about the UI layer.
The role of the UI is to display the application data on screen,
and to serve as the primary point of user interaction.
Whenever the data changes, the UI
should update and reflect those changes.
This could be due to user interaction,
such as pressing the button.
Or external input like a network response.
Effectively, the UI is a visual representation
of the application state in the data layer.
To help explain things, we'll use the Jetpack Compose sample,
JetNews, as a case study.
However, everything we cover here
is just as applicable to Views.
The term UI refers to UI elements,
such as activities and fragments that
display data independent of the APIs they used to do this,
such as Views or Jetpack Compose.
Because the role of the data layer
is to hold, manage, and provide access to the app data,
the UI layer must perform the following steps.
Convert app data to UI specific data.
Update UI elements to reflect UI specific data.
Process user input events that cause UI changes.
And repeat all the above as long as necessary.
To understand this pipeline we'll
cover the following concepts.
How to define the UI state.
How to manage a production of the UI state.
How to expose the UI state with observable data types
according to unidirectional data flow principles.
And how to implement a UI that consumes the observable UI
state.
Let's start with defining the UI state.
In JetNews, the UI presents the user with a list of articles.
This information is the UI state.
In other words, much like two sides of the same coin,
The UI is the visual representation of the UI state.
The UI immediately reflects any changes to the UI state.
In this case study, the information
required to fully render the UI can
be encapsulated in the NewUIState data
class defined as follows.
Note that the UI state definition is immutable.
These frees up to focus on a singular role.
To read the state and update its UI elements accordingly.
As a result, you should never modify the UI state in the UI
directly unless the UI itself is the sole source of its data.
Violating this principle results in multiple sources of truth
for the same piece of information.
Leading to data inconsistencies and subtle bugs.
With the UI state defined, let's talk
about how we can create it.
App data changes over time due either to data updates
or user actions.
It is therefore often beneficial to delegate
the management of the UI state to a separate type.
This prevents the UI from being both the UI and the state
manager.
This separate type is called a state holder.
State holders come in a variety of shapes and sizes
depending on the UI elements that produce state for.
For screens, activities, fragments, or navigation
destinations, a view model instance
is typically the primary state holder.
Let's break down the diagram a bit.
The ViewModel and exposes the state to be consumed by the UI.
The UI notifies the ViewModel of user events.
The ViewModel handles the user actions and updates the state.
The data state is then fed back to the UI to render.
The above is repeated for any event that
causes a mutation of state.
This is the core tenet of unidirectional data flow.
Events go in and state comes out.
This is better illustrated with an example from my case study
where the user wants to bookmark an article.
The ViewModel first transforms data
from the data layer for the UI to display.
When the user bookmarks an article,
the ViewModel relays this to the data layer.
After the data layer processes the request,
the UI consumes the new UI state and displays it to the user.
But how does the UI know that the UI state has changed?
The key to this is observable data.
The UI state should be exposed in an observable data holder
like StateFlow or live data.
This allows the UI to react to any changes in the state
without manually pulling the data directly
from the ViewModel.
These observable data types also have the latest version
of the UI state cached.
This is useful for quick state restoration
after configuration changes.
In Jetpack Compose apps, You can also
use Composes observable state APIs,
such as mutable state of or snapshot flow
for the exposure of UI state.
User events cause the ViewModel to mutate the state.
Since the UI is observing the UI state,
it always sees the most up to date version.
Back to a case study.
Say we want to fetch news for category.
We define our UI state and keep a reference to a job
for fetching the news.
We define a fetch articles method
that first cancels a previous fetch if it was in progress,
and then launches a core routine to bring in the new articles.
After the new articles are fetched,
we can update the UI state.
These change will automatically be pushed to the UI.
If there were any errors fetching,
that error should also be reflected in the UI state.
This is because the UI may want to display
a message to the user notifying them
about the error that occurred.
We'll learn more about this kind of new model and UI event
loop in the next episode.
OK.
We've learned what UI state is, how unidirectional data
flow helps us manage it, and the role state holders
like the ViewModel play in producing and consuming it.
That's quite a bit.
The last step is consuming the UI state in the UI.
Consuming UI state is done with the terminal operator
on the observable data type.
With live data, this is the observed method.
And flows, it's the collect method.
An important thing to note when observing UI state in the UI
is the lifecycle of the UI.
Oftentimes, state holders like ViewModels
live much longer than the UIs that observe them.
So [INAUDIBLE] be taken to ensure
that they are not observing UI state when they can't be seen.
For example, in the news activity,
we make sure that collecting the flow
respects the lifecycle of the activity
by only collecting the flow when the activity is started.
With that, we've covered the UI layer.
We saw how to represent, produce, and consume UI state.
In the next episode, we'll learn more
about the interactions between the ViewModel and the UI.
You don't want to miss that.
So subscribe to be notified when it drops.