05 junio 2022

Android Studio: aprovechando el almacenamiento interno

Muchas veces en nuestra app queremos guardar los datos que tenemos en la memoria para que estén disponibles cuando abramos la app en otra ocasión.

Para hacerlo, según los desarrolladores de Android, tenemos 4 opciones:

  • Almacenamiento específico de la app: Almacena archivos diseñados solo para tu app, ya sea en directorios dedicados dentro de un volumen de almacenamiento interno o en directorios dedicados diferentes dentro del almacenamiento externo. Usa los directorios del almacenamiento interno para guardar información sensible a la que otras apps no deberían acceder. El almacenamiento externo es cuando quieres hacer un archivo que el usuario después puede usar de forma independiente a tu programa.
  • Almacenamiento compartido: Almacena archivos que tu app pretenda compartir con otras apps, incluidos archivos multimedia, documentos y otros.
  • Preferencias: Almacena datos primitivos y privados en pares clave-valor. Muy útil para guardar datos sobre la configuración, pero se puede usar para otras cosas también.
  • Bases de datos: Almacena datos estructurados en una base de datos privada.
Para mayor explicación y una tabla comparativa de todos estos tipos, les sugiero que visiten esta liga. En este post, voy a explicar como usar la primera opción, y de forma más particular, solo el almacenamiento interno.

Según Google (que se puede ver en esta liga), nos dice que son seguros porque en la ubicación donde los guarda en tu teléfono, como está encriptado, no se pueden accesar de otras apps. También hay que considerar que cuando el usuario desinstala tu app, estos archivos se borran (generalmente eso es lo que quieres para que no termines como con Windows que al desinstalar algo deja como mil archivos en tu compu).

Para ejemplificar como se puede almacenar y leer algo del almacenamiento interno, te lo muestro en este video donde explico cómo hice una app que guarda datos de un arreglo de objetos a un archivo, luego los lee. Abajo comparto el código de la app.

En este primer video muestro la interfaz de la app y se ve como funciona:


Y en éste video muestro cómo funciona la app, el código en Java que hace que todo funcione.


Éste es el archivo XML de la interfaz de la app. Yo lo hice con las herramientas visuales, pero aquí se pueden ver todas las propiedades que le asigné a cada control:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/lblAgregar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="@string/agregar_datos"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/lblLeer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="96dp"
        android:text="@string/leer_datos_del_archivo"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnGuardar" />

    <EditText
        android:id="@+id/txtNombre"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="@string/nombre"
        android:inputType="textPersonName"
        android:minHeight="48dp"
        android:textColorHint="#757575"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lblAgregar"
        android:importantForAutofill="no" />

    <EditText
        android:id="@+id/txtLugar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="@string/lugar"
        android:inputType="textPersonName"
        android:minHeight="48dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtNombre"
        android:importantForAutofill="no" />

    <EditText
        android:id="@+id/txtEdad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="@string/edad"
        android:inputType="number"
        android:minHeight="48dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtLugar"
        android:importantForAutofill="no" />

    <Button
        android:id="@+id/btnGuardar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="28dp"
        android:backgroundTint="#F44336"
        android:onClick="GuardaEnLista"
        android:text="@string/guardar_en_lista"
        android:textAllCaps="false"
        android:textColor="#FFEB3B"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtEdad"
        tools:ignore="TextContrastCheck" />

    <Button
        android:id="@+id/btnLeer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="40dp"
        android:layout_marginTop="28dp"
        android:backgroundTint="#F44336"
        android:onClick="LeeDelArchivo"
        android:text="@string/leer_del_archivo"
        android:textAllCaps="false"
        android:textColor="#FFEB3B"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lstNombres"
        tools:ignore="TextContrastCheck" />

    <Button
        android:id="@+id/btnMostrar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="28dp"
        android:backgroundTint="#F44336"
        android:onClick="MuestraDatos"
        android:text="@string/mostrar_datos"
        android:textAllCaps="false"
        android:textColor="#FFEB3B"
        app:layout_constraintStart_toEndOf="@+id/btnLeer"
        app:layout_constraintTop_toBottomOf="@+id/lstNombres"
        tools:ignore="TextContrastCheck" />

    <Button
        android:id="@+id/btnGuardarDisco"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="28dp"
        android:backgroundTint="#F44336"
        android:onClick="GuardaEnArchivo"
        android:text="@string/guardar_al_archivo"
        android:textAllCaps="false"
        android:textColor="#FFEB3B"
        app:layout_constraintStart_toEndOf="@+id/btnGuardar"
        app:layout_constraintTop_toBottomOf="@+id/txtEdad"
        tools:ignore="TextContrastCheck" />

    <Spinner
        android:id="@+id/lstNombres"
        android:layout_width="259dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="76dp"
        android:layout_marginTop="28dp"
        android:contentDescription="@string/lista_de_nombres"
        android:minHeight="48dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/lblLeer" />
</androidx.constraintlayout.widget.ConstraintLayout>

Y aquí está el código Java de la app que fue explicada en el video:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
package com.tony.example.almacena;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    // Declaro un ArrayList de objetos Datos (mismos que voy a guardar en un archivo y
    // al leer del archivo, los voy a almacenar aquí)
    List<Datos> listaDatos = new ArrayList<>();
    // Lista para el Spinner de nombres
    List<String> listaNombres = new ArrayList<>();

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

    // Método que guarda un nuevo objeto en el ArrayList
    public void GuardaEnLista(View v) {
        // Creo objetos Java y los vinculo con el layout
        EditText txtNombre = findViewById(R.id.txtNombre);
        EditText txtLugar = findViewById(R.id.txtLugar);
        EditText txtEdad = findViewById(R.id.txtEdad);
        Spinner lstNombres = findViewById(R.id.lstNombres);

        // Agrego un elemento a mi ArrayList
        // (creo un nuevo objeto Datos y lo agrego)
        listaDatos.add(new Datos(txtNombre.getText().toString(),
                txtLugar.getText().toString(),
                Integer.parseInt(txtEdad.getText().toString())));

        // Muestro mensaje
        Toast.makeText(this,"Agregado a la lista",Toast.LENGTH_SHORT).show();

        // Agrego nombre al spinner
        listaNombres.add(txtNombre.getText().toString());
        ArrayAdapter<String> llenaSpinner = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item,listaNombres);
        llenaSpinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        lstNombres.setAdapter(llenaSpinner);

        // Limpio los campos
        txtNombre.setText("");
        txtLugar.setText("");
        txtEdad.setText("");
    }

    // Método que guarda la lista a un archivo interno
    public void GuardaEnArchivo(View v) {
        // El objeto File me permite acceder el archivo (en este caso, para escribir en él)
        // (obtengo la ruta donde almacenarlo; en la carpeta de la app)
        File ruta = getApplicationContext().getFilesDir();
        // Éste es el nombre del archivo
        String nombreArch = "archivo.tpo";

        // El acceso a archivo tiene que ir en un try catch por si sucede algo inesperado
        try {
            FileOutputStream escribirArch = new FileOutputStream(new File(ruta,nombreArch));
            // Tengo que usar un ObjectOutputStream porque el almacenamiento interno es un archivo de bytes
            // y este objeto es el que me permite convertir de objeto a byte. Si fuera un String u otra cosa,
            // bastaría escribirArch.write(lacadena.getBytes())
            // suponiendo que lacadena es un String que contiene el texto a guardar.
            ObjectOutputStream streamArch = new ObjectOutputStream(escribirArch);
            streamArch.writeObject(listaDatos);
            streamArch.close();
        } catch (Exception e) {
            e.printStackTrace();        // Si hay error, que muestre datos sobre el fallo
        }
    }

    // Método que lee del archivo y lo pone en la lista
    public void LeeDelArchivo(View v) {
        // Obtengo referencia a los controles de la pantalla que voy a usar
        Spinner lstNombres = findViewById(R.id.lstNombres);

        // El objeto File con la ruta donde almacenarlo
        File ruta = getApplicationContext().getFilesDir();
        // Éste es el nombre del archivo
        String nombreArch = "archivo.tpo";

        // Borro la lista y borro lo que está en el spinner (el adapter será el arreglo vacío)
        listaNombres.clear();
        ArrayAdapter<String> llenaSpinner = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item,listaNombres);
        lstNombres.setAdapter(llenaSpinner);

        // Leo los datos del archivo
        try {
            // FileInputStream me permite abri el archivo para leer de él
            FileInputStream leeArch = new FileInputStream (new File(ruta,nombreArch));
            // El ObjectInputStream me pemite traducir el arreglo de bytes al Arraylist
            ObjectInputStream streamArch = new ObjectInputStream (leeArch);
            // Leo todo y lleno la lista
            listaDatos = (ArrayList<Datos>) streamArch.readObject();
            // Cierro el stream
            streamArch.close();

            // Lleno la lista de nombres (strings) con los nombres de la lista de datos
            listaNombres.clear();
            for (int i=0;i<listaDatos.size();i++) {
                listaNombres.add(listaDatos.get(i).getNombre());
            }
            // Lleno el Spinner de la nueva lista
            llenaSpinner = new ArrayAdapter<>(this,
                    android.R.layout.simple_spinner_item,listaNombres);
            llenaSpinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            lstNombres.setAdapter(llenaSpinner);
        } catch (Exception e) {
            e.printStackTrace();        // Si hay error, que muestre datos sobre el fallo
        }
    }

    // Método que busca en la lista de datos y muestra información del que
    // se seleccionó en el Spinner
    public void MuestraDatos(View v) {
        // Obtengo referencia a los controles de la pantalla que voy a usar
        Spinner lstNombres = findViewById(R.id.lstNombres);

        // Creo un Alert Builder (ventana estándar que puedo usar)
        AlertDialog.Builder constructor = new AlertDialog.Builder(this);
        constructor.setTitle("Almacena");       // Pongo título a la ventana
        constructor.setPositiveButton("Aceptar",null);      // Agrego un botón

        // Si se seleccionó algo en el spinner, le sigo
        long index = lstNombres.getSelectedItemId();
        if (index > -1) {
            // Pongo mensaje a la ventana que voy a mostrar
            constructor.setMessage("Nombre: " + listaDatos.get((int) index).getNombre()
                + " - Lugar: " + listaDatos.get((int) index).getLugar()
                + " - Edad: " + listaDatos.get((int) index).getEdad());     // El texto en la ventana
        } else {
            constructor.setMessage("Debe seleccionar un nombre de la lista");
        }

        // Creo y muestro la ventana
        AlertDialog ventanaMensaje = constructor.create();
        ventanaMensaje.show();
    }
}

Finalmente está el código de la clase Datos, que es con la que se llena el ArrayList y se escribe al archivo:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package com.tony.example.almacena;

import java.io.Serializable;

// Clase con los datos que voy a almacenar y leer
// Serializable es porque debo poderlo mandar a un flujo (stream) para guardarlo en el archivo
public class Datos implements Serializable {
    // Campos: información a guardar
    private String nombre,lugar;
    private int edad;

    // Constructor para asignar datos
    Datos(String nombre,String lugar,int edad) {
        this.nombre = nombre;
        this.edad = edad;
        this.lugar = lugar;
    }

    // Métodos para leer datos (getters)
    String getNombre() {
        return nombre;
    }

    String getLugar() {
        return lugar;
    }

    int getEdad() {
        return edad;
    }
}
Ligas de interés:


El Tony y sus ondas...

Related Posts Plugin for WordPress, Blogger...