Si llevas un tiempo programando en Java recordarás esas decenas de líneas de código de inicialización y aquellos enormes XML de configuración con los que teníamos que lidiar en frameworks como Spring, JPA o JSF antes de que se popularizaran las anotaciones. En Android esa tendencia todavía no ha impuesto, pero os alegrará saber que sí existen librerías externas con las que reducir enormemente el código auxiliar y centrarnos en lo que realmente importa.
La opción más interesante es AndroidAnnotations, ya que es muy completa, no impone penalización alguna en el rendimiento y no nos obliga a que nuestras actividades extiendan de una cierta clase, lo que al carecer Java de herencia múltiple complicaría ligeramente el uso de librerías como ActionBarSherlock. ¿Suena bien? Entonces genera un proyecto con AndroidAnnotations en AndroidKickstarR y sigue leyendo; te aseguro que no te vas a arrepentir.
Asociar un layout a una actividad o fragmento
Sin AndroidAnnotations:
public class Actividad extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.actividad);
}
}
Con AndroidAnnotations podemos usar la anotación @EActivity
(de enhanced activity o actividad mejorada) y pasarle el identificador del layout:
@EActivity(R.layout.actividad)
public class MyActivity extends Activity {}
De forma parecida a las actividades, con la anotación @EFragment
podemos obviar el método onCreateView
del fragmento. Sin AndroidAnnotations:
public class Fragmento extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmento, container, false);
return view;
}
}
Con AndroidAnnotations:
@EFragment(R.layout.fragmento)
public class Fragmento extends Fragment {
}
Inyección de vistas
Sin AndroidAnnotations:
public class Actividad extends Activity {
Button btnGuardar;
EditText txtNombre;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.actividad);
btnGuardar = (Button) findViewById(R.id.btnGuardar);
txtNombre = (EditText) findViewById(R.id.txtNombre);
}
}
Con AndroidAnnotations usaríamos la anotación @ViewById
pasando como parámetro el identificador de la vista
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@ViewById(R.id.btnGuardar)
Button btnGuardar;
@ViewById(R.id.txtNombre)
EditText txtNombre;
}
Si el nombre de la variable es igual al identificador de la vista el parámetro se puede obviar:
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@ViewById Button btnGuardar;
@ViewById EditText txtNombre;
}
También podemos inyectar fragmentos con @FragmentById
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@ViewById Button btnGuardar;
@ViewById EditText txtNombre;
@FragmentById Fragmento fragmento;
}
Manipular las vistas
Si usamos @ViewById
para inyectar la vistas, por la manera que tiene de funcionar AndroidAnnotations, no tendremos todavía las instancias asociadas en onCreate
, por lo que si tenemos que hacer algo con las vistas tendremos que anotar uno o varios métodos con @AfterViews
. Esto, que a alguno le puede parecer un incordio, a mí personalmente me gusta, porque prefiero extraer el resto del código a otros métodos siguiendo el Principio de Responsabilidad Única.
De esta forma un código como
public class Actividad extends Activity {
TextView txtVersion;
TextView txtFecha;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.actividad);
txtVersion = (TextView) findViewById(R.id.txtVersion);
txtFecha = (TextView) findViewById(R.id.txtFecha);
establecerVersion();
establecerFecha();
}
void establecerVersion() {
try {
PackageInfo infoPaquete = getPackageManager().getPackageInfo(getPackageName(), 0);
txtVersion.setText(infoPaquete.versionName);
} catch(NameNotFoundException e) {
e.printStackTrace();
}
}
void establecerFecha() {
DateFormat formato = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
Date fecha = new Date();
String strFecha = formato.format(fecha);
txtFecha.setText(strFecha);
}
}
se convertiría en lo siguiente
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@ViewById TextView txtVersion;
@ViewById TextView txtFecha;
@AfterViews
void establecerVersion() {
try {
PackageInfo infoPaquete = getPackageManager().getPackageInfo(getPackageName(), 0);
txtVersion.setText(infoPaquete.versionName);
} catch(NameNotFoundException e) {
e.printStackTrace();
}
}
@AfterViews
void establecerFecha() {
DateFormat formato = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
Date fecha = new Date();
String strFecha = formato.format(fecha);
txtFecha.setText(strFecha);
}
}
Eventos
Android cuenta con un atributo XML android:onClick
con el que es muy rápido y sencillo asociar un listener a un evento clic. El equivalente en AndroidAnnotations es la anotación @Click
, a la que se le pasa el identificador de la vista a la que se asocia el listener (se puede obviar en caso de que el nombre del método coincida con el identificador de la vista o que sea igual pero con el sufijo Clicked).
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@Click(R.id.button1)
void mostrarVersion() {
// ...
}
@Click
void mostrarFecha() {
// ...
}
@Click
void guardarClicked() {
// ...
}
}
En este caso la ventaja de las anotaciones no es tan obvia; quizás el poder ver la asociación entre la vista y el método en un golpe de vista. Tiene mayor interés el que podamos asociar listeners para unos pocos eventos más de forma igual de sencilla, con @LongClick
para pulsaciones largas o @TextChange
para cambios de texto.
Menús
Con la anotación @OptionsMenu
podemos inflar el XML de menú, ahorrándonos sobreescribir onCreateOptionsMenu
. Con @OptionsItem
asociamos las distintas opciones de menú con distintos listeners, pasando el id de la opción a la anotación (también se puede obviar el id si coincide con el nombre del método, o si es igual pero con el sufijo Selected).
Versión sin anotaciones:
public class Actividad extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.actividad);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.actividad, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.actualizar:
actualizar();
return true;
case R.id.acercaDe:
acercaDeSelected();
return true;
default:
return false;
}
}
void actualizar() {
// ...
}
void acercaDeSelected() {
// ...
}
}
Versión con anotaciones:
@EActivity(R.layout.actividad)
@OptionsMenu(R.menu.actividad)
public class Actividad extends Activity {
@OptionsItem(R.id.actualizar)
void actualizar() {
// ...
}
@OptionsItem
void acercaDeSelected() {
// ...
}
}
Inyección de recursos
Para cada tipo de recurso existe una anotación que permite inyectar dicho recurso en nuestra clase pasándole simplemente el identificador (o sin pasarle nada si el nombre del recurso coincide con el de la variable): AnimationRes
, BooleanRes
, ColorRes
, ColorStateListRes
, DimensionPixelOffsetRes
, DimensionPixelSizeRes
, DimensionRes
, DrawableRes
, HtmlRes
, IntArrayRes
, IntegerRes
, LayoutRes
, MovieRes
, StringArrayRes
, StringRes
, TextArrayRes
, TextRes
.
De esta forma en lugar de:
String nombreApp = getResources().getString(R.string.nombreApp);
simplemente podríamos escribir:
@StringRes String nombreApp;
Además, en el caso concreto de que queramos obtener una cadena HTML de un archivo de recurso y establecerla como texto en un TextView
, podemos llevar a cabo ambos pasos con una simple anotación @FromHtml
.
Hilos de ejecución
¿Harto de usar AsyncTask
o similares para ejecutar un cierto código en un hilo distinto del principal o de interfaz de usuario?
void ejecutarTareaSegundoPlano(URL urls) {
new AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int numUrls = urls.length;
Long tamanyoDescarga = 0;
for (int i = 0; i < numUrls; i++) {
tamanyoDescarga += ClienteHttp.descargar(urls[i]);
publishProgress((int) ((i / (float) numUrls) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progreso) {
actualizarProgreso(progreso[0]);
}
protected void onPostExecute(Long tamanyoDescarga) {
mostrarDialogo("Descargados " + tamanyoDescarga[0] + " bytes");
}
}.execute();
}
¿Qué tal si pudieras usar la anotación @Background
para indicar que un método debe ejecutarse en un hilo en segundo plano y @UiThread
para que se ejecute en el hilo de interfaz de usuario? De esta forma el ejemplo anterior se reduciría a:
@Background
void descargarArchivos(URL urls) {
int numUrls = urls.length;
Long tamanyoDescarga = 0;
for (int i = 0; i < numUrls; i++) {
tamanyoDescarga += ClienteHttp.descargar(urls[i]);
actualizarEstadoDescarga((int) ((i / (float) numUrls) * 100));
}
mostrarDialogoFinDescarga(tamanyoDescarga);
}
@UiThread
void actualizarEstadoDescarga(int progreso) {
actualizarProgreso(progreso);
}
@UiThread
void mostrarDialogoFinDescarga(Long tamanyoDescarga) {
mostrarDialogo("Descargados " + tamanyoDescarga + " bytes");
}
Guardar y recuperar el estado
Esta es otra de esas tareas repetitivas que suponen gran cantidad de código. Comparemos una implementación típica con su correspondiente versión con anotaciones.
public class Actividad extends Activity {
Jugador jugador;
int puntuacion;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if(savedInstanceState != null) {
jugador = (Jugador)savedInstanceState.getSerializable("jugador");
puntuacion = savedInstanceState.getInt("puntuacion");
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("jugador", jugador);
outState.putInt("puntuacion", puntuacion);
}
}
@EActivity(R.layout.actividad)
public class Actividad extends Activity {
@InstanceState
Jugador jugador;
@InstanceState
int puntuacion;
}
Otras funcionalidades de interés
@Trace
nos permite trazar la ejecución de un método escribiendo una entrada en el log cuando se entra y se sale del método.@HierarchyViewerSupport
permite utilizar la herramienta Hierarchy Viewer aunque no dispongamos de un teléfono con permisos de root o de desarrollo@RoboGuice
nos permite integrar AndroidAnnotation con el framework de inyección de dependencias del mismo nombre (basado en Guice, de Google)@OrmLiteDao
nos permite integrar AndroidAnnotation con el framework de mapeo objeto-relacional OrmLite@EView
y@EViewGroup
facilitan la creación de vistas personalizadas@SystemService
nos permite inyectar un servicio del sistema
http://ifttt.com/images/no_image_card.png
0 comentarios:
Publicar un comentario