Sharing files via FileProvider

Why

Android Nougat (SDK 24) enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app.

As a result, sharing a file:// URI outside of your application will throw an FileUriExposedException.

How

If your app supports Android 7.0 and newer versions, make sure to generate a content:// URI instead and share it with the appropriate permissions. FileProvider is a custom class extending from ContentProvider that facilitates this process.

There are 3 main steps to follow in order to share a content:// URI:

Step 1: Define locations for files that you intend to share

FileProvider can only generate URI for files that are stored in directories specified by your application. This is done via an XML file with <paths> root element and one to multiple <path> children elements depending on the type of file you intend to share and the storage area where they’ll be in.

                        <paths>
    <files-path name="my_files" path="files" />
    <files-path name="my_other_files" path="other_files" />
    <cache-path name="my_cache" path="cache" />
    <external-path name="my_external" path="external" />
    <external-files-path name="my_external_files" path="external_files" />
    <external-cache-path name="my_external_cache" path="external_cache" />
    <external-media-path name="my_external_media" path="external_media" />
</paths>
                    

There are 6 type of paths:

  • <files-path> are files stored in the files/ directory of your application
  • <cache-path> are files stored in the cache/ directory of your application
  • <external-path> are files stored in the external storage area
  • <external-files-path> are files stored in the external storage area dedicated to the app
  • <external-cache-path> are files stored in the external cache storage area dedicated to the app
  • <external-media-path> are files stored in external media area dedicated to the app

Each element has two attributes:

  • path is the path of the directory the files will be stored in
  • name is a virtual name for the path (this is a security measure to prevent exposing the files’ paths).

If you are sharing files from multiple locations, you’ll have to define as many path elements:

                        <paths>
    <files-path name="documents" path="files/documents" />
    <files-path name="forms" path="files/forms" />
</paths>
                    

Step 2: Define a custom file provider for your application

Your custom provider must extend from FileProvider and its constructor takes a resourceId which correspond to the XML file you’ve created in the previous step

                        class CustomFileProvider: FileProvider(R.xml.file_paths)
                    

Add a <provider> tag to your manifest (as a child of <application>)

                        <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application ...>

        <provider
            android:name=".CustomFileProvider"
            android:authorities="${applicationId}.customfileprovider"
            android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
        </provider>
        ...

    </application>
</manifest>
                    

Make sure that:

  1. Your provider tag has the attribute android:name set to your custom provider
  2. You’re defining the android:authorities attribute. It can be one or multiple authorities separated by a semi-colon.
  3. The attribute grantUriPermissions is set to true. This allows the content provider to temporary grant permission to the data (note that if you set this to false you can control Uri permission at a more granular level)
  4. You must add the <meta-data> tag as a child to <provider> and set
    • its android:name attribute to android.support.FILE_PROVIDER_PATHS
    • its android:resource attribute to the your path XML file.

Step 3: Share the file

Sharing the file is quite simple once the previous configurations are done:

  • Create a reference to your File
  • Generate a content:// URI from the file using FileProvider
  • Share your content:// URI using the createChooser Intent

Here’s a code sample for creating and sharing a file

                        enum class PathType {
    FILES,
    CACHE,
    EXTERNAL,
    EXTERNAL_FILES,
    EXTERNAL_CACHE,
}

fun createDummyFile(context: Context, pathType: PathType = PathType.FILES): File {
    val timestamp = System.currentTimeMillis()
    val path = when (pathType) {
        PathType.FILES -> File(context.filesDir, "files")
        PathType.CACHE -> File(context.cacheDir, "cache")
        PathType.EXTERNAL -> File(Environment.getExternalStorageDirectory(), "external")
        PathType.EXTERNAL_FILES -> File(context.getExternalFilesDir(null), "external_files")
        PathType.EXTERNAL_CACHE -> File(context.externalCacheDir, "external_cache")
    }

    try {
        path.mkdirs()
    } catch (_: SecurityException) {}

    val file = File(path, "$timestamp.txt")
    file.printWriter().use { out ->
        out.println("hello world")
    }

    return file
}

fun shareFile(file: File, context: Context) {
    val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.customfileprovider", file)

    val intent = Intent(Intent.ACTION_SEND).apply {
        putExtra(Intent.EXTRA_STREAM, uri)
        setDataAndType(uri, "text/html")
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }

    val shareIntent = Intent.createChooser(intent, "Share file")
    try {
        context.startActivity(shareIntent)
    } catch (e: ActivityNotFoundException) {
        Log.w("MainActivity", "failed to initiate `createChooser`", e)
    }
}
                    

The pitfalls

Here’s a list of things that are easy to miss when working with FileProvider:

  1. In your manifest, make sure the <provider> tag is a child of the <application> tag. Also don’t forget the <meta-data> tag.
  2. Make sure that your <meta-data> tag has the properties android:name and android.resource. Specifically, do not replace android.resource with android.value
  3. The provider authority must match the authority injected in your FileProvider.getUriForFile().
  4. According to Google, you can use FileProvider directly instead of creating your custom provider, however “this is not reliable and will causes crashes on some devices”
  5. If you’re defining file paths with sub-folders, do not forget to create these folders as needed when creating and storing files. This can be done easily using File.mkdirs().
  6. Creating a file to share is done differently from one file path to another. Here’s the mapping:
                        <files-path /> : File(Context.filesDir, "")
<cache-path /> : File(Context.cacheDir, "")
<external-path /> :  File(Environment.getExternalStorageDirectory(), "")
<external-files-path /> :  File(Context.getExternalFilesDir(null), "")
<external-cache-path /> :  File(Context.externalCacheDir, "")