Use Storage Access Framework in Android

The Storage Access Framework (SAF) provides a file picker that allows to browse, create, and open files hosted by storage services (document providers). It can be external storage, cloud-based storage, etc.

SAF doesn’t require to define permissions in the manifest file because the user selects the files or directories that application can access. Files won’t be removed when the user uninstalls the application.

In the layout XML file we added two Button elements. One of these will be used to create a file, another one to open a file. A TextView element will be used to display a file content.

app/src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/createButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Create" />

    <Button
        android:id="@+id/openButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open" />

    <TextView
        android:id="@+id/myTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

We can use the ACTION_CREATE_DOCUMENT intent action to open the file picker that allows to select a location where to create a new file. When configuring the intent, we can specify a MIME type and name of the file. We cannot overwrite an existing file. A number in parentheses will be appended at the end of the file name, if application tries to create a file with the same name.

We can open a file by using a file picker which was loaded by ACTION_OPEN_DOCUMENT intent action. We can specify a MIME type to show only specific file types.

When the user selected a file, an intent returns control to the application by calling the onActivityResult() method. First parameter is request code that is used to identify the intent. Second paramter is result code that indicates whether or not the intent was successful. Third parameter is result data that contains selected file’s URI.

By using the URI of the selected file, we can write data to the file or read data from it.

app/src/main/java/com/example/app/MainActivity.kt

package com.example.app

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.io.FileInputStream
import java.io.FileOutputStream

class MainActivity : AppCompatActivity()
{
    private val fileName: String = "my_file.txt"
    private val createRequestCode: Int = 1
    private val openRequestCode: Int = 2

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        createButton.setOnClickListener { createFile() }
        openButton.setOnClickListener { openFile() }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?)
    {
        super.onActivityResult(requestCode, resultCode, resultData)

        if (resultCode == Activity.RESULT_OK && resultData != null) {
            if (requestCode == createRequestCode) {
                write("Hello world", resultData.data as Uri)
            } else if (requestCode == openRequestCode) {
                myTextView.text = read(resultData.data as Uri)
            }
        }
    }

    private fun createFile()
    {
        val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "text/plain"
        intent.putExtra(Intent.EXTRA_TITLE, fileName)

        startActivityForResult(intent, createRequestCode)
    }

    private fun openFile()
    {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "text/plain"

        startActivityForResult(intent, openRequestCode)
    }

    private fun write(message: String, uri: Uri)
    {
        val descriptor = contentResolver.openFileDescriptor(uri, "w")!!.fileDescriptor
        val fileOut = FileOutputStream(descriptor)

        fileOut.write(message.toByteArray())
        fileOut.close()
    }

    private fun read(uri: Uri): String?
    {
        val descriptor = contentResolver.openFileDescriptor(uri, "r")!!.fileDescriptor
        val fileIn = FileInputStream(descriptor)

        val message = fileIn.readBytes().toString(Charsets.UTF_8)
        fileIn.close()

        return message
    }
}
Use Storage Access Framework in Android

Leave a Comment

Your email address will not be published. Required fields are marked *