Building a complex screen with FeatureAdapter

Groupon is all about providing great deals to our customers. One of the main screens in the Groupon Android Application displays all the details about a deal. We call this screen Deal Details and it is one of the most important screens in the app.

One of the most complex screen of the Groupon App: Deal Details

To provide the best possible user experience, this page needs open fast, scroll smoothly, and convey information in a clear manner. For this reason, the screen is constantly evolving — product and development are constantly iterating on this page with new features.

It ends up being very challenging to maintain code quality, memory usage and performance on a screen which is evolving so quickly. Because of the high amount of iteration from such a large number of people, we need an architecture that scales well and maximizes developer productivity.

In this article, we introduce FeatureAdapter: a library that enables us to fully isolate the work of our different teams, enabling them to work on their features without impacting others. FeatureAdapter will help to enforce that the page remains responsive and performant.

Let’s first explain why RecyclerView can be used in complex screens, and then go through the design of FeatureAdapter itself.

Recycler View is not only for lists

We all know about the RecyclerView component from Google. Historically, it has been developed to improve scroll performance on Android. RecyclerView uses various levels of caching and recycling that allows it to draw list items efficiently.

RecyclerView is not only good at drawing long list of items, but it is also good at drawing only the portion of the list that is visible on screen. A ScrollView, would draw all the items of the list in memory, even those which are not actually being displayed on screen. In contrast, RecyclerView will only draw what’s necessary, and will not even inflate the items that are off screen. This laziness of RecyclerView has inspired us to use it for our complex screens.

All the view items are different on Deal Details page. Using a vanilla RecyclerView.Adapter would lead to complex createViewHolder and bindViewHolder methods. They would basically look like long switch statements, and maintaining them would be costly. Also, we wanted to provide a framework where feature development is isolated. Last but not least, we also wanted to guarantee that any new feature could be added without degrading the screen rendering performance. All this together motivated us to create a custom adapter forRecyclerView: FeatureAdapter.

FeatureAdapter

FeatureAdapter enforces separation of concerns and will provide optimal performance for complex screens.

Unidirectional flow of data in FeatureAdapter.

In FeatureAdapter we have the following components:

  • Big Model represents the data for the whole screen. It is typically a POJO(Plain Old Java Object) returned by a network request.
  • FeatureControllers contains the business logic related to a feature. Each FeatureController produces a list of ViewItems
  • ViewItem contains both a Small Model and the AdapterViewTypeDelegate.
  • AdapterViewTypeDelegate is used to create or recycle a RecyclerView.ViewHolder for the Small Model.
  • Small Model is the view model of a view holder. It also represents a portion or a trait of the Big Model, it is derived from the Big Model and can also contain additional computed properties.

Feature developers will now focus only on their own FeatureController, Small Models and AdapterViewTypeDelegates. This ensures a proper isolation and a good separation of concerns. Hence, developers can now work in parallel without impacting each other.

An interesting aspect of this design, which was inspired by Flux, Redux, etc, is that the flow of data is unidirectional. The data flow always goes from the Big Model to the Small Model. Nevertheless, FeatureAdapter also allows every feature to post a FeatureEvent to update the Big Model, which in turn will trigger a screen update.

In order to optimize screen updates, FeatureAdapter relies on two distinct optimization mechanisms when it receives a new Big Model:

  1. every FeatureController can decide whether or not it wants to update its list of ViewItems;
  2. the new Small Models are compared against the old ones using DiffUtil, a standard library to optimize RecyclerView. It is also possible to provide a custom comparator when desired.

The design of FeatureAdapter is very flexible, developers can freely group features the way they want. They can group or split features at the FeatureController level, and/or at the ViewItems level. FeatureAdapter can be used with any RecyclerView.LayoutManager.

The screenshot below is an example of a single FeatureController using multiple ViewItems. Here we use FlexboxLayoutManager to render the view holders horizontally and vertically. As we can see, FeatureAdapter promotes reusability of UI elements.

Additional benefits of FeatureAdapter

Besides the general components mentioned in this article, the FeatureAdapter library provides a few extra modules:

  • FeatureAdapter-Rx which provides a good integration with Rx for managing user interactions. These modules also provides enhanced optimizations to render the features off the UI thread.
  • FeatureAdapter-Group is an extra component that provides more flexibility at the UI level. It allows to group multiple sub-features together and render them horizontally.
  • FeatureAdapter also offers support for animations and item decorators.

Moreover, FeatureAdapter (and especially it’s Rx flavor) has been designed to be highly interoperable with Grox, a model(state) dispatcher library, also open sourced by Groupon. We highly recommend using them together.

Conclusion

We wanted to have a library which emphasizes performance, scalability and separation of concerns in order to render complex screens. As a result we created FeatureAdapter. FeatureAdapter provides the user with a fast screen rendering time, smooth scrolling performances, and advanced features such as animations, decorators, etc.

By using FeatureAdapter we have achieved a maintainable code base which scales well with new requirements. In addition, thanks to the isolation enforced by the library, engineering teams can work on features independently from each other. This allows the team to spend more time focusing on developing great features for the customer, without worrying about whether their work will impact other features.

Feel free to have a look at the FeatureAdapter repo (including its sample) and give us your feedback.

With 💚, Siqi Guo, Alex Lin and Stephane NICOLAS from the Groupon Android Team.