[MUSIC PLAYING] MANUEL VICENTE
VIVO: Hi, everyone. Welcome to the third episode
of the Architecture MAD Skills series. After learning about
the data and UI layers, now it is time to take a look
at how to handle UI events. Note that we will show Jetpack
Compose code in this episode. However, the same principles
can be applied to views. UI events are
actions that should be handled in the UI layer. We'll take a look at
two types of events, the user and ViewModel events. User events are
produced by users when they interact with the
app, for example, by tapping a button on the screen. ViewModel events
are actions that we want the UI to take that
originate from the ViewModel. OK, let's start by
covering user events. As with previous
episode, we'll use the Jetpack Compose get new
sample to show some use cases. As I mentioned
earlier, user events are produced by the user
interacting with the app. For example, when
there are no posts available to show
on the screen, we show a button that lets the
user load the content manually. This code path can be found
inside the HomeScreenWithList composable in the
HomeScreens file. In Compose, we just pass a
value to the onClick parameter available in the
button composable. Note that in the
view system, you could use a certain click
listener in the button to react to the event. You may already know
Compose uses functions as a way to propagate events,
update your hierarchy until we reach a composable that
knows how to handle them. As refreshing content
is business logic, we delegate that logic
to the ViewModel. Going up the UI
hierarchy, we end up here in the HomeRoute composable
present in the HomeRoute file. In there, we have a
state for composable where we can call our new model. In the onRefreshPost's
lambda, we call homeViewModel.refreshPosts
function that will trigger the business logic. If we take a look at
the ViewModel class, you can see how it
exposes functions that handle business logic for
the UI to call when needed. User events are
very common in apps, as it is what makes them
responsive-- nothing new here. You've probably been doing
this for a long time already. Let's move on to
ViewModel events. ViewModel events
are actions you want the UI to take that originate
from the ViewModel class, for example, telling
the user that there is no internet connection
or that a request failed. Even though some
of these actions produce transient UI updates,
such as showing messages on the screen, they
should also be modeled as part of the UI state. These events should always
result in a UI state update, since that's the way we
communicate with the UI. And that is what we
are going to do next. Let's see how we can
communicate with the UI to display an error
message on the screen when a request fails. Back to the
HomeViewModel class, see how HomeUiState models the
UI state on the home screen. It is exposed StateFlow, which
is an observable data holder type. As alternatives, you could use
live data or composite state APIs, such as mutable state of. As all ViewModel events should
be modeled as part of the UI state, we add a property
to our home UI state called errorMessages of
type list of integers, which represent a string
resource ID, which is a list, as we
might want to queue up multiple messages that
are displayed sequentially to the user. Note that the ViewModel
is agnostic of the UI implementation. These error messages could be
displayed in a toast, snack bar, or dialog. The name is generic enough
to accommodate any UI needs. In this case, our
designer decided that we should show these
messages in a snack bar, as seen here. Also, see how we allow
the user to retry again, if they want to. Before jumping into
the code, let's see how this is going to work. When the request
fails, the ViewModel will update the UI state
with a new error message. Then the UI will react
to the new UI state and display the
message on the screen. When the message is gone, either
because the user interacted with it or it timed
out, the UI will notify the ViewModel, which will
remove the message from the UI state. In Compose the
scaffoldState has a property called snackbarhostState,
which has a method called showSnackbar. We will use this method
to show the snackbar. The code we need to look at
is the refreshPosts function from the ViewModel
that gets called when new data is requested. Even though we are
interested in the case when the request fails, let's take
a quick look at the happy path. When we call the
function, we update the UI state to indicate to the
UI that the data is loading. It refers to the current UI
state, which is immutable, so we make a copy of it
and update it is loading. Then we launch a new
coroutine to call the suspended getPostsFeed
method from the repository. If the result is successful,
we update the UI state with the postsFeed, which is
the data that the repository returned, and set the
loading flag to false, because the data
is already loaded. OK, that's cool,
but we are in fact interested in the case
when the request fails and the repository
returns an error. If that's the case, we need
to update the error messages queue. First, we add the string
resource ID of the load error message to the current
error messages queue and then we update the
UI state with a copy that contains the new messages
and the loading flag to false. To show the snackbar
in the UI, we need to handle these error
messages in the home screen with the list composable,
where we have access to the snackbar
home state, which is part of the scaffold state. Apart from the UI state
and scaffold state, the composable also
takes two functions as parameters,
onRefreshPosts, in case the user taps on
the Retry button, and onErrorDismiss, to
notify when the error message has been dismissed. In the composable
function body, we need to check if there
are any error messages to display on the screen. If that's the case, we
retrieve from resources the actual text for the
error message and the one for the Retry button. To show the snackbar, we need to
call the suspend show snackbar function available in
the snackbar state. Since show snack bar is
a suspended function, we need to call it in
the context of a routine. LaunchedEffect is a
composable designed for exactly this purpose. We call the function,
passing our error message. Then we draw a message
on the scaffold state. These are used as keys
by the launch effect. And if any of their
values change, the routine will be
canceled and restarted so that the UI always
displays the most up to date information. As this effect creates
a new coroutine, we can now call the suspended
showSnackbar function, passing the error and retry
messages as parameters. Calling this function
is what displays the snackbar on the screen. As any other suspended function,
it suspends the coroutine until the snackbar
result is available, which is either when the user
taps Retry or the snackbar is dismissed after a
certain amount of time. We can check whether the user
tapped on the Retry button by checking the snackbar
result. If the result is equal to action performed, the
user tapped on the Retry button and we called the
onRefreshPosts lambda. At this point, we know that
the message is no longer shown on the screen. Therefore, we called the
onErrorDismiss lambda with the message
I need to notify that the error was dismissed. Cool. Our job here is done. Snackbar will be shown
whenever the ViewModel adds a new message to the
error messages list. Up in our [INAUDIBLE]
hierarchy, we end up in the
HomeRoute composable that is in charge of
interacting with the ViewModel. Here, we give a value to
the onErrorDismiss lambda that tells the ViewModel
that the error has been shown to the user. If we jump to that
function, you can see how we are removing the
message from the error messages list and updating the UI
state with the new value. The user interacting with the
message or the message timing out is a state change that the
ViewModel should be aware of. That's why the UI has
to communicate when it happens to the ViewModel. However, this
configuration is only required for those events that
are affected by user actions. And that's it for this episode. I hope you learned the different
types of UI events and the best practices to deal
with them in code. To learn more about
this topic, don't forget to check out our
guidance on UI events. Link below. Next up in the series is
the domain layer episode. Don't forget to subscribe to
the Android Developers YouTube channel to get notified
when it comes out. Bye. [MUSIC PLAYING]