Building for Android TV — Episode 4

Sebastiano Gottardo
Building for Android TV
4 min readMay 27, 2015

Catching up to speed.

Even though I started this series of articles as a tutorial on how to create a custom base layout for TV apps, I have been asked several times to keep writing about Android TV development stuff. This time, I’m going to write about the TV-related additions that came with the latest Leanback Support Library, but also the TV-related deletions that we now need to put up with!

I heard you like settings..

The Leanback Support Library offers a variety of widgets that help us displaying multimedia content in a pleasing fashion. It basically rethinks what we’re used to on Android for smartphones/tablets (like ListViews, GridViews, NavigationDrawers, ..) and brings it to a big TV screen, allowing users to navigate inside the app with its somewhat clumsy TV remote. What Leanback was missing is a way of guiding the user through a series of configurations steps: the app’s settings is the first example that comes to mind, but an introductory wizard may as well be another valid one.

This shortcoming has been addressed by the latest version of the Support Library (22.1.+), by adding the GuidedStepFragment component. Its main purpose, according to the official docs, consists in “guiding the user through a decision or series of decisions”.

This is how GuidedStepFragment looks like in action (taken from Android TV’s own settings):

Using it is as easy as it looks. First things first, create a fragment that extends GuidedStepFragment:

public class SettingsFragment extends GuidedStepFragment {
..
}

Then, override the onCreateGuidance method and return a new Guidance instance:

@NonNull
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
return new GuidanceStylist.Guidance("Settings", null, "Demo", null);
}

Moving on, override the onCreateActions method and provide your own actions:

@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
actions.add(new GuidedAction.Builder()
.id(R.id.settings_category_id)
.infoOnly(true)
.title("General Settings")
.build());

actions.add(new GuidedAction.Builder()
.id(R.id.settings_action_id)
.title("First action")
.description("Some action")
.build());
}

And finally, override the onGuidedActionClicked to actually do something when the user selects that action:

@Override
public void onGuidedActionClicked(GuidedAction action) {
switch ((int) action.getId()) {
case R.id.settings_action_id :
// do something (anything, really)
break;
default :
break;
}

You’re all set! One small thing that you need to be sure of is that GuidedStepFragment uses the Theme_Leanback_GuidedStep theme. The docs specify that there are several ways of doing so, but if you’re simply using Theme_Leanback, you’re good to go! Wondering why?

<style name="Theme.Leanback" parent="Theme.LeanbackBase">..<style name="Theme.LeanbackBase" parent="..">
<item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
</style>
..<style name="Theme.Leanback.GuidedStep" parent="Theme.LeanbackBase">

You can customize nearly every aspect of GuidedStepFragment, by means of the *Stylist objects (e.g., GuidanceStylist), and you can even build a chain of steps to create some sort of wizard. If you’re interested, be sure to take a look at Google’s Leanback sample code!

The working code for the GuidedStepFragment.. Uh? What’s this? Wait a minute...

Give me my listeners back, Google!

Imagine my surprise when I joyfully updated Leanback to 22.1.0, added the GuidedStepFragment example to my project and suddenly noticed that it wasn’t compiling anymore. Gradle immediately pointed me to the culprit:

setOnItemSelectedListener(new OnItemViewSelectedListener() {
..
}

A quick look at the HeadersFragment source code revealed that the method has been removed and it has been replaced by setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener). So far so good. But, as it turns out, the HeadersFragment.OnHeaderViewSelectedListener interface has a package-local visibility. So, there is no way for us to create our own listener. Say that again?

I hate when stuff like this happens.

The setOnItemSelectedListener method is extremely important for the custom version of BrowseFragment to work, because it allows us to detect when the user changes the category on the left (so we can change the content accordingly).

The solution, again, must be found in Leanback’s inner working mechanisms. HeadersFragment (and its base class BaseRowFragment) is simply a wrapper around a VerticalGridView object which, in turn, is a wrapper around a RecyclerView. As we can see here, BaseRowFragment attaches a OnChildSelectedListener instance to the VerticalGridView, calling its onRowSelected() method whenever a selection occurs. HeadersFragment overrides this method and invokes the onHeaderSelected() method of its (private) OnHeaderViewSelectedListener listener.

Nice. We can then take over the HeadersFragment listener by setting our own directly on the VerticalGridView. The code is pretty straightforward:

VerticalGridView grid = getActivity().getVerticalGridView(this);
grid.setOnChildSelectedListener
(new OnChildSelectedListener() {
@Override
public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) {
// we can now update the content accordingly!
}
});

Our custom BrowseFragment is back in business.

Conclusion

GuidedStepFragment is a big addition to Leanback, a component that almost every application are going to take advantage of. I updated example code by adding a GuidedStepFragment example that allows you to switch between the stock BrowseFragment implementation and the custom one.

The code can be found on GitHub.

--

--