Focus Navigation in AndroidTV with React Native

Norigin Media
7 min readMar 15, 2021

--

by Guillermo Constantino, Frontend Software Developer, Norigin Media

I am a Software Engineer at Norigin Media, and I develop streaming TV applications for Broadcasters around the world on an ever-growing list of devices from mobiles to Smart TVs.

On Smart TVs or connected TVs, there are many different ways to control the device. An undeniable challenge that I have faced is managing the focus navigation while using a remote control. Focus navigation is essentially how a user interacts with the elements on the screen — moving the focus from one element to another and making selections.

The complexity of implementing this logic varies from platform to platform, but in general the built-in focus navigation doesn’t provide a good user experience. So a lot of custom work is necessary to adapt to standards required by UI/UX.

On web platforms, our team has had to create our own library to manage focus effectively which is compatible with React.

In this article, we will develop a simple streaming app on AndroidTV using React Native, and see how we can manage focus navigation. We will also go into detail about some limitations that the standard navigation has, and some ways to mitigate them.

On a real project, we will have many badges, icons, and different types of gallery items. However, for the simplicity of this article, we will reduce the scope of the gallery item; to have only a wrapper with an image and a text.

Once we have created our gallery item component, we will create a gallery (or carousel), consisting of a list of gallery items. They will be wrapped using a horizontal ScrollView.

Then we will see how we can integrate the standard focus navigation to our Home page.

Let’s start coding!

Let’s start by creating a simple GalleryItem component on React Native, consisting of a View, an Image component using the image prop, and a Text component with a title:

This component has two methods that we need to define: onFocus and onBlur. The first one, as the name suggests, will be triggered when the component is focused. onBlur will be called when the focus is lost.

So basically, what we need to add to this component is a focus state, that we will set to true when onFocus is called, and false when onBlur is called. Of course, we will also add a cool style to our wrapper when it is focused, so we can distinguish it from the other elements. Then our GalleryItem will become:

At this point, we should be able to create a list of ScrollView containing some gallery items. I won’t go into all these details right now, but if you want to look at how this can be implemented, you can check out the Github repository.

So let’s see in action what we have created so far:

Standard Android focus navigation. You can navigate through items with arrow keys

By default, there is no focus on any item; so in order to focus on the first item, we need to press any arrow key. Then by pressing arrow keys, we can navigate through items in the same row, or even jump into the next row. Android calculates the closest item to be focused, changes focus accordingly, and React Native tells us where the new focus is. There is no additional code.

Pretty simple, right?

This is a straightforward solution for a proof of concept, however, when we create a more sophisticated solution, the client (or our QA team or designers) will ask us to change some of the standard behaviors. For example, when we are focusing on the last item of the row, and we press right, the focus will go down by default, but we want to keep the focus on the last item.

I will cover some of these challenges in the next section.

The dark side

Ref: Wallpaperflare

The android navigation works out-of-the-box, and it behaves quite decently if you compare it with navigation on other platforms. However, there are many challenges that we faced when we wanted to override the standard navigation into a more friendly UX.

In this section, I will dive into some solutions — even some that can be considered as a ‘workaround’, but which have saved us a lot of time, and simplified our code.

1. Autofocus

As I described in the previous section, by default there is no focus on any item. No user will be able to understand this behavior, so we need to force the focus on an item by default, and here is when the hasTVPreferredFocus prop comes into play. This prop tells Android navigation that a specific item will be focused when it is initialised. You should pay attention to always have at most one item with this feature enabled, otherwise it won’t work at all.

As a side note, in the documentation it is described as “(Apple TV only) TV preferred focus (see documentation for the View component)”. However this property can also be used on Android TV.

We can set this value to true to select a preferred focus for a specific item. So in this case, we will enable it for the first item in the row, of the first row:

Note: You should pay attention to always have at most one item with this feature enabled (hasTVPreferredFocus=true), otherwise it won’t work at all.

2. Block focus on specific directions (nextFocusDirection)

When we started developing on the first project using Android Navigation, I remember the first issue that was reported by QA was that the items were jumping between rows, when using left/right keys. I mean, if you focus on the last item in the row, and press right (twice) you will jump into the item underneath. We told them that was the “standard” Android navigation, but of course they did not agree since it was not a “standard” focus navigation, as we use in our web applications.

In order to fix this issue we will need to use nextFocusRight (there are other props for all the directions). For those items that we want to block the navigation in the right direction, we will need to set nextFocusRight to the same item. In fact, the value for nextFocusRight will be the node for the current TouchableHighlight component. Then we will use findNodeHandle to the component reference, like so:

And the component reference was saved using onRef callback:

And since we want to block the right navigation only for the last item in the row, we have defined a GalleryItem prop as follows:

3. Resolve focus collisions using translate

We usually face some challenges when we start to have a lot of items on the screen, usually corresponding to different layers, and they overlap each other while we navigate. Let’s take for example the case of a side menu, that slightly overlaps the current carousels. You can see this situation in the picture below:

Here I was focusing on the first item, and I pressed down five times. Since these items are overlapping, the navigation moves to the closest item, even if they are not siblings or the same type of element.

This unexpected behavior would happen in many of our pages since we usually have a side menu on almost all pages. A similar situation happens when we have a keyboard that overlaps with some input or even other GalleryItems.

The first solution that comes to mind to solve this problem, is probably to set nextFocusDown for every item of the side menu, to the item below. And then we can do the same for every first GalleryItem of each row. However, this would require applying the same logic in the up direction. And if we have to do the same work on a lot of different pages, it will become unmanageable and very hard to maintain this code in the future.

So here is a trick that we can use to avoid the collision. We can move the menu out of the screen using left: -200 and then using the translate property (transform: [{translateX: 200}]) to move it back to our desired position. This last transformation is not being taken into account for the Android navigation, thus it will behave as the menu belongs to a different plane.

And voila! We can now navigate down from the side menu without collision to any gallery item. And also when we navigate down through the gallery items, it won’t collide with the menu. There is no additional code required to any other page; only these two simple lines added to the menu.

To summarise…

Focus navigation has unique challenges on different platforms. On AndroidTV, using React Native works out-of-the-box by using TouchableHighlight. However, redefining the standard navigation behavior can be troublesome. Some of these issues can be solved by taking a look at React Native components API. But in some cases, these solutions would require a huge effort, and instead, we recommend doing some workarounds to avoid redefining some logic everywhere.

If you are interested in reading how we solve this challenge on Web platforms, I recommend reading this article: Smart TV Navigation with React.

You can check out the repository with the link below, containing the code for this whole article, including the solutions we presented in the last chapter. Don’t hesitate to ask any questions.

https://github.com/NoriginMedia/android-focus-navigation

--

--