Ficheros usando MediaStore en Kotlin

Escribir y leer ficheros usando MediaStore.

Para versiones de Android 10 (API 29) o superiores

Crear un nuevo proyecto en Kotlin: MemoriaExternaMediaStoreKotlin

Añadir el permiso al manifiesto:

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

build.gradle

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.memoriaexternakotlin"
    compileSdk = 34

    buildFeatures {
        viewBinding = true
    }

strings.xml

<resources>
    <string name="app_name">Memoria Externa en Kotlin</string>
    <string name="texto">una prueba de escritura en memoria externa</string>
    <string name="botonEsc">Guardar</string>
    <string name="botonLee">Leer</string>
    <string name="propiedades">Propiedades del fichero</string>
</resources>

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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="270dp"
        android:layout_height="156dp"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="68dp"
        android:ems="10"
        android:gravity="top|left"
        android:text="@string/texto"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.04" />

    <com.google.android.material.switchmaterial.SwitchMaterial
        android:id="@+id/swExplorer"
        android:layout_width="170dp"
        android:layout_height="48dp"
        android:layout_marginBottom="20dp"
        android:checked="false"
        android:text="Explorador"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="265dp" />

    <Button
        android:id="@+id/btnWrite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="36dp"
        android:layout_marginEnd="236dp"
        android:text="@string/botonEscribir"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline"
        app:layout_constraintVertical_bias="0.025" />

    <Button
        android:id="@+id/btnRead"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="49dp"
        android:layout_marginEnd="68dp"
        android:text="@string/botonLeer"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/btnWrite"
        app:layout_constraintTop_toTopOf="@+id/guideline"
        app:layout_constraintVertical_bias="0.025" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="310dp"
        android:layout_height="236dp"
        android:text="@string/propiedades"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.495"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText"
        app:layout_constraintVertical_bias="0.599" />


</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity(), View.OnClickListener {
    lateinit var binding: ActivityMainBinding

    companion object {
        const val FICHERO = "ficheroExterna.txt"
        val RELATIVE_PATH = Environment.DIRECTORY_DOCUMENTS + "/"
        // val RELATIVE_PATH = Environment.DIRECTORY_DOCUMENTS + "/ficheros/" //end "/" is not mandatory
        val TYPE = "text/plain"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view: View = binding.root
        setContentView(view)

        binding.btnWrite.setOnClickListener(this)
        binding.btnRead.setOnClickListener(this)
    }

    override fun onClick(view: View) {
        if (view === binding.btnWrite)
            if (!binding.swExplorer.isChecked)
                writeTextFileToMediaStore(FICHERO)
            else
                saveFileExplorer()
        else if (view === binding.btnRead)
            if (!binding.swExplorer.isChecked)
                readTextFileFromMediaStore(FICHERO)
        else
            openAndReadFile()
    }


    // write and read the file





}

 

Ask Gemini

For Android 10 and higher, give me the code in Kotlin to write a text file to the user’s «Documents» directory using the Storage Access Framework (SAF)

private fun saveFileExplorer() {
    val fileName = FICHERO //"my_document.txt"
    val mimeType = TYPE

    // Create an intent to create a new document
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = mimeType
        putExtra(Intent.EXTRA_TITLE, fileName)
    }

    // Launch the activity to create the document
    createDocumentLauncher.launch(intent)
}

private val createDocumentLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.data?.let { uri ->
                // Write the data to the file
                writeFile(uri, binding.editText.text.toString())
            }
        } else {
            Log.e("MyTag", "Error creating file")
        }
    }

private fun writeFile(uri: Uri, data: String) {
    //val fileUri: Uri = uri

    uri.let { fileUri ->
        try {
            contentResolver.openOutputStream(fileUri)?.use { outputStream ->
                outputStream.write(data.toByteArray())
                Log.d("MyTag", "File written successfully to: $fileUri")
            }
        } catch (e: IOException) {
            Log.e("MyTag", "Error writing to file: $fileUri", e)
        }
    }
}

 

Ask Gemini

For Android 10 and higher, give me the code in Kotlin to read a text file using the Storage Access Framework (SAF)

private fun openAndReadFile() {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "text/plain" // Filter for text files
    }
    openFileLauncher.launch(intent)
}
private val openFileLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.data?.let { uri ->
                readFileContent(uri)
            }
        } else {
            Log.e("MyTag", "File not selected")
        }
    }
private fun readFileContent(uri: Uri) {
    try {
        contentResolver.openInputStream(uri)?.use { inputStream ->
            BufferedReader(InputStreamReader(inputStream)).use { reader ->
                val fileContent = reader.readText()
                binding.editText.setText(fileContent)
                Log.d("MyTag", "File content: $fileContent")
            }
        }
        showMessage("File read successfully:\n $FICHERO")
    } catch (e: IOException) {
        binding.editText.setText("Error reading file")
        showMessage("Fail to read the file:\n $FICHERO \n" + e.message.toString())
        Log.e("MyTag", "Error reading file" + e.message.toString())
    }
}
 
private fun showMessage(texto: String) {
    Toast.makeText(this, texto, Toast.LENGTH_SHORT).show()
}

 

Ask Gemini:

give me the code to write a text file in kotlin using MediaStore

fun writeTextFileToMediaStore(fileName: String) {
    val relativePath = RELATIVE_PATH
    val type = TYPE

    try {
        val values = ContentValues()

        values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) //file name
        values.put(MediaStore.MediaColumns.MIME_TYPE, type) //file extension, will automatically add to file
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)

        val uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), values) //important!

        val outputStream = contentResolver.openOutputStream(uri!!)

        outputStream!!.write(binding.editText.text.toString().toByteArray())

        outputStream.close()

        getFileProperties(uri)

        showMessage("File created successfully: $FICHERO")
    } catch (e: IOException) {
        showMessage("Fail to create file: " + e.message.toString())
    }
}

 

Ask Gemini:

give me the code in Kotlin to get the properties of the file (path and creation date) after I write it

private fun getFileProperties(uri: Uri) {
    val projection = arrayOf(
        MediaStore.Files.FileColumns.DATA,
        MediaStore.Files.FileColumns.DATE_ADDED
    )

    val query = contentResolver.query(
        uri,
        projection,
        null,
        null,
        null
    )

    query?.use { cursor ->
        val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)
        val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_ADDED)

        if (cursor.moveToFirst()) {
            val path = cursor.getString(dataColumn)
            val dateAddedTimestamp = cursor.getLong(dateAddedColumn)

            val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm:ss", Locale.getDefault())
            val creationDate = dateFormat.format(Date(dateAddedTimestamp * 1000))

            binding.textView.text = "File Path: $path\n Creation Date: $creationDate"

            Log.d("MyTag", "File Path: $path")
            Log.d("MyTag", "Creation Date: $creationDate")
        } else {
            binding.textView.text = "File properties not found"
            Log.e("MyTag", "File properties not found")
        }
    }
}

 

Ask Gemini:

give me the code to read a text file in kotlin using MediaStore, the file relative path is Environment.DIRECTORY_ DOCUMENTS

private fun readTextFileFromMediaStore(fileName: String) {

    val relativePath = RELATIVE_PATH

    val collection =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
        } else {
            MediaStore.Files.getContentUri("external")
        }

    val projection = arrayOf(
        MediaStore.Files.FileColumns._ID,
        MediaStore.Files.FileColumns.DISPLAY_NAME,
        MediaStore.Files.FileColumns.RELATIVE_PATH
    )

    val selection = "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?"
    val selectionArgs = arrayOf(fileName, relativePath)

    val query = contentResolver.query(
        collection,
        projection,
        selection,
        selectionArgs,
        null
    )

    query?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
        val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
        val relativePathColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.RELATIVE_PATH)

        if (cursor.moveToFirst()) {
            val id = cursor.getLong(idColumn)
            val name = cursor.getString(nameColumn)
            val path = cursor.getString(relativePathColumn)
            val contentUri: Uri = Uri.withAppendedPath(collection, id.toString())

            Log.d("MyTag", "Found file: $name, Path: $path, Uri: $contentUri")

            readFileContent(contentUri)
        } else {
            showMessage("File not found: $FICHERO")
            Log.e("MyTag", "File not found")
        }
    }
}

/*

private fun readFileContent(uri: Uri) {
    try {
        contentResolver.openInputStream(uri)?.use { inputStream ->
            BufferedReader(InputStreamReader(inputStream)).use { reader ->
                val fileContent = reader.readText()
                binding.editText.setText(fileContent)
                Log.d("MyTag", "File content: $fileContent")
            }
        }
        showMessage("File read successfully:\n $FICHERO")
    } catch (e: IOException) {
        binding.editText.setText("Error reading file")
        showMessage("Fail to read the file:\n $FICHERO \n" + e.message.toString())
        Log.e("MyTag", "Error reading file" + e.message.toString())
    }
}
*/

 

Código en GitHub

 

Más información:

Access media files from shared storage

How to Save a File in Shared Storage Location in Android 13

getExternalStoragePublicDirectory deprecated in Android Q

Deja una respuesta