Architecture: Handling UI events - MAD Skills

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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]
Info
Channel: Android Developers
Views: 40,352
Rating: undefined out of 5
Keywords: pr_pr: Android, series: MAD Skills, type: Screencast (0-10min), GDS: Yes, mad skills, modern android development, developer, developers, android developer, android developers, google developers, android, google
Id: lwGtp0Yr0PE
Channel Id: undefined
Length: 10min 17sec (617 seconds)
Published: Mon Mar 28 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.