Saltar a contenido

Almacenamiento de datos en un archivo de texto en almacenamiento externo (tarjeta SD)

En el apartado anterior se ha visto cómo crear y leer un archivo de texto en la memoria interna de un dispositivo Android.
En determinadas situaciones puede resultar útil almacenar los datos en el almacenamiento externo (comúnmente asociado a una tarjeta SD), ya sea por:

  • Mayor capacidad de almacenamiento.
  • Posibilidad de compartir archivos con otros dispositivos o aplicaciones.
  • Persistencia más allá del ciclo interno de la app.

Info

No todos los dispositivos Android disponen de tarjeta SD física.
En la práctica actual, el término almacenamiento externo suele referirse al directorio externo privado de la aplicación, accesible mediante getExternalFilesDir().

Este enfoque no requiere permisos especiales en versiones modernas de Android y es el recomendado con fines didácticos y de compatibilidad.

Objetivo del ejemplo

La aplicación permitirá:

  • Introducir el nombre de un archivo.
  • Escribir texto libre en un campo multilínea.
  • Guardar el contenido en un archivo de texto en el almacenamiento externo.
  • Recuperar el contenido del archivo y mostrarlo en pantalla.

Diseño de la interfaz

La interfaz estará compuesta por:

  • Un EditText de texto simple para el nombre del archivo.
  • Un EditText multilínea para el contenido.
  • Un botón para grabar el archivo.
  • Un botón para recuperar el contenido.

activity_main.xml

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <EditText
        android:id="@+id/et1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Ingrese nombre del archivo"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <EditText
        android:id="@+id/et2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#FF0000"
        android:gravity="top|start"
        android:inputType="textMultiLine"
        app:layout_constraintTop_toBottomOf="@id/et1"
        app:layout_constraintBottom_toTopOf="@id/boton1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:padding="8dp"/>

    <Button
        android:id="@+id/boton1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Grabar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <Button
        android:id="@+id/boton2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Recuperar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Permisos en Android

En versiones antiguas de Android era necesario declarar:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

⚠️ Actualmente NO es necesario si se utiliza:

getExternalFilesDir(null)

Este método accede a un directorio externo privado de la aplicación, seguro y compatible con Scoped Storage.

Código de la aplicación

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import java.io.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val et1 = findViewById<EditText>(R.id.et1)
        val et2 = findViewById<EditText>(R.id.et2)
        val boton1 = findViewById<Button>(R.id.boton1)
        val boton2 = findViewById<Button>(R.id.boton2)

        // Guardar archivo
        boton1.setOnClickListener {
            try {
                val directorio = getExternalFilesDir(null)
                val file = File(directorio, et1.text.toString())

                val osw = OutputStreamWriter(FileOutputStream(file))
                osw.write(et2.text.toString())
                osw.flush()
                osw.close()

                Toast.makeText(this, "Datos grabados correctamente", Toast.LENGTH_SHORT).show()
                et1.setText("")
                et2.setText("")

            } catch (e: IOException) {
                Toast.makeText(this, "No se pudo grabar el archivo", Toast.LENGTH_SHORT).show()
            }
        }

        // Leer archivo
        boton2.setOnClickListener {
            try {
                val directorio = getExternalFilesDir(null)
                val file = File(directorio, et1.text.toString())

                val fIn = FileInputStream(file)
                val reader = BufferedReader(InputStreamReader(fIn))
                val contenido = StringBuilder()
                var linea = reader.readLine()

                while (linea != null) {
                    contenido.append(linea).append("\n")
                    linea = reader.readLine()
                }

                reader.close()
                et2.setText(contenido.toString())

            } catch (e: IOException) {
                Toast.makeText(this, "No se pudo leer el archivo", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Funcionamiento interno

  • getExternalFilesDir(null) devuelve el directorio externo privado de la app.
  • Los archivos almacenados:

    • Son accesibles por el usuario (vía explorador de archivos).
    • Se eliminan automáticamente al desinstalar la app.
    • No se requieren permisos en tiempo de ejecución.

Ventajas y limitaciones

Ventajas

  • Mayor capacidad que la memoria interna.
  • Archivos accesibles desde fuera de la app.
  • Compatible con versiones modernas de Android.

Limitaciones

  • No es adecuado para datos sensibles.
  • No estructurado.
  • No reactivo.

Actividad

  • AC603. Realiza el ejemplo en Compose, realiza una investigación para poder replicarlo.