Multiple rows per header (category) - dextorer/Sofa GitHub Wiki

Introduction

This is certainly one of the most common requests that pop up when developers get their hands dirty with Leanback.

I believe the limitation has been posed intentionally to prevent the UI (and, in turn, the UX) from getting complicated. I agree in a general sense, but there are certain situations that just require a more structured organization for the content, otherwise the result is going to be even messier. YouTube's TV app is possibly the most resounding example, right there in plain sight.

Sofa overcomes this limitation by "relaxing" the strict hierarchical requirements of BrowseFragment, allowing you to display several RowsFragment instances, each one with its own set of rows.

Structure

What follows is a step-by-step tutorial on how adding multiple RowsFragment instances works on Sofa.

1. Start with your BrowseFragment

You can either create a new instance (i.e., new BrowseFragment()) or make your Fragment extend from BrowseFragment. Just make sure you're using the com.sgottard.sofa.BrowseFragment class, and not Leanback's!

2. Create a RowsFragment

Create a new instance of RowsFragment (double check that you're using com.sgottard.sofa.RowsFragment).

3. Create an ArrayObjectAdapter with a ListRowPresenter and fill it as you normally do

Nothing special here, this is something you've done plenty of times. You can add HeaderItems, customize the UI, the behavior and all that stuff.

4. Set the adapter to RowsFragment

Again, nothing special here.

5. Create another ArrayObjectAdapter (no need for a Presenter)

This is something new. Basically, we are going to 'wrap' the RowsFragment instance into an ArrayObjectAdapter container. This is just a container, plain and simple, so there is no need to plug a Presenter for it (even if you do, it is going to be ignored; but why would you do that?).

5. Add a new ListRow that contains the RowsFragment instance and the corresponding HeaderItem

In order to wrap the RowsFragment instance, we need to put it inside a ListRow object. Here you can also specify the corresponding HeaderItem. Providing an HeaderItem is almost mandatory, since it's the only way for the user to move across different RowsFragments.

6. Set the adapter to BrowseFragment

Finally, set the ArrayObjectAdapter as the adapter for BrowseFragment.


This is basically it. If you want to add more RowsFragments, just repeat all the steps and add them to the ArrayObjectAdapter container.

Navigation and behavior

Moving between different RowsFragments can be done only if the headers on the left are visible: so please do not disable them.

Also, if you choose to display the title and the search orb on BrowseFragment, you will be glad to notice that the 'title bar' correctly animates in and out, depending on which row is selected (if headers are hidden) or which header is selected (if headers are visible).

Handling selections and clicks

Typically, BrowseFragment offers you the setOnItemViewSelectedListener() and setOnItemViewClickedListener() methods, but for obvious reasons, these are no longer suitable if you have multiple RowsFragment instances.

There are two possibilities when it comes to handle the selection event and the click event:

  • Set an appropriate listener on the ViewHolder
  • Set an appropriate listener on RowsFragment

I would recommend the second approach, since it guarantees that those events are properly handled by Leanback. It is also a more robust choice.