How to add Touch interactions to Android-based Fire TV applications

If you are building an app for Amazon Fire TV, it is good practice to implement the navigation and interaction of your app UI with the remote control. This can be achieved in multiple ways, but the key concept is to map the controls of the movement and interactions inside your apps to the D-Pad and buttons present on the Fire TV remote. The Fire TV documentation on UX best practices covers this basic aspects of app navigation and input.

With Fire TV expanding to automotive, it’s important to now add touch interactions to Fire TV apps on top of the D-Pad navigation. Customers will both be able to use remote controls, and touch on these devices.

In this tutorial we will see how to modify Android-based Fire TV apps designed for D-Pad to add touch interaction and how to provide a good touch-based UX.

The tutorial will cover 5 key steps:

 

  • Step 1: Understanding UI Directional Navigation and focus with D-Pad
  • Step 2: Applying Directional Navigation to TV apps layouts
  • Step 3: Adding touch interactions for clicks: OnClickListeners
  • Step 4: Managing view focus between D-Pad and touch interactions
  • Step 5: Additional best practices and testing touch on Fire TV

 

Step 1: Understanding UI Directional Navigation and focus with D-Pad

Android-based applications for Fire TV should follow clear platform-level patterns for remote-based navigation. Since Fire OS is built on top of Android, it follows the same layout and design patterns as standard Android apps.

In order to map app UI navigation automatically to the remote D-Pad, and specify in what order the different Android Views should be navigated by the customer, we need to use Android’s Directional Navigation (see Android’s documentation here). This is a best practice, and if your Android application doesn’t follow this pattern, it’s important to apply the following changes as this impacts how the Touch behaviour connects to the D-Pad behaviour.

The Directional Navigation requires us to specify for each “focusable” view what is the next or previous view that needs to be selected. This will allow the system to automatically map what is the next view that will be focused on when a user presses the navigation buttons on their remote D-Pad (Up, Down, Left, Right).

This is achieved by adding to our layout XML files views the following tags:

android:nextFocusDown, android:nextFocusRight, android:nextFocusLeft, android:nextFocusUp

followed by the id of the view we want the app to select next based on the order.

So for example

<TextView android:id="@+id/Item1"
    	  android:nextFocusRight="@+id/Item2"/>

Allows the TextView called Item1 to move to the view called Item2 when the customer presses the “Right” button on their D-Pad. This applies to all directions in navigation.

 

Step 2: Applying Directional Navigation to TV apps layouts

Before we apply touch interactions to our Fire TV app, we need to make sure that we create a consistent Directional Navigation experience between D-pad and touch navigation.

This is quite simple for basic apps interfaces, but Media and Entertainment TV Apps interfaces can be quite complex, as more often than not they need to display dynamic content. Hence, most views might be generated at runtime.

In order to achieve this, most developers use Android Views that can easily hold dynamic content. A good example of this is the RecyclerView. The RecyclerView is a component which allows dynamic content to be parsed from an adapter. RecyclerView is efficient and it implements one of the standard Android patterns: the ViewHolder.

However, since the content of a RecyclerView is dynamic, we need to make sure that the navigation between the views which are generated inside RecyclerViews is correct.

In order to demonstrate this, we created a simple application which simulates the standard implementation of a TV interface. This application has two main UI components:

  • A first LinearLayout called “menuLayout”, containing a RecyclerView called “recyclerViewMenu” which itself contains the left-side menu with all the categories
  • A second LinearLayout called “rowsLayout” containing other RecyclerViews which instead contain all the movies and content that can be played

On the left you can see the menuLayout in black, and on the right the rowsLayout in grey

 

While this is an oversimplification for the sake of this tutorial, as your app might have more complex nesting for its views, this represents the skeleton of a dynamic Media/TV App UI.

What you want to do now is to define how the directional navigation works on this layout.

The first thing that we want to make sure of is that we can actually move from our categories menu to the content rows. So what we do is to set nextFocusRight to the first row RecyclerView for our LinearLayout:

<LinearLayout
   android:id="@+id/menuLayout"
   [...]
   android:nextFocusRight="@id/rowRecyclerView1">

This way, once the user clicks on the right button, it will automatically move the focus to the first RecyclerView on the right.

Another thing that we need to do is to set up how the navigation between the items of the RecyclerView itself works. Since the views of a RecyclerView are created dynamically at runtime, it is not practical to manually set the direction of navigation on each individual view; it cannot be achieved using the XML layout anyway. Instead, in order to do this, we need to use a very specific tag on the RecyclerView called descendantFocusability:

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/recyclerViewMenu"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:descendantFocusability="afterDescendants" />

By setting descendantFocusability to afterDescendants we ensure that automatically, once the views are dynamically generated, the RecyclerView itself provides focus to the items inside the RecyclerView (in this case, it gives focus to the categories defined inside the RecyclerView menu).

Note: it is important to apply this to all our RecyclerViews, as this automatically defines the relationship between the items. The great news is that we don’t have to manually define the relationship between each item because Android automatically takes care of that for us as a framework level feature.

We need to apply this to all the RecyclerViews in our right side layout as well.We need to define the Directional Navigation between each one of the RecyclerView (for sake of simplicity, in our example we defined 4 rows through 4 dedicated RecyclerView)

At the end, our RecyclerViews should look like this:

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rowRecyclerView1"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:descendantFocusability="afterDescendants"
   android:nextFocusDown="@id/rowRecyclerView2" />

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rowRecyclerView2"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:descendantFocusability="afterDescendants"
   android:nextFocusDown="@id/recyclerView3" />

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rowRecyclerView3"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:descendantFocusability="afterDescendants"
   android:nextFocusDown="@id/rowRecyclerView4" />

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rowRecyclerView4"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:descendantFocusability="afterDescendants"/>

Notice how the last RecyclerView, rowRecyclerView4, doesn’t have the next Focus down target. The reason for that is there is no further RecyclerView to navigate to.

Once we complete this step, we now have a fully navigable UI using the D-Pad. We can now look into how to properly touch-enable our interface by modifying the content of our RecyclerViews.

You can see how the UI is fully navigable using the remote even though the views are generated dynamically.

 

Step 3: Adding touch interactions for clicks: OnClickListeners

The next step is to add touch interactions when the user clicks on an item. Luckily for us, Android was built for touch in mind. To add click actions to our applications UI, we can use standard Android components which will allow us to cover both the D-Pad interaction and the touch interaction. 

The best and easiest way to implement click actions or touch actions on a view is to use the standard Android OnClickListener. OnClickListener allows both touch clicks and d-pad button clicks to be performed on a view. onClickListener triggers a method called onClick() where you can execute any desired operation.

Note: If you have implemented the click action on your or D-Pad based UI in any other way, you might need to add the OnClickListener on top of any custom implementation you have. This is to ensure D-Pad clicks and touch clicks both execute the desired operation.

While on a D-Pad style navigation the size of the view is not important, , it is very relevant for touch interactions. Therefore, it’s better to use larger views and areas in our UI to provide a good user experience.

In our simple application we are going to apply the OnClickListener to the layout itself and not to the views inside of the layout. This  could be achieved also by expanding the internal views to fill the entire layout area and apply the OnClickListener to the individual views like TextView or ImageView, however applying it to the entire layout it’s a simple solution that allows us to fulfil our goal and doesn’t require to change the look and feel of the UI at all.

The views are dynamic and created by the RecyclerViews, so we need to apply individual on click listeners to each one of the created elements of each RecyclerView.We do this by modifying the code for the RecyclerView adaptors, getting a reference to the layout of each individual item of the RecyclerView and applying the onClickListener in the onBindViewHolder() method of the adapter:

public class MenuItemsAdapter extends RecyclerView.Adapter<MenuItemsAdapter.ViewHolder> {

  private String[] localDataSet;

   /**
    * Provide a reference to the type of views that you are using
    * (custom ViewHolder).
    */
   public class ViewHolder extends RecyclerView.ViewHolder {
       private final TextView textView;
       private final ConstraintLayout menuConstraintLayout;

       public ViewHolder(View view) {
           super(view);
           // Define click listener for the ViewHolder's View
           textView = (TextView) view.findViewById(R.id.textView);
           menuConstraintLayout = view.findViewById(R.id.menuconstraintLayout);
       }

       public TextView getTextView() {
           return textView;
       }

       public ConstraintLayout getMenuConstraintLayout() {
           return menuConstraintLayout;
       }
   }

   /**
    * Initialize the dataset of the Adapter.
    *
    * @param dataSet String[] containing the data to populate views to be used
    * by RecyclerView.
    */
   public MenuItemsAdapter(String[] dataSet) {
       localDataSet = dataSet;
   }

   // Create new views (invoked by the layout manager)
   @Override
   public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
       // Create a new view, which defines the UI of the list item
       View view = LayoutInflater.from(viewGroup.getContext())
               .inflate(R.layout.menulayout, viewGroup, false);

       return new ViewHolder(view);
   }



   // Replace the contents of a view (invoked by the layout manager)
   @Override
   public void onBindViewHolder(ViewHolder viewHolder, final int position) {

       // Get element from your dataset at this position and replace the
       // contents of the view with that element
       viewHolder.getTextView().setText(localDataSet[position]);
       viewHolder.getMenuConstraintLayout().setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
             //In this sample app we are just logging the Click, 
//but here you could for example open a new Activity or select 
//a specific Category  

Log.e("Click ", "Clicked "+localDataSet[position]);
           }
       });
   }

   // Return the size of your dataset (invoked by the layout manager)
   @Override
   public int getItemCount() {
       return localDataSet.length;
   }
}

In order to show if an item is focused on or clicked, it’s important to use backgrounds and drawables that contain the different states in which a specific view can find itself.

This is easily achieved using a selector drawable, which can contain multiple states like focused and pressed (clicked)

Menuselectordrawable.xml (used as background for our Menu Layout)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_pressed="true"
       android:drawable="@android:color/holo_blue_bright" /> <!-- pressed -->
   <item android:state_focused="true"
       android:drawable="@android:color/holo_orange_light" /> <!-- focused -->
   <item android:state_hovered="true"
       android:drawable="@android:color/holo_green_light" /> <!-- hovered -->
   <item android:drawable="@android:color/background_dark" /> <!-- default -->
</selector>

In the menulayout.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/menuconstraintLayout"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/menuselectordrawable"
   android:focusable="true"
   android:focusableInTouchMode="true">

At this point, all of our UI views are clickable and will show a different background color when clicked on (blue) and when focused on (orange)

You can see the onClickListeners being correctly triggered via the remote control.

 

Step 4: Managing view focus between D-Pad and touch interactions

The next step is to build a consistent experience between the d-pad and touch interactions. This means making sure that the directional navigation works consistently if we are using a remote or if we are interacting using touch.

As we mentioned above, Android was built with touchscreen and touch interactions in mind.  This means that the underlying layer managing our apps UI and views is mostly touch-enabled already.

In Android, most of the views are visible and focusable by default. Actually, views inherit a parameter called focusable, which by default is set to “auto”, meaning that is up to the platform itself to determine if this view should be focusable or not. Views like Buttons, TextView, EditText are focusable by default as those are main UI components, while layouts and layout inflators are usually not automatically focusable by default, as they are usually used just to define the UI structure.

In order to make our app fully touch-enabled, we need to make sure that the most important views in our app are focusable, and also we need to make sure that they can be focused on when the customer uses touch.

For this reason, we are going to edit two parameters in our views: focusable and focusableInTouchMode.

Going back to our sample app, we created two new separate layouts which are used to populate the individual items inside the “Categories” RecyclerView and the “rows” RecyclerView.

 

We need to make sure that:

  1. The whole layout is treated as touch surface.
  2. We are enabling users to focus on the layout both using the D-Pad and touch.

We do this by setting both focusable and focusableInTouchMode to true.

 

menuLayout.xml (defines the individual content of the Categories on the left, which contains only a TextView)

<androidx.constraintlayout.widget.ConstraintLayout 
   [...]
   android:id="@+id/menuconstraintLayout"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/menuselectordrawable"
   android:focusable="true"
   android:focusableInTouchMode="true">

   <TextView
       android:id="@+id/textView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
      [...] />

cardLayout.xml (defines the individual content of the Movies rows on the right. Each card contains a ImageView and a TextView)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
[...]
   android:id="@+id/cardconstraintLayout"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:background="@drawable/selectordrawable"
   android:focusable="true"
   android:focusableInTouchMode="true"
>

   <ImageView
       android:id="@+id/imageView"
       android:layout_width="150dp"
       android:layout_height="100dp"
       [...] />

   <TextView
       android:id="@+id/textView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       [...] />
</androidx.constraintlayout.widget.ConstraintLayout>

By doing this, we ensure that if the user touches the UI or if they navigate through the d-pad controllers, in both cases the right UI element will be focused on. See clip below for a demonstration.

In this clip you can see how the views are correctly clicked and focused on using touch interaction.

 

Step 5: Additional best practices and testing Touch on Fire TV

After completing the steps above, we will have successful touch-enabled the most important components of our app UI.

There are additional simple steps to ensure that we are providing a great user experience for both touch and d-pad navigation:

 

  • Ensuring that all the views that need any kind of interaction have an OnClickListener assigned to them and can be focused on
  • Remember that touch interaction also includes the gestures and scrolling. Therefore do not just rely on the standard behaviours of views using D-Pad Navigation but make sure that least include ways to be scrolled through gestures (for example using scroll views and RecyclerView is where possible).
  • Secondary activities of your applications (for example a Detail page or the Playback UI) need to be also touch enabled so make sure that if you have any settings page those are touch enabled as well and use the same patterns described above.

 

How can you test Touch on Fire TV devices without a touchscreen?

The easiest solution is to connect a wireless mouse to your Fire TV. Mouse on Android simulates touch interaction. You can do this by:

  1. Going to Settings
  2. Go to Remote and Bluetooth Devices
  3. Go to Other Bluetooth devices
  4. Follow the on-screen instructions on how to connect your Bluetooth mouse
  5. After connecting the mouse, go back to your app. The mouse will show a cursor on screen you can use to simulate touch interactions, including clicks and gestures.

 

Conclusion

This tutorial gave you a practical first overview on how to touch enable your Fire TV application. 

For more details on these please check out our documentation:

Introducing “Serverless Migration Station” Learning Modules

Posted by Wesley Chun (@wescpy), Developer Advocate, Google Cloud

graphic showing movement with arrows,. settings, lines, and more

Helping users modernize their serverless apps

Earlier this year, the Google Cloud team introduced a series of codelabs (free, online, self-paced, hands-on tutorials) designed for technical practitioners modernizing their serverless applications. Today, we’re excited to announce companion videos, forming a set of “learning modules” made up of these videos and their corresponding codelab tutorials. Modernizing your applications allows you to access continuing product innovation and experience a more open Google Cloud. The initial content is designed with App Engine developers in mind, our earliest users, to help you take advantage of the latest features in Google Cloud. Here are some of the key migrations and why they benefit you:

  • Migrate to Cloud NDB: App Engine’s legacy ndb library used to access Datastore is tied to Python 2 (which has been sunset by its community). Cloud NDB gives developers the same NDB-style Datastore access but is Python 2-3 compatible and allows Datastore to be used outside of App Engine.
  • Migrate to Cloud Run: There has been a continuing shift towards containerization, an app modernization process making apps more portable and deployments more easily reproducible. If you appreciate App Engine’s easy deployment and autoscaling capabilities, you can get the same by containerizing your App Engine apps for Cloud Run.
  • Migrate to Cloud Tasks: while the legacy App Engine taskqueue service is still available, new features and continuing innovation are going into Cloud Tasks, its standalone equivalent letting users create and execute App Engine and non-App Engine tasks.

The “Serverless Migration Station” videos are part of the long-running Serverless Expeditions series you may already be familiar with. In each video, Google engineer Martin Omander and I explore a variety of different modernization techniques. Viewers will be given an overview of the task at hand, a deeper-dive screencast takes a closer look at the code or configuration files, and most importantly, illustrates to developers the migration steps necessary to transform the same sample app across each migration.

Sample app

The baseline sample app is a simple Python 2 App Engine NDB and webapp2 application. It registers every web page visit (saving visiting IP address and browser/client type) and displays the most recent queries. The entire application is shown below, featuring Visit as the data Kind, the store_visit() and fetch_visits() functions, and the main application handler, MainHandler.


import os
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
'create new Visit entity in Datastore'
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
'get most recent visits'
return (v.to_dict() for v in Visit.query().order(
-Visit.timestamp).fetch(limit))

class MainHandler(webapp2.RequestHandler):
'main application (GET) handler'
def get(self):
store_visit(self.request.remote_addr, self.request.user_agent)
visits = fetch_visits(10)
tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(tmpl, {'visits': visits}))

app = webapp2.WSGIApplication([
('/', MainHandler),
], debug=True)

Baseline sample application code

Upon deploying this application to App Engine, users will get output similar to the following:

image of a website with text saying VisitMe example

VisitMe application sample output

This application is the subject of today’s launch video, and the main.py file above along with other application and configuration files can be found in the Module 0 repo folder.

Next steps

Each migration learning module covers one modernization technique. A video outlines the migration while the codelab leads developers through it. Developers will always get a starting codebase (“START”) and learn how to do a specific migration, resulting in a completed codebase (“FINISH”). Developers can hit the reset button (back to START) if something goes wrong or compare their solutions to ours (FINISH). The hands-on experience helps users build muscle-memory for when they’re ready to do their own migrations.

All of the migration learning modules, corresponding Serverless Migration Station videos (when published), codelab tutorials, START and FINISH code, etc., can all be found in the migration repo. While there’s an initial focus on Python 2 and App Engine, you’ll also find content for Python 3 users as well as non-App Engine users. We’re looking into similar content for other legacy languages as well so stay tuned. We hope you find all these resources helpful in your quest to modernize your serverless apps!

Connect to your Amazon CloudWatch data to detect anomalies and diagnose their root causes using Amazon Lookout for Metrics

We are excited to announce that Amazon Lookout for Metrics now allows you to detect anomalies on your Amazon CloudWatch data. Amazon Lookout for Metrics uses machine learning (ML) to automatically detect and diagnose anomalies (outliers from the norm) without requiring any prior ML experience. Amazon CloudWatch provides you with actionable insights to monitor your applications, respond to system-wide performance changes, optimize resource utilization, and get a unified view of operational health.

Amazon Keyspaces (para Apache Cassandra) ahora ayuda a monitorear y mejorar el rendimiento de lectura/escritura de la aplicación, así como la capacidad de producción, mediante el uso de nuevas métricas de Amazon CloudWatch

Amazon Keyspaces (para Apache Cassandra), un servicio de base de datos compatible con Apache Cassandra, escalable, de alta disponibilidad y completamente administrado, ahora ayuda a monitorear y mejorar el rendimiento de lectura/escritura de la aplicación, así como la capacidad de producción, mediante el uso de nuevas métricas de Amazon CloudWatch.

Amazon Connect Customer Profiles launches in Canada(Central) region

Amazon Connect now allows you to enable Amazon Connect Customer Profiles in Canada (Central) region, equipping contact center agents with the most up to date information about the incoming contact to provide faster and more personalized customer service. Customer Profiles automatically brings together customer information from multiple applications such as Salesforce, Amazon S3 and ServiceNow into a unified customer profile, delivered to agents at the beginning of the customer interaction. 

AWS Gateway Load Balancer ahora se encuentra disponible en las regiones AWS GovCloud (EE. UU.)

AWS Gateway Load Balancer está disponible ahora en las regiones AWS GovCloud (EE. UU.). Gracias a la expansión a la región de AWS GovCloud (EE. UU.), las agencias gubernamentales de EE. UU. y sus subcontratistas podrán trasladar a la nube cargas de trabajo con un mayor nivel de confidencialidad al disponer de ayuda para abordar sus requisitos específicos de conformidad y normativos. Con este lanzamiento, AWS Gateway Load Balancer ya está disponible en 25 regiones.

Amazon Aurora PostgreSQL es compatible con la extensión pg_proctab para acceder a las estadísticas de sistema de PostgreSQL

La edición de Amazon Aurora compatible con PostgreSQL incorpora compatibilidad con la extensión pg_proctab. Dicha extensión es una recopilación de funciones almacenadas que pueden acceder a la tabla de procesos de sistemas operativos, de modo que se pueden realizar consultas de las estadísticas del sistema mediante la base de datos. Las funciones de pg_proctab hacen que sea más fácil recopilar datos de su base de datos de PostgreSQL, incluida información actualizada sobre estadísticas del procesador u operaciones de E/S en consultas de SQL, lo cual facilita la resolución de problemas. 

Amazon Aurora PostgreSQL es compatible con la extensión pg_partman para la administración de particiones de tablas basadas en ID de serie o tiempo

La edición de Amazon Aurora compatible con PostgreSQL soporta la extensión del administrador de particiones (pg_partman). Se trata de una extensión de PostgreSQL que le ayuda a administrar conjuntos de particiones de tablas basadas tanto en series temporales como en series, lo cual incluye la administración automática de la creación de particiones y el mantenimiento del tiempo de ejecución. pg_partman funciona con la partición nativa de PostgreSQL, de modo que los usuarios pueden beneficiarse de mejoras del rendimiento significativas.