Master-Detail. Activities y Fragment en diferentes dispositivos

Hasta el momento hemos visto la aplicación en un emulador de un dispositivo similar al de un teléfono. ¿Qué pasa si probamos con un dispositivo más grande, una tablet, en lugar de emular un teléfono?

Configurando un nuevo dispositivo

En Android Studio: Tools > Android > AVD Manager...

Creamos un nuevo Virtual Device o emulador, mediante un click en "Create Virtual Device":

Seleccionamos una tablet (en este caso una de 9 pulgadas):

Elegimos el Sistema Operativo que tengamos instalado:

Y con un nuevo Next finalizamos el Wizard:

Cerramos el anterior emulador y volvemos a correr la app, nos aparece el launcher: 
Device chooser > Launch emulator > Android virtual device: y en el combo seleccionamos nuestro device recientemente creado.


Actualización de pelìculas (en tablet)

Ahora vemos la explicación que nos deja el javadoc del LibroListActivity:
/**
* An activity representing a list of Peliculas. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a {@link PeliculaDetailActivity} representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
* <p/>
* The activity makes heavy use of fragments. The list of items is a
* {@link PeliculaListFragment} and the item details
* (if present) is a {@link PeliculaDetailFragment}.
* <p/>
* This activity also implements the required
* {@link PeliculaListFragment.Callbacks} interface
* to listen for item selections.
*/
y cómo se visualiza la aplicación Master/Detail en Tablet: al hacer click en un libro de la lista, a la derecha vemos el detalle en el mismo panel...


Mientras que en un dispositivo pequeño se utilizan dos activities, una de master y otra de detalle, en la tablet existe una sola actividad que contiene los fragmentos master y detail en dos paneles.

Para ver cómo se logra esta técnica veamos la clase LibroListActivity, donde se discrimina tablet de teléfono a partir de un control container presente solamente para el layout de las primeras, y se almacena en una variable booleana mTwoPane:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pelicula_app_bar);

    if (findViewById(R.id.pelicula_detail_container) != null) {
        // The detail container view will be present only in the
        // large-screen layouts (res/values-large and
        // res/values-sw600dp). If this view is present, then the
        // activity should be in two-pane mode.
        mTwoPane = true;

        // In two-pane mode, list items should be given the
        // 'activated' state when touched.
        ((PeliculaListFragment) getSupportFragmentManager()
                .findFragmentById(R.id.pelicula_list))
                .setActivateOnItemClick(true);
    }
}

¿Dónde está definido pelicula_detail_container? No en la activity de película del directorio layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        android:id="@+id/fragment1"
        android:name="org.uqbar.peliculasapp.PeliculaListFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
layout/activity_pelicula_list.xml

Sino en la actividad específica del directorio layout-sw600dp (para resoluciones grandes):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp" android:baselineAligned="false"
    android:divider="?android:attr/dividerHorizontal" android:orientation="horizontal"
    android:showDividers="middle" tools:context=".PeliculaListActivity">

    <!--
    This layout is a two-pane layout for the Peliculas
    master/detail flow.
    
    -->

    <fragment android:id="@+id/pelicula_list"
        android:name="org.uqbar.peliculasapp.PeliculaListFragment" android:layout_width="0dp"
        android:layout_height="match_parent" android:layout_weight="1"
        tools:layout="@android:layout/list_content" />

    <FrameLayout android:id="@+id/pelicula_detail_container" android:layout_width="0dp"
        android:layout_height="match_parent" android:layout_weight="3" />

</LinearLayout>

Y cuando el usuario selecciona una película de la lista el comportamiento es diferente en cada dispositivo:
  • en un dispositivo pequeño como un teléfono, se dispara una nueva actividad
  • en un dispositivo más grande como una tablet, la actividad tiene dos fragments, el de la derecha se actualiza con la información de la nueva película
   @Override
    public void onItemSelected(Pelicula pelicula) {
        if (mTwoPane) {
            // In two-pane mode, show the detail view in this activity by
            // adding or replacing the detail fragment using a
            // fragment transaction.
            Bundle arguments = new Bundle();
            arguments.putSerializable(PeliculaDetailFragment.ARG_ITEM_ID, pelicula);
            PeliculaDetailFragment fragment = new PeliculaDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.pelicula_detail_container, fragment)
                    .commit();

        } else {
            // In single-pane mode, simply start the detail activity
            // for the selected item ID.
            Intent detailIntent = new Intent(this, PeliculaDetailActivity.class);
            detailIntent.putExtra(PeliculaDetailFragment.ARG_ITEM_ID, pelicula);
            startActivity(detailIntent);
        }
    }

Pequeño arreglo

Se podrá observar que no aparece el título de la película cuando estamos en modalidad tablet. Esto ocurre porque ese dato está asociado al title de la Activity de Detalle, que no se activa en este caso. Para corregir eso vamos a hacer un pequeño cambio... en el fragment de detalle asignaremos el título de la película a
  • la toolbar de la actividad de detalle
  • y si no la encontramos, significa que seguimos en la list_activity de una tablet, le configuramos la propiedad title
            CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout);
            if (appBarLayout != null) {
                appBarLayout.setTitle(pelicula.getTitulo());
            } else {
                activity.setTitle(pelicula.getTitulo());
            }

Ahora sí cuando el usuario lo selecciona se visualiza el título correspondiente: