Chapter 10. Making Overviews Even Better
When you built the overview/dashboard screen in Chapter 7, Creating Overview Screens, you used a RecyclerView
and used Room and data binding to retrieve the list of records from the database and display them to the user, and it worked fantastically well. However, it can be done even better. RecyclerView
is an incredibly powerful engine for data display, and we've only really scratched the surface of what it's capable of. In this chapter, we'll take a deeper look at some of the ecosystem surrounding the RecyclerView
and integrate some big improvements into the claim example. Specifically, we'll explore the following:
- Different ways to lay out a
RecyclerView
with more than one view type - Ways to improve the
RecyclerView
performance - Animating changes to
RecyclerView
- Keeping the complexity off the main thread
RecyclerView
is capable of handling almost any number of different types of widgets for display on the screen, and recycling them all independently. This is an amazingly powerful and useful technique, not just for being able to display different types of data on the screen, but also to adjust the layout of the RecyclerView
in a way that is mostly transparent. However, you'll need to look at how exactly you want to break the layout up.
There are generally two main reasons you will want to use different view types in a RecyclerView
:
- To break up a long list of items with a divider
- As you have different types of data you want to render together
Let's start with creating and adding dividers; you can just adjust the margin of each of the widgets when the data is bound to them, but that doesn't help the user understand why the divider is there. Often, you'll want a divider to carry details of what it actually represents, such as a date label. In these cases, you need widgets to...
In order to introduce dividers into the claim overview screen, you'll need to run a second pass over the data being delivered from the Room database layer, and figure which items require a divider. This should be done on a background worker thread, so that larger datasets won't impact the user experience. Let's get to work and add some simple dividers to the travel claim app to appear between claim items made on different days; this will require some major changes to how the ClaimItemAdapter
class works. The most obvious change is that it will now have a List
of DisplayItem
objects instead of directly containing a List
of ClaimItem
objects.
Follow these steps to restructure the ClaimItemAdapter
to use DisplayItem
objects to mix both claim items and dividers in the RecyclerView
:
- First, you'll need a nice line that you can use as a divider. This will be a drawable that can be rendered using an
ImageView
widget. Right-click on the res/drawable
directory and select New|
Drawable...
Up until this point, when the data changes in the database, the ClaimItemAdapter
simply tells the RecyclerView
that the data has changed. This is not the most efficient use of resources, because the RecyclerView
doesn't actually know what in the model has changed, and it's forced to relayout the entire scene as though the entire model has changed (although it will reuse the widgets it has already pooled).
RecyclerView
actually has a secondary mechanism that allows you to tell it what has changed, rather than just saying that the data has changed. This is provided through a series of notifications the signal single items, or ranges being added, removed, and moved. The problem is that in order to use these methods, you need to know what has actually changed.
Most developer's first instincts here will be to use more events and signal from the DAO or a delegate layer what is changing, and then catch those events in the Adapter
and forward them to the RecyclerView.
This...
In this chapter, we largely focused on the RecyclerView
and how to make it work even better within your application, and especially for overview/dashboard screens. Changes such as adding the dividers and animations don't change the functionality of an application, but they do change the user experience. In this case, they make it easier for the user to understand the screen and easier for them to understand what happened when things changed.
These sorts of changes can be seen as "polishing" the application. You can build the application without them to ensure that everything works, and then add them in afterward. It's a good idea to slowly build a list of generic structures that can be used to quickly polish any application. A good example will be a generic ActionCommand
to use the DiffUtil
and apply the changes to an Adapter
.
In the next chapter, we'll spend some more time on polishing applications. We'll look at animations, colors, and styling, and explore how to define and use them...