Mi primera APP para Android

05 de Noviembre del 2016


Hola a todos, hoy escribiré sobre la primera aplicación que he hecho para Android, bueno no es realmente la primera pero si la que tiene alguna funcionalidad más allá de lo básico, las anteriores eran simples pruebas didácticas.

La función de esta pequeña aplicación es la de gestionar el intercambio de datos entre actividades y además ser capaz de llamar a otra aplicación, en nuestro caso Google Maps.

Si os hace especial ilusión verla al completo la tenéis disponible en mi repositorio de GitHub.


android

Conceptos a entender antes de nada

Primero de todo vamos a dar un pequeño repaso a algún que otro concepto para entender mejor de qué trata esta aplicación.

Activities

Ya hemos nombrado a las actividades, pero, ¿qué es realmente una actividad (o como se les conoce en inglés, Activities)? Podemos definir como cada actividad a cada pantalla que tenemos en una aplicación, es decir, si tenemos 5 pantallas, tendremos 5 activities. Esta definición con un conocimiento más avanzado de Android no es realmente correcta, pero por ahora podemos trabajar con ella así, por lo tanto cada pantalla que trataremos será una activity.

Intent y Bundle

Intent es una clase que utilizamos como mecanismo para podernos comunicar entre activities, ya sea invocándola, mandándole servicios o datos.

Bundle como bien indica su nombre es un contenedor de datos muy utilizado junto con Intent a la hora de añadirlos a éste.

startActivity() y startActivityForResult()

Tenemos dos métodos similares a la hora de llamar a otra activity, con startActivity() la llamamos, mientras que con startActivityForResult() además de llamarla la preparamos para que sea capaz de devolver datos a la activity anterior.

Constantes

Las constantes contienen información que va a permanecer igual durante toda la ejecución, podemos y debemos utilizarlas para trabajar entre distintas actividades y así poder comprobar que el envío y recibo de datos mediante pares clave/valor se haga correctamente, en nuestro caso hemos definido las constantes de abajo en la clase principal.

// constantes definidas para usar como claves
public static final String KEY_NOMBRE = "nombre";
public static final String KEY_EDAD = "edad";
public static final String KEY_TELEFONO = "telefono";
public static final String KEY_LATITUD = "latitud";
public static final String KEY_LONGITUD = "longitud";

// constante para el requestCode
public static final int REQUEST_CODE = 1234;

Explicación de la práctica.

mainactivity

Como podemos apreciar en la foto de arriba tenemos unas etiquetas sin datos (TextView en Android) ya que de momento no hemos introducido ninguno de ellos, un botón para ver en Google Maps y otro para editar que nos llevara a la siguiente activity para introducir los datos. El botón de “ver en Google Maps esta desactivado, ya veremos más adelante el por qué.

secondctivity

En esta segunda activity ya podemos introducir los valores, para ellos tendremos dos EditText (campo de texto editable) para nombre y teléfono y una barra conocida como SeekBar (como curiosidad la barra no enviará ningún dato hasta que se haya movido al menos una vez).

Justo debajo tenemos un ToggleButton (Obtener Coordenadas) que como bien indica su nombre sirve para poder obtener las coordenadas desde el GPS y por último un botón de aceptar que nos devolverá a la activity principal enviándole los datos introducidos.

Cómo podemos hacer esto.

demo

Una vez tenemos claro el funcionamiento de la práctica podemos comenzar con la lógica. Ahora os iré poniendo secciones de éste explicando su cometido para no extender innecesariamente el post.

MainActivity

Esta clase es la que que contiene la actividad principal tenemos 3 métodos en ella:

  • onCreate(): Las activities en Android se pintan, por lo general, desde este método, por lo que a la hora de mostrar la primera actividad hemos de inicializar los componentes que aparecerán en ella.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // inicializamos los TextView que aparecerán en pantalla
        tvNombre = (TextView) findViewById(R.id.textViewNombreDevuelto);
        tvEdad = (TextView) findViewById(R.id.textViewEdadDevuelto);
        tvTelefono = (TextView) findViewById(R.id.textViewTelefonoDevuelto);
        tvLatitud = (TextView) findViewById(R.id.textViewLatitudDevuelto);
        tvLongitud = (TextView) findViewById(R.id.textViewLongitudDevuelto);
    
        // inicializamos los botones que aparecerán en pantalla
        botonEditar = (Button) findViewById(R.id.buttonEditar);
        botonMaps = (Button) findViewById(R.id.buttonMaps);
    
        // añadimos los listeners a los botones
        botonEditar.setOnClickListener(this);
        botonMaps.setOnClickListener(this);
    }
    
  • onClick(): Método encargado de gestionar qué ocurre cuando pulsamos los botones a los que hemos añadido el listener.

    @Override
    public void onClick(View v) {
    
        // Si pulsamos el botón Editar
        if(R.id.buttonEditar == v.getId()) {
    
            // declaramos e inicializamos un Intent y un Bundle para enviar los datos
            // a la actividad secundaria
            Intent intent = new Intent(this, SecondActivity.class);
            Bundle b = new Bundle();
    
            // comprobamos si los campos tienen valor para enviarlos a la segunda actividad,
            // utilizamos clave/valor para que las actividades conozcan a qué dato nos
            // estamos refiriendo
            if(!tvNombre.getText().toString().isEmpty())
                b.putString(KEY_NOMBRE, tvNombre.getText().toString());
            if(!tvEdad.getText().toString().isEmpty())
                b.putInt(KEY_EDAD, Integer.valueOf(tvEdad.getText().toString()));
            if(!tvTelefono.getText().toString().isEmpty())
                b.putInt(KEY_TELEFONO, Integer.valueOf(tvTelefono.getText().toString()));
    
            // insertamos el contenedor (Bundle) que contiene los datos al intent
            intent.putExtras(b);
    
            // llamamos a la segunda actividad
            startActivityForResult(intent, REQUEST_CODE);
        }
    
        // Si pulsamos el botón para acceder a Google Maps
        if(R.id.buttonMaps == v.getId()) {
    
            // definimos un Uri que pueda recibir Google Maps
            String uri = String.format(Locale.ENGLISH, "geo:%f,%f", latitud, longitud);
            Intent intentMaps = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
    
            // hacemos la llamada
            startActivity(intentMaps);
        }
    }
    
  • onActivityResult(): Este método es el encargado de recibir los datos desde la actividad secundaria. Aquí está explicado por qué está desactivado el botón de Google Maps que comenté más arriba.

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    
        // comprobamos si hemos recibido los datos correctamente
        if(requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
    
            // para cada clave comprobamos que el intent contiene ese extra para mostrarlo
            if(data.hasExtra(KEY_NOMBRE))
                tvNombre.setText(data.getExtras().getString(KEY_NOMBRE));
            else
                // si el usuario borra el dato en la segunda actividad mostramos
                // un String vacío
                tvNombre.setText("");
            if(data.hasExtra(KEY_EDAD))
                tvEdad.setText(String.valueOf(data.getIntExtra(KEY_EDAD, -1)));
            else
                tvEdad.setText("");
            if(data.hasExtra(KEY_TELEFONO))
                tvTelefono.setText(String.valueOf(data.getIntExtra(KEY_TELEFONO, -1)));
            else
                tvTelefono.setText("");
            if(data.hasExtra(KEY_LATITUD)) {
                tvLatitud.setText(String.valueOf(data.getDoubleExtra(KEY_LATITUD, -1d)));
                latitud = data.getDoubleExtra(KEY_LATITUD, -1d);
            } else {
                tvLatitud.setText("");
            }
            if(data.hasExtra(KEY_LONGITUD)) {
                tvLongitud.setText(String.valueOf(data.getDoubleExtra(KEY_LONGITUD, -1d)));
                longitud = data.getDoubleExtra(KEY_LONGITUD, -1d);
            } else {
                tvLongitud.setText("");
            }
    
            // activamos el botón si recibimos una latitud y una longitud
            botonMaps.setEnabled(data.hasExtra(KEY_LATITUD) && data.hasExtra(KEY_LONGITUD));
    
            // si el usuario aprieta el botón back devolveremos un Toast avisándole
        } else if(requestCode == REQUEST_CODE && resultCode == RESULT_CANCELED) {
            Toast.makeText(this, R.string.usuarioBack, Toast.LENGTH_SHORT).show();
        }
    }
    

SecondActivity

Clase que contiene la segunda actividad que nos permite introducir los datos que mostraremos en la primera actividad, en ella utilizaremos estos métodos:

  • onCreate(): Definimos qué aparecerá en pantalla, además controlamos si ya hemos introducido algún valor anteriormente para que lo muestre.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    
        // inicializamos la tabla que se mostrará cuando el ToggleButton este enabled
        tablaCoordenadas = (TableLayout) findViewById(R.id.tablaCoordenadas);
    
        // inicialiamos los EditText y TextView que aparecerán en pantalla
        etNombre = (EditText) findViewById(R.id.editTextNombre);
        tvEdad = (TextView) findViewById(R.id.textViewMostrarEdad);
        etTelefono = (EditText) findViewById(R.id.editTextTelefono);
        tvLatitud = (TextView) findViewById(R.id.textViewMostrarLatitud);
        tvlongitud = (TextView) findViewById(R.id.textViewMostrarLongitud);
    
        // inicializamos la variable que accede al servicio de localización
        lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    
        // inicializamos la SeekBar
        seekBarAnyos = (SeekBar) findViewById(R.id.seekBar);
    
        // inicializamos los botones que aparecerán en pantalla
        botonAceptar = (Button) findViewById(R.id.buttonAceptar);
        botonToggle = (ToggleButton) findViewById(R.id.toggleButtonCoordenadas);
    
        // añadimos los listeners a los botones y la barra
        botonToggle.setOnCheckedChangeListener(this);
        botonAceptar.setOnClickListener(this);
        seekBarAnyos.setOnSeekBarChangeListener(this);
    
        // solicitamos que nos notifique cuando hay cambios en la localización
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
    
        // si anteriormente ya hemos mandado un nombre, edad o teléfono a la actividad
        // principal y volvemos a la secundaria para volverlos a editar, éstos aparecerán
        // ya escritos, los recuperamos con getIntent() y los mostramos con setText()
        etNombre.setText(getIntent().getExtras().getString(MainActivity.KEY_NOMBRE));
    
        // colocamos el cursor a la derecha de la palabra
        etNombre.setSelection(etNombre.getText().length());
    
        // comprobamos que la edad tenga un valor válido para mostrarlo
        if(getIntent().getExtras().getInt(MainActivity.KEY_EDAD) >= 18) {
            seekBarAnyos.setProgress(getIntent().getExtras()
                                            .getInt(MainActivity.KEY_EDAD) - 18);
            tvEdad.setText(getString(R.string.anyos, String.valueOf(getIntent()
                                            .getExtras().getInt(MainActivity.KEY_EDAD))));
        }
    
        // comprobamos que el teléfono tenga un valor para mostrarlo (si no hacemos esta
        // comprobación y accedemos a la actividad secundaria sin tener un teléfono en la
        // principal escribe un 0 ya que el int no tiene definido un valor todavía)
        if(getIntent().hasExtra(MainActivity.KEY_TELEFONO)) {
            etTelefono.setText(String.valueOf(getIntent().getExtras()
                                            .getInt(MainActivity.KEY_TELEFONO)));
    
            // colocamos el cursor a la derecha de los números
            etTelefono.setSelection(etTelefono.getText().length());
        }
    }
    
  • onClick(): Igual que en la clase principal es el método encargado de gestionar qué ocurre cuando pulsamos los botones.

    @Override
     public void onClick(View v) {
    
         // declaramos e inicializamos el intent que recogerá y mandará los datos
         // a la actividad principal
         Intent intent = new Intent();
    
         // comprobamos que el botón pulsado es el de aceptar para todos los campos
         if(R.id.buttonAceptar == v.getId()) {
    
             // comprobamos si está vacío el campo para enviar datos sólo si hemos
             // introducido algún valor para cada uno de los campos
             if(!etNombre.getText().toString().isEmpty())
                 intent.putExtra(MainActivity.KEY_NOMBRE, etNombre.getText().toString());
             if(!tvEdad.getText().toString().isEmpty())
                 intent.putExtra(MainActivity.KEY_EDAD, seekBarAnyos.getProgress() + 18);
             if(!etTelefono.getText().toString().isEmpty())
                 intent.putExtra(MainActivity.KEY_TELEFONO, Integer.valueOf(etTelefono
                                                                .getText().toString()));
         }
    
         // comprobamos que el ToggleButton esté activo para poder enviar la latitud
         // y la longitud
         if(botonToggle.isChecked()) {
    
             // comprobamos que el valor sea distinto de 0 para enviar los datos (si no
             // se realiza esta comprobación se envían los valores 0 y 0 si el GPS
             // esta desactivado)
             if(latitud != 0)
                 intent.putExtra(MainActivity.KEY_LATITUD, latitud);
             if(longitud != 0)
                 intent.putExtra(MainActivity.KEY_LONGITUD, longitud);
           }
    
         // define el resultado que será recuperado por la actividad llamante que lo espera
         setResult(RESULT_OK, intent);
    
         // termina la actividad
         finish();
     }
    
  • onCheckedChanged(): Método que debemos sobrescribir cuando implementamos los listeners de un ToggleButton (CompoundButton.OnCheckedChangeListener), en este caso su función es hacer visible la tabla (TableLayout) que muestras las coordenadas cuando está activo.

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        tablaCoordenadas.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE);
    }
    
  • onLocationChanged(): Este método es necesario utilizarlo cuando implementamos LocationListener para trabajar con el GPS, en nuestro caso nos guardará la latitud y la longitud en unas variables (para poderlas enviar luego a la actividad principal) y las pintará en pantalla cada vez que se actualicen.

    @Override
    public void onLocationChanged(Location location) {
        latitud = location.getLatitude();
        longitud = location.getLongitude();
    
        tvLatitud.setText(String.valueOf(latitud));
        tvlongitud.setText(String.valueOf(longitud));
    }
    
  • onProgressChanged(): Al implementar el listener la barra (SeekBar.OnSeekBarChangeListener) podemos sobrescribir este método, en nuestro caso nos mostrará la edad por pantalla en un TextView cada vez que movamos la barra.

    @Override
     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
         tvEdad.setText(getString(R.string.anyos, String.valueOf(progress + 18)));
     }
    

Bueno, con esto ya hemos dado un repaso a las dos clases que componen la actividad principal y secundaria de nuestra pequeña aplicación, tan solo me queda comentaros que para poder utilizar el GPS debemos definir unos permisos que varían según la versión de Android que queramos utilizar, por lo que no voy a poner cuáles he utilizado yo ya que podría llevaros a algún problema, esto debéis mirarlo por vosotros o preguntarme.

Como siempre cualquier duda o corrección no dudéis escribir a mi correo iam@jmoral.es o a mi Twitter @owniz.

Un saludo. ツ

Comparte en →