ActivityLabs

Figura 1

Lecturas previas: Save Project As Escribir en el LogAcitivity Lifecycle Salvar la Instancia

Crear un proyecto nuevo o duplicar el proyecto Saludo como el proyecto ActivityLab utilizando la guía que podéis encontrar en Android Sudio – Save Project As

En este ejercicio hay que sobrecargar en ambas actividades (MainActivity y Saludo) todos los callback del ciclo de vida de una Activity.

Figura 2. Seleccionar los métodos a sobrecargar

Para sobrecargar un método podemos usar el menú de Code->OverrideMethods que nos lanza la pantalla de la Figura 2

Definirás como variables miembro de la clase, los contadores en cada Activity para contabilizar el número de veces que entra en cada callback de esa Activity y mostrarlo por pantalla (Figura 1). (Adicionalmente avisarás mediante escritura en el Log de cuando entra en ejecución cada callback del ciclo de vida.

En cada uno de los métodos sobrecargados simplemente se incrementa el contador apropiado

Gestionar el guardado de los datos del bundle y la recuperación de éste tras una recreación de la Activity.
Ver la entrada “Salvando la instancia


Usando el Objeto Application

El objeto Application engloba al resto de elementos de la arquitectura de la aplicación.

Ahora podemos ver otra forma de almacenar los valores de los contadores en el objeto Application.

Nota: Esta forma de utilizar el objeto Aplication no es correcta del todo, pero me sirve para introducir el objeto Application y los interfaces.

En la practica anterior cada activity define su interfaz de usuario con los campos del interfaz que va a mostrar, también utilizar variables miembro de su clase Activity para almacenar el valor de los contadores e incrementarlos en los métodos sobrecargados del ciclo de vida.

Aquí plantemos crear un nuevo fichero de layout xml y reutilizarlo en ambas actividades.

Además, hemos pensado que sería bueno crear una clase llamada Counters que se encargará de ofrecer los métodos set y get para los contadores. Es decir tenemos una clase Counters con los contadores como datos miembro ofreciendo métodos set y get para recuperarlos e incrementarlos. La idea es que cada Activity podrá tener su objeto de tipo Counters para llevar la cuenta de los callbacks a los que ha entrado.

Esto de entrada nos plantea un problema que vamos a resolver de dos formas, pero ninguna de las dos formas es totalmente correcta, puesto que utiliza como elemento de persistencia de datos (dónde se almacenan los datos para distintas activities) al objeto Application, y esto no debe de hacerse por motivos que se explican aquí (Understanding the Android Application Class), esto tiene riesgo.

La primera idea es instanciar un objeto de la clase Counters en cada una de las actividades, pero eso no funcionará, ya que cuando el dispositivo gira, la actividad es recreada, destruyéndose previamente la misma, con lo que los contadores vuelven a estar a cero, puesto que la nueva Activity crea un objeto Counters nuevo que tiene sus contadores a cero. Necesitaríamos una persistencia de los contadores que perviva a la destrucción de la Activity. Soluciones:

  • La correcta es la de la práctica anterior, es decir Salvar la Instancia
  • Otra solución correcta sería utilizar shared preferences o cualquier otra forma de persistencia (ficheros, base de datos, .. .)
  • Utilizar la clase Application como contenedora de los contadores. Esta es la que vamos a utilizar para conocer la clase Application pero como dice la cita no es la mejor forma…

However, you should never store mutable instance data inside the Application object because if you assume that your data will stay there, your application will inevitably crash at some point with a NullPointerException. The application object is not guaranteed to stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.

CodePath*

A pesar de ello si la Applicataion, que almacena los datos persistentes, no es matada, la técnica funciona.

Este ejemplo sirve para ver también un par de cosas:

  1. Ver cómo acceder al Interfaz de usuario desde una clase que no es la Activity, pasando una referencia a la activity como parámetro a un método de una clase
  2. Hacer eso mismo pero bien, es decir, utilizando una Interfaz

Primera versión

En la primera versión creamos un layout xml utilizando un LinearLayout vertical y LinearLayouts horizontales para cada contador. Este Layout se incluye con el contenedor <include> tanto en la MainActivity como en la Acitivity Saludo.

Creamos una clase propia para los contadores, esta es la que los mantiene, pero también, en esta versión, hacemos que desde la misma clase se actualize el interfaz de usuario de la actividad que corresponda.

Sobrecargamos la Clase Application creando una clase llamada MyApplication que hereda de Application y en ella instanciamos dos objetos de tipo Counters, uno para los contadores de la Main Activity y otro para los contadores de Saludo Acitivity.

Cuando sobrecargamos la clase Application, debemos hacer que nuestra Application (en el manifest) sea la que hemos sobreccargado, esto se hace poniendo el atributo name a la misma.

El Layout sería:

Y su código:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview1"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnCreate #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnCreate"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview2"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnStart #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnStart"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview3"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnResume #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnResume"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview4"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnRestart #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnRestart"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview5"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnPause #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnPause"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview6"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnStop #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnStop"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textview7"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="OnDestroy #"
            android:textAlignment="textEnd" />

        <TextView
            android:id="@+id/tvOnDestroy"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:inputType="number" />
    </LinearLayout>

</LinearLayout>

La clase Counters sería:

public class Counters {

    int mOnCreate;
    int mOnStart;
    int mOnResume;
    int mOnRestart;
    int mOnPause;
    int mOnStop;
    int mOnDestroy;

    public void incOnCreate() {
        this.mOnCreate += 1;
    }
    public int getOnCreate(){
       return mOnCreate;
    }

    public void incOnStart() {
        this.mOnStart += 1;
    }
    public int getOnStart(){
        return mOnStart;
    }

    public void incOnResume() {
        this.mOnResume +=1;
    }
    public int getOnResume(){
        return mOnResume;
    }

    public void incOnRestart() {
        this.mOnRestart +=1;
    }
    public int getOnRestart() {return mOnRestart;}

    public void incOnPause() {
        this.mOnPause +=1;
    }
    public int getOnPause() {return mOnPause;}

    public void incOnStop() {
        this.mOnStop +=1;
    }
    public int getOnStop() {return mOnStop;}

    public void incOnDestroy() {this.mOnDestroy +=1;}
    public int getOnDestroy() {return mOnDestroy;}

    public void UpdateUI(Context mContext){
        //OnCreate
        TextView tvOnCreate = (TextView) ((Activity)mContext).findViewById(R.id.tvOnCreate);
        tvOnCreate.setText(Integer.toString(mOnCreate));
        //OnStart
        TextView tvOnStart = (TextView) ((Activity)mContext).findViewById(R.id.tvOnStart);
        tvOnStart.setText(Integer.toString(mOnStart));
        //OnResume
        TextView tvOnResume = (TextView) ((Activity)mContext).findViewById(R.id.tvOnResume);
        tvOnResume.setText(Integer.toString(mOnResume));
        //OnRestart
        TextView tvOnRestart = (TextView) ((Activity)mContext).findViewById(R.id.tvOnRestart);
        tvOnRestart.setText(Integer.toString(mOnRestart));
        //OnPause
        TextView tvOnPause = (TextView) ((Activity)mContext).findViewById(R.id.tvOnPause);
        tvOnPause.setText(Integer.toString(mOnPause));
        //OnStop
        TextView tvOnStop = (TextView) ((Activity)mContext).findViewById(R.id.tvOnStop);
        tvOnStop.setText(Integer.toString(mOnStop));
        //OnDestroy
        TextView tvOnDestroy = (TextView) ((Activity)mContext).findViewById(R.id.tvOnDestroy);
        tvOnDestroy.setText(Integer.toString(mOnDestroy));
    }
}

Como vemos los contadores se definen por defecto private y por tanto ofrecemos métodos inc…. para incrementar el contador y métodos get… para obtener su valor.

La clase Application sería:

public class MyApplication extends Application {

    public Counters countersMain, countersSaludo;

    @Override
    public void onCreate() {
        super.onCreate();

        countersMain = new Counters();
        countersSaludo = new Counters();
    }

}

y su propiedad name en el manifest se ve aqui:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="saludoactivitylabapp">

    <application
        android:name="com.example.miguel.saludoactivitylabapp.MyApplication"
        android:label="@string/app_name"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:roundIcon="@mipmap/ic_tiger_round">
        <activity android:name="com.example.miguel.saludoactivitylabapp.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.miguel.saludoactivitylabapp.Saludo"
            android:parentActivityName="com.example.miguel.saludoactivitylabapp.MainActivity"></activity>
    </application>

</manifest>

El cógido completo de esta primera versión lo podéis descargar del botón…

Segunda Versión

En esta versión utilizaremos un interfaz para que cada activitiy la implemente para presentar el interfaz de usuario. Aunque aquí utilizamos un Layout que se incluye con <include> y por tanto los nombres de las views son los mismos en las dos activities, de esta forma, usando el interfaz, cada activity podría instanciar con nombres diferentes las vistas ( y poner otra distribución)…

El interfaz sería:

public interface ContersUI {
    void  UpdateCountersUI();
}

Y la clase Counters ya no tendría el método UpdateUI(), quedando así:

public class Counters {

    int mOnCreate;
    int mOnStart;
    int mOnResume;
    int mOnRestart;
    int mOnPause;
    int mOnStop;
    int mOnDestroy;

    public void incOnCreate() {
        this.mOnCreate += 1;
    }
    public int getOnCreate(){
       return mOnCreate;
    }

    public void incOnStart() {
        this.mOnStart += 1;
    }
    public int getOnStart(){
        return mOnStart;
    }

    public void incOnResume() {
        this.mOnResume +=1;
    }
    public int getOnResume(){
        return mOnResume;
    }

    public void incOnRestart() {
        this.mOnRestart +=1;
    }
    public int getOnRestart() {return mOnRestart;}

    public void incOnPause() {
        this.mOnPause +=1;
    }
    public int getOnPause() {return mOnPause;}

    public void incOnStop() {
        this.mOnStop +=1;
    }
    public int getOnStop() {return mOnStop;}

    public void incOnDestroy() {this.mOnDestroy +=1;}
    public int getOnDestroy() {return mOnDestroy;}

}

De forma que las Activities deben implementar la interfaz y cuando en un callbakc del lifecycle se llame a actualizar el interfaz haremos:

public class MainActivity extends AppCompatActivity implements View.OnClickListener, ContersUI{

....

public class Saludo extends AppCompatActivity implements ContersUI{

...


        // Update the appropriate count variable
        mCounters.incOnCreate();
        Log.d(TAG, "Entering OnCreate(), increasing counter, set to " + Integer.toString(mCounters.getOnCreate()));
        UpdateCountersUI();



    @Override
    public void UpdateCountersUI() {
        //OnCreate
        TextView tvOnCreate = (TextView) findViewById(R.id.tvOnCreate);
        tvOnCreate.setText(Integer.toString(mCounters.getOnCreate()));
        //OnStart
        TextView tvOnStart = (TextView) findViewById(R.id.tvOnStart);
        tvOnStart.setText(Integer.toString(mCounters.getOnStart()));
        //OnResume
        TextView tvOnResume = (TextView) findViewById(R.id.tvOnResume);
        tvOnResume.setText(Integer.toString(mCounters.getOnResume()));
        //OnRestart
        TextView tvOnRestart = (TextView) findViewById(R.id.tvOnRestart);
        tvOnRestart.setText(Integer.toString(mCounters.getOnRestart()));
        //OnPause
        TextView tvOnPause = (TextView) findViewById(R.id.tvOnPause);
        tvOnPause.setText(Integer.toString(mCounters.getOnPause()));
        //OnStop
        TextView tvOnStop = (TextView) findViewById(R.id.tvOnStop);
        tvOnStop.setText(Integer.toString(mCounters.getOnStop()));
        //OnDestroy
        TextView tvOnDestroy = (TextView) findViewById(R.id.tvOnDestroy);
        tvOnDestroy.setText(Integer.toString(mCounters.getOnDestroy()));
    }

El código de esta versión lo puedes descargar del botón.