Creando actividades en Mis Lugares

La actividad VistaLugarActivity nos mostrará la información que hemos almacenado de un determinado lugar y nos permitirá realizar una gran cantidad de acciones sobre ese lugar (mostrar en mapa, llamar por teléfono, compartir en redes sociales, etc.). Desde esta actividad podremos cambiar algunos valores de modificación frecuente. En concreto: la valoración, la fecha de visita y la fotografía.

Ejercicio: Creación de Ia actividad VistaLugarActivity

1.     Abre el proyecto MisLugares.

2.     Descarga www.androidcurso.com/images/dcomg/mislugares.zip y descomprime en una carpeta. Copia los gráficos que encontrarás en el portapapeles y pegalos dentro de res/drawable en el explorador del proyecto.

3.    Crea un nuevo layout y llámalo vista_lugar.xml. Copia el siguiente código para usarlo como base:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
        <TextView
            android:id="@+id/nombre"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:text="Nombres del lugar"
            android:textAppearance="?android:attr/textAppearanceLarge" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
            <ImageView
                android:id="@+id/logo_tipo"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:contentDescription="logo del tipo"
                android:src="@drawable/otros" />
            <TextView
                android:id="@+id/tipo"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="tipo del lugar" />
        </LinearLayout>
      …
        <RatingBar
            android:id="@+id/valoracion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:rating="3" />
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
            <ImageView
                android:id="@+id/foto"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:contentDescription="fotografía"
                android:src="@drawable/foto_epsg" />
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right" >
            <ImageView
                android:id="@+id/camara"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:contentDescription="logo cámara"
                android:src="@android:drawable/ic_menu_camera" />
            <ImageView
                android:id="@+id/galeria"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:contentDescription="logo galería"
                android:src="@android:drawable/ic_menu_gallery" />
            </LinearLayout>
        </FrameLayout>
    </LinearLayout>
</ScrollView>
 

Observa como el elemento exterior es un ScrollView. Esto es conveniente cuando pensemos que los elementos de layout no cabrán en la pantalla. En este caso el usuario podrá desplazar verticalmente el layout arrastrando con el dedo. Dado que algunas pantallas pueden ser muy pequeñas la mayoría de diseños han de incorporar un ScrollView.

Dentro de este elemento tenemos un LinearLayout para organizar las vistas verticalmente. La primera vista es un TextView cuyo id es nombre. Se ha asignado un valor para text inicial, que será reemplazado por el nombre del lugar. La única función que tiene este texto inicial es ayudarnos en el diseño. El siguiente elemento es un LinearLayout vertical que contiene un ImageView y un TextView. Este elemento será utilizado para indicar el tipo de lugar.

Los puntos suspensivos indican el lugar donde tendrás que insertar el resto de elementos que no se han incluido (dirección, teléfono, etc.). El siguiente elemento que se incluye es un RatingBar donde podremos introducir una valoración del lugar. El último elemento es un FrameLayout que permite superponer varias vistas. Se dibujarán en el orden en que las indicamos. En el fondo se dibuja un ImageView con una fotografía de la EPSG. El atributo adjustViewBounds indica que la imagen sea escalada para ocupar todo el espacio disponible. Sobre la fotografía se dibujará un LinearLayout con dos ImageView. Estos botones permitirán cambiar la fotografía desde la cámara o desde la galería.

4.     Reemplaza los puntos suspensivos por los elementos que faltan para obtener la apariencia mostrada al principio de este punto. Para cada elemento haz una copia del <LinearLayout> donde se introduce el tipo de lugar y reemplaza los valores que consideres necesarios. Utiliza los recursos del sistema mostrados en la siguiente tabla. Identifica dada TextView con el id que se indica.

Recurso para ImageView

Id para TextView

@android:drawable/ic_menu_myplaces

@+id/direccion

@android:drawable/ic_menu_call

@+id/telefono

@android:drawable/ic_menu_mapmode

@+id/url

@android:drawable/ic_menu_info_details

@+id/comentario

@android:drawable/ic_menu_my_calendar

@+id/fecha

@android:drawable/ic_menu_recent_history

@+id/hora

5.     Crea la clase VistaLugarActivity y reemplaza el código por el siguiente:

public class VistaLugarActivity extends AppCompatActivity {
   private RepositorioLugares lugares;
   private CasosUsoLugar usoLugar;
   private int pos;
   private Lugar lugar;
   private VistaLugarBinding binding;

   @Override protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      binding = VistaLugarBinding.inflate(getLayoutInflater());
      setContentView(binding.getRoot());
      Bundle extras = getIntent().getExtras();
      pos = extras.getInt("pos", 0);
      lugares = ((Aplicacion) getApplication()).lugares;
      usoLugar = new CasosUsoLugar(this, lugares);
      lugar = lugares.elemento(pos);
      actualizaVistas();
   }
   
   public void actualizaVistas() {
      lugar = lugares.elemento(pos);
      binding.nombre.setText(lugar.getNombre());
      binding.logoTipo.setImageResource(lugar.getTipo().getRecurso());
      binding.tipo.setText(lugar.getTipo().getTexto());
      binding.direccion.setText(lugar.getDireccion());
      binding.telefono.setText(Integer.toString(lugar.getTelefono()));
      binding.url.setText(lugar.getUrl());
      binding.comentario.setText(lugar.getComentario());
      binding.fecha.setText(DateFormat.getDateInstance().format(
            new Date(lugar.getFecha())));
      binding.hora.setText(DateFormat.getTimeInstance().format(
            new Date(lugar.getFecha())));
      binding.valoracion.setRating(lugar.getValoracion());
      binding.valoracion.setOnRatingBarChangeListener(
            new OnRatingBarChangeListener() {
                @Override public void onRatingChanged(RatingBar ratingBar, 
                                         float valor, boolean fromUser) {
                   lugar.setValoracion(valor);
                }
      });
   }
}  
class VistaLugarActivity : AppCompatActivity() {
   lateinit var lugares: RepositorioLugares 
   lateinit var usoLugar: CasosUsoLugar
   var pos = 0
   lateinit var lugar: Lugar
   lateinit var binding: VistaLugarBinding

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      binding = VistaLugarBinding.inflate(layoutInflater)
      setContentView(binding.root)
      pos = intent.extras?.getInt("pos", 0) ?: 0
      lugares = (application as Aplicacion).lugares
      usoLugar = CasosUsoLugar(this, lugares)
      actualizaVistas()
   }

   fun actualizaVistas(){
      lugar = lugares.elemento(pos)
      binding.nombre.text = lugar.nombre
      binding.logoTipo.setImageResource(lugar.tipoLugar.recurso)
      binding.tipo.text = lugar.tipoLugar.texto
      binding.direccion.text = lugar.direccion
      binding.telefono.text = Integer.toString(lugar.telefono)
      binding.url.text = lugar.url
      binding.comentario.text = lugar.comentarios
      binding.fecha.text = DateFormat.getDateInstance().format(
                                                        Date(lugar.fecha))
      binding.hora.text = DateFormat.getTimeInstance().format(
                                                        Date(lugar.fecha))
      binding.valoracion.rating = lugar.valoracion
      binding.valoracion.setOnRatingBarChangeListener { 
         ratingBar, valor, fromUser -> lugar.valoracion = valor
      }
   }
}
  

Nota sobre Java/Kotlin: Pulsa Alt-Intro  para que automáticamente se añadan los imports con los  paquetes que faltan. Dos clases aparecen en varios paquetes, selecciona java.text.DateFormat y java.util.Date.


Se definen tres variables globales para que se pueda acceder a ellas desde cualquier método de la clase: lugares corresponde con el repositorio de lugares, cuya referencia se obtiene desde Application, pos es la posición del elemento que vamos a visualizar y lugar es el elemento en sí; usoLugar nos permite acceder a los casos de usos definidos para un lugar como se explicó en el apartado anterior y finalmente
permite acceder a las vistas del layout (ver apartado View Binding del capítulo 2).

El método onCreate() será ejecutado cuando se cree la actividad Tras llamar al super se obtiene el objeto binding y se asocia el layout de la actividad con setContentView(). A continuación, obtiene el repositorio de lugares desde Aplicacion y se averigua la posición del lugar a mostrar, que ha sido pasado en un extra. A partir de la posición obtenemos el objeto lugar a mostrar.  Se crea un objeto para acceder a los casos de uso y se llama a actualizaVistas() donde se inicializa el contenido de cada vista según el lugar a mostrar.
Observa cómo se obtiene una referencia a cada uno de los elementos de la vista utilizando el objeto binding.

y en el tenemos que asociar un layout (setContentView(R.layout.vista_lugar)) e inicializar todos sus valores. A continuación, se averigua la posición del lugar a mostrar, que ha sido pasado en un extra. A partir de este id obtenemos el objeto Lugar a mostrar.

 Al final se realiza una acción especial con el objeto valoracion, utilizando el método setOnRatingBarChangeListener() para asignarle un escuchador de eventos al RatingBar que es creado allí mismo. Este escuchador de evento se activará cuando el usuario modifique la valoración. El código a ejecutar consiste en modificar la propiedad Valoracion() del objeto lugar con la nueva valoración.

6.     Abre la clase TipoLugar y asigna un recurso drawable a cada tipo de lugar. Puedes utilizar la opción de autocompletar, es decir, escribe R.drawable. y espera a que el sistema te dé una alternativa.

public enum TipoLugar {
    OTROS ("Otros", R.drawable.otros),
    RESTAURANTE ("Restaurante", R.drawable.restaurante),
    BAR("Bar", R.drawable.bar),
    …

7.     Añade en MainActivity el siguiente método:

public void lanzarVistaLugar(View view){
  usoLugar.mostrar(0);
} 
fun lanzarVistaLugar(view: View? = null) {
    usoLugar.mostrar(0) 
} 

Este método lanzará la actividad VistaLugarActivity pasándole como posición del lugar a visualizar siempre 0. Más adelante mostraremos algunas alternativas para que el usuario pueda seleccionar el lugar a mostrar.

8.     En el método onOptionsItemSelected() de la actividad MainActivity añade el siguiente código:

@Override public boolean onOptionsItemSelected(MenuItem item) {
   int id = item.getItemId();
   …
   if (id == R.id.menu_buscar) {
      lanzarVistaLugar(null);
      return true;
   }
   …
} 
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
      …
      R.id.menu_buscar -> {
         lanzarVistaLugar()
         true;
      }

9.     Ejecuta la aplicación. Aparecerá un error cuando selecciones Buscar. Siempre que aparezca un error en ejecución es el momento de visualizar el LogCat. No es sencillo analizar la información que se muestra, pero es muy importante que te acostumbres a buscar la causa del problema en el LogCat. En este caso la información clave se muestra a continuación:

>

10.     Para resolver el error en ejecución registra la nueva actividad en AndroidManifest.xml

11.     Ejecuta la aplicación y verifica que cuando seleccionas el icono buscar se arranca una actividad que muestra el primer lugar.

Ejercicio: Un cuadro de dialogo para indicar el id de lugar

Tras realizar el ejercicio anterior comprobarás que siempre se visualiza el lugar con posición 0. En este ejercicio vamos a introducir un cuadro de dialogo que permita introducir al usuario el id que desea visualizar.

Ha de quedar claro que esta es la forma más correcta de diseñar el interfaz de usuario. En la siguiente unidad reemplazaremos este cuadro de dialogo por un RecyclerView.

1.      Abre la clase MainActivity del proyecto MisLugares.

2.      Reemplaza el método por el lanzar LanzaVistaLugar() siguiente:

public void lanzarVistaLugar(View view){
    final EditText entrada = new EditText(this);
    entrada.setText("0");
    new AlertDialog.Builder(this)
       .setTitle("Selección de lugar")
       .setMessage("indica su id:")
       .setView(entrada)
       .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int whichButton) {
               int id = Integer.parseInt (entrada.getText().toString());
             usoLugar.mostrar(id);
           }})
       .setNegativeButton("Cancelar", null)
       .show();
} 
fun lanzarVistaLugar(view: View? = null) {
   val entrada = EditText(this)
   entrada.setText("0")
   AlertDialog.Builder(this)
      .setTitle("Selección de lugar")
      .setMessage("indica su id:")
      .setView(entrada)
      .setPositiveButton("Ok")  { dialog, whichButton ->
         val id = parseInt(entrada.text.toString())
          usoLugar.mostrar(id);
      }
      .setNegativeButton("Cancelar", null)
      .show()
} 

  Nota sobre Java/Kotlin: En Java es posible crear un objeto sin que este disponga de un identificador de objeto. Este tipo de objeto se conoce como objeto anónimo. El código mostrado a continuación a la derecha es equivalente al de la izquierda.

Clase objeto = new Clase();         new Clase().metodo();
        objeto.metodo(); 
 objeto: Clase = Clase()             Clase().metodo()
        objeto.metodo(); 

Un objeto anónimo no tiene identificador por lo que solo puede ser usado donde se crea. En el método anterior se ha creado un objeto anónimo de la clase AlertDialog.Builder.Observa cómo no se llama a un método, sino a una cadena de métodos. Esto es posible porque los métodos de la clase AlertDialog.Builder  retornan el objeto  que estamos creando. Por lo tanto, cada método es aplicado al objeto devuelto por el método anterior.

En Android puedes usar la clase AlertDialog para crear un cuadro de dialogo configurable. Si te fijas en la captura anterior están formados por cuatro elementos, de arriba abajo: título, mensaje, vista y botones. Estos elementos pueden ser configurados mediante los método setTitle(), setMessage(), setView(), setPositiveButton() y setNegativeButton().

La vista que se utiliza en este diálogo es un EditText, inicializado con un texto. En caso de necesitar varias entradas se puede crear una vista de tipo layout, que contendría estas entradas. Se han introducido dos botones, indicando el texto del botón y un escuchador de evento que será llamado cuando se pulse el botón. Finalmente se llama al método show() para que se visualice el cuadro de diálogo.

3.      Verifica que funciona correctamente. Pero cuidado, no se verifica que el id sea válido, por lo que ocurrirá un error si es incorrecto.

 Práctica: Ocultar elementos en VistaLugarActivity

En ocasiones no se dispondrá de parte la información de un lugar. En estos casos, puede resultar más conveniente desde un punto de vista estético, no mostrar campos sin información en VistaLugarActivity. Por ejemplo, si el campo de teléfono es igual a 0, podríamos usar el siguiente código para que no se muestre:

if (lugar.getTelefono() == 0) {
   binding.barraTelefono.setVisibility(View.GONE);
} else {
   binding.barraTelefono.setVisibility(View.VISIBLE);
   binding.telefono.setText(Integer.toString(lugar.getTelefono()));
} 
if (lugar.telefono == 0) {
   binding.barraTelefono.setVisibility(View.GONE)
} else {
   binding.barraTelefono.setVisibility(View.VISIBLE)
   binding.telefono.text = Integer.toString(lugar.telefono)
} 

Para ocultarlo, en el layout telefono, ponemos el valor propiedad visibility al valor GONE Este atributo es aplicado a cualquier tipo de vista. Otros posibles valores para este atributo son VISIBLE e INVISIBLE. Tanto con GONE como con INVISIBLE la vista no se verá. Pero con INVISIBLE el espacio ocupado por la vista se mantiene, mientras que con GONE este espacio se elimina.

Trata de realizar un proceso similar a este para los campos dirección, telefono, url y comentario. Para verificar si un  String es vacío puedes usar el método isEmpty().

Ejercicio: Añadir una barra de acciones a VistaLugarActivity

En este ejercicio vamos a añadir a la actividad un menú a la barra de acciones similar al que se muestra a continuación:

1.     En primer lugar crea el fichero res/menu/vista_lugar.xml que contendrá las acciones a mostrar. Para ello pulsa con el botón derecho sobre la carpeta res/menu y crea el fichero vista_lugar.

2.     Reemplaza su contenido por el siguiente código:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/accion_compartir"
        android:title="compartir"
        android:icon="@android:drawable/ic_menu_share"
        android:orderInCategory="10"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/accion_llegar"
        android:title="cómo llegar"
        android:icon="@android:drawable/ic_menu_directions"
        android:orderInCategory="20"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/accion_editar"
        android:title="editar"
        android:icon="@android:drawable/ic_menu_edit"
        android:orderInCategory="30"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/accion_borrar"
        android:title="borrar"
        android:icon="@android:drawable/ic_menu_delete"
        android:orderInCategory="40"
        app:showAsAction="ifRoom"/>
</menu> 

3.     En la clase VistaLugarActivity añade los siguientes métodos:

@Override public boolean onCreateOptionsMenu(Menu menu) {
   getMenuInflater().inflate(R.menu.vista_lugar, menu);
   return true;
}

@Override public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
   case R.id.accion_compartir:
      return true;
   case R.id.accion_llegar:
      return true;     
   case R.id.accion_editar:
      return true;
   case R.id.accion_borrar:
      usoLugar.borrar(pos);
      return true;
   default:
      return super.onOptionsItemSelected(item);
   }
} 
override fun onCreateOptionsMenu(menu: Menu): Boolean {
   menuInflater.inflate(R.menu.vista_lugar, menu)
   return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   when (item.getItemId()) {
      R.id.accion_compartir -> return true
      R.id.accion_llegar -> return true
      R.id.accion_editar -> return true
      R.id.accion_borrar -> {
         usoLugar.borrar(pos)
         return true
      }
      else -> return super.onOptionsItemSelected(item)
   }
} 

4.     Añade a CasosUsoLugar:

public void borrar(final int id) {
   lugares.borrar(id) 
   actividad.finish();
} 
fun borrar(id: Int) {
   lugares.borrar(id)
   actividad.finish()
} 

5.     Ejecuta la aplicación y borra un lugar. Verifica que, si tratas de visualizar el mismo id ahora se muestra el siguiente lugar. Más adelante trataremos de actualizar la lista, tras borrar un lugar.

 Práctica: Un cuadro de diálogo para confirmar el borrado

Un usuario podría pulsar por error el botón de borrar, por lo que sería muy conveniente pedir una confirmación antes de borrar.

1.     En el método crea un cuadro de dialogo siguiendo el esquema planteado en el ejercicio anterior. Puede ser similar al siguiente.

Creando la actividad EdicionLugarActivity

En este apartado crearemos otra actividad en la aplicación Mis Lugares, EdicionLugarActivity. Esta actividad nos permitirá modificar la mayoría de valores asignados a un lugar (se excluyen los valores que se modifican desde VistaLugarActivity: Valoración, fecha y foto):

Práctica: Creación de la actividad EdicionLugarActivity

1.     En el proyecto MisLugares verifica que existe el layout edicion_lugar.xml. En caso contrario realiza la práctica “Creación de Mis Lugares y formulario de edición”.

2.     Crea la clase EdicionLugarActivity y haz  que extienda AppCompatActivity. Copia en esta clase los atributos y los métodos onCreate() y actualizaVistas() de la clase  VistaLugarActivity.

3. Reemplaza la VistaLugarBinding por EdicionLugarBinding. De esta forma cargaremos el layout correspondiente a esta actividad

4.     El paso de parámetros para obtener pos y lugar puede realizarse de la misma forma.

5     Si has realizado la práctica “Ocultar elementos en VistaLugarActivity”, has de eliminar el código introducido. Por ejemplo, en el caso del campo del teléfono elimina el código tachado:

if (lugar.getTelefono() == 0) {
   binding.barraTelefono.setVisibility(View.GONE);
} else {
   fbinding.barraTelefono.setVisibility(View.VISIBLE);
   binding.telefono.setText(Integer.toString(lugar.getTelefono()));
} 

6.    Puedes eliminar el resto del código de este método, que hace referencia a logoTipo, tipo, fechahora y valoración.

7.    Crea un caso de uso, con la función editar(pos) que abra la actividad que acabas de crear. Usa mostrar(pos) como referencia.

8.     En la clase VistaLugarActivity  dentro del método onOptionsItemSelected(), añade el código necesario para que se llame a esta función.

9.     Ejecuta el proyecto. Pero antes, piensa si falta alguna acción por realizar. Si la aplicación falla abre el Logcat y comprueba el error que has cometido. Si funciona correctamente, podrás comprobar que los valores pueden ser editados, pero no son almaceados. Lo haremos más adelante.

Ejercicio: Inicializar el Spinner en EdicionLugarActivity

Como has podido verificar en la ejecución anterior el Spinner (lista desplegable) no muestra ningún valor. En este ejercicio trataremos que funcione adecuadamente:

1.     Añade el siguiente código el método actualizaVistas():

ArrayAdapter adaptador = new ArrayAdapter(this,
         android.R.layout.simple_spinner_item, TipoLugar.getNombres());
adaptador.setDropDownViewResource(android.R.layout.
         simple_spinner_dropdown_item);
binding.tipo.setAdapter(adaptador);
binding.tipo.setSelection(lugar.getTipo().ordinal()); 
val adaptador = ArrayAdapter(this,
   android.R.layout.simple_spinner_item,
   lugar.tipoLugar.getNombres()
)
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.tipo.adapter = adaptador
binding.tipo.setSelection(lugar.tipoLugar.ordinal) 

Para inicializar los valores que puede tomar un Spinner necesitamos una clase especial conocida como Adapter. Esta clase se estudiará en la siguiente unidad. De momento solo adelantamos que un Adapterva a crear una lista de vistas, inicializándolas con unos valores determinados. La clase ArrayAdapter<String> es un tipo de Adapter que permite inicializar sus valores a partir de un array de String. Su constructor necesita tres parámetros: un contexto (usamos la actividad actual), una vista para mostrar elemento (usamos un vista definida en el sistema) y un array de String. Para el último parámetro necesitamos un array con todos los valores que puede tomar el enumerado TipoLugar. Para obtener este array se define un nuevo método que se muestra a continuación.

El siguiente método, setDropDownViewResource(), permite indicar una vista alternativa que se usará cuando se despliegue el Spinner. En la versión 4.x esta vista es un poco más grande que la usada en el método anterior para poder seleccionarla cómodamente con el dedo. En la versión 2.x muestra círculos seleccionables a la derecha de cada elemento. Este código concluye asignando el adaptador al Spinner y poniendo un valor inicial según el tipo actual de lugar.

2.     Añade el siguiente método a la clase TipoLugar:

public static String[] getNombres() {
   String[] resultado = new String[TipoLugar.values().length];
   for (TipoLugar tipo : TipoLugar.values()) {
      resultado[tipo.ordinal()] = tipo.texto;
   }
   return resultado;
} 
fun getNombres(): Array<String?> {
   val resultado = arrayOfNulls<String>(TipoLugar.values().size)
   for (tipo in TipoLugar.values()) {
      resultado[tipo.ordinal] = tipo.texto
   }
   return resultado
} 

3.    Ejecuta la aplicación y verifica que la lista desplegable funciona correctamente..

Práctica: Añadir una barra de acciones a EdicionLugarActivity

En esta práctica vamos a añadir a la actividad un menú en la barra de acciones similar al que se muestra a continuación:

1.     Crea un nuevo recurso de menú con las opciones que se indican.

2.     Asocia este menú a la actividad EdicionLugarActivity con el método onCreateOptionsMenu().<

3.     Crea el método onOptionsItemSelected() de manera que cuando se seleccione la acción Guardar se ejecute el siguiente código:

lugar.setNombre(binding.nombre.getText().toString());
lugar.setDireccion(binding.direccion.getText().toString());
lugar.setTipo(TipoLugar.values()[binding.tipo.getSelectedItemPosition()]);
lugar.setTelefono(Integer.parseInt(binding.telefono.getText().toString()));
lugar.setUrl(binding.url.getText().toString());
lugar.setComentario(binding.comentario.getText().toString());
usoLugar.guardar(pos, lugar);
finish(); 
val nuevoLugar = Lugar(binding.nombre.text.toString(),
        binding.direccion.text.toString(), lugar.posicion, 
        TipoLugar.values()[binding.tipo.selectedItemPosition],
        lugar.foto, Integer.parseInt(binding.telefono.text.toString()),
        binding.url.text.toString(), binding.comentario.text.toString(),
        lugar.fecha, lugar.valoracion)
usoLugar.guardar(pos, nuevoLugar)
finish() 

4.    Cuando se seleccione la acción cancelar, simplemente se saldrá de la actividad.

5.    Añade a CasosLugar la siguiente función:

public void guardar(int id, Lugar nuevoLugar) {
   lugares.actualiza(id, nuevoLugar);
} 
fun guardar(id: Int, nuevoLugar: Lugar) {
   lugares.actualiza(id, nuevoLugar)
} 

6.    Ejecuta la aplicación. Modifica algún lugar y pulsa en Guardar. Al regresar a la actividad anterior los valores permanecen sin variación. Sin embargo si pulsas la tecla volver y entras a visualizar el mismo lugar, los cambios sí que son actualizados. ¿Qué puede estar pasando?

Ejercicio: Refrescar VistaLugarActivity tras entrar en  EdicionLugarActivity

Parece que al regresar a VistaLugarActivity  desde EdicionLugarActivity no estamos indicando que vuelva a obtener los datos mostrados en las vistas. Para actualizar estos valores puedes hacer los siguientes pasos:

1.     Añade el código subrayado en VistaLugarActivity la siguiente variable:

ActivityResultLauncher edicionLauncher =registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback() {
           @Override
           public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == Activity.RESULT_OK) {
                 actualizaVistas();
              }
           }
        }); 
var edicionLauncher = registerForActivityResult(StartActivityForResult()){
   result -> if (result.resultCode == Activity.RESULT_OK) {
               actualizaVistas();
             }
}

Como vimos en el apartado Comunicación entre Actividades, edicionLauncher nos va a permitir lanzar una actividad, de forma que cuando termine podemos ejecutar un código. En este caso llamamos a actualizaVistas(). Lo que queremos hacer es que una vez que regresamos de la actividad EdicionLugarActivity, actualizar los valores de las vistas y forzar al sistema a que repinte las vistas.

2.     En el método onOptionsItemSelected() añade el código subrayado:

…
case R.id.accion_editar:
   usoLugar.editar(pos, edicionLauncher);
   return true;
…
R.id.accion_editar -> {
   usoLugar.editar(pos, edicionLauncher)
   return true
}

3.     Añade el código subrayado en CasosUsoLugar:
 

…
public void editar(int pos, ActivityResultLauncher launcher) {
   Intent i = new Intent(actividad, EdicionLugarActivity.class);
   i.putExtra("pos", pos);
   launcher.launch(i);
}
…
fun editar(pos: Int, launcher: ActivityResultLauncher) {
   val i = Intent(actividad, EdicionLugarActivity::class.java)
   i.putExtra("pos", pos)
   launcher.launch(i)
}
4.     Como vimos en el apartado Comunicación entre Actividades, al salir de una actividad el atributo resultCode puede tener dos posibles valores RESULT_CANCELED y RESULT_OK. Como puedes ver en el código del paso 1, solo nos interesa actualizar las vistas con RESULT_OK. Para indicar de forma adecuada el resultado, añade el código subrayado en EdicionLugarActivity:

case R.id.accion_cancelar:
   setResult(RESULT_CANCELED);u>
   finish();
   return true;
case R.id.accion_guardar:
   …
   setResult(RESULT_OK);u>
   finish();
   return true; 
R.id.accion_cancelar -> {
   setResult(RESULT_CANCELED)
   finish()
   return true
}
R.id.accion_guardar -> {
   …
   setResult(RESULT_OK)
   finish();
   return true
} 

5. Ejecuta la aplicación y verifica que al editar un lugar y volver a VistaLugarActivity los datos son actualizados.