Using FileProvider properly in Android

Aung Kyaw Myint
6 min readMay 16, 2019

Most of the time, we want to use camera in our app. And there’re two ways to do it. First is to delegate the action to default camera app or implement the action from ground up with Camera or Camera2 features. While the latter is useful when you want to customise your camera feature and offer different experience than default one, delegating the action to default camera app saves us lot of time when we just want to take pictures.

When we want to use default camera app, there are steps we have to do. You can follow the steps in this link . I won’t repeat the steps here.

After we launch the camera app and the returns back to our calling activity, we can retrieve ActivityResult in two ways. One is retrieving bitmap with intent.getExtras().get(“data”) and another is saving the full-size photo. While retrieving bitmap is relatively easy, there has been some difficulties in terms of working with full-size photo. What I want to emphasise in this article is the latter one: saving full-size photo.

Before we continue, starting from Android 7 (Nougat), Google introduced some security features. One of them was file system permission changes.

“Passing file:// URIs outside the package domain may leave the receiver with an unaccessible path. Therefore, attempts to pass a file:// URI trigger a FileUriExposedException. The recommended way to share the content of a private file is using the FileProvider.”

You can find the details in this link. If you have followed the above mentioned link or have experience working with implicit camera intent, you know this is something you have to change since Android does not allow you to pass file Uri any more.

Since we will be saving the file, we need permission to write to storage and add file provider tag to the application tag in AndroidManifest file.

In provider tag, we have android:authorities which is the combination of applicationId and fileprovider. And set android:exported to false since we don’t want our files to be used publicly. And lastly set android:grantUriPermissions to true to grant temporary access to files. You can find the more details in this link.

Now to get to the juicy part, what is file_paths in android:resource in meta-data tag? This is the xml file where you specify the locations of your files. It will be something similar like this.

There are a number of ways to save a file in a number of directories. Details about saving a file can be found here. Google explanation on storage is this:

“All Android devices have two file storage areas: “internal” and “external” storage. These names come from the early days of Android, when most devices offered built-in non-volatile memory (internal storage), plus a removable storage medium such as a micro SD card (external storage). Many devices now divide the permanent storage space into separate “internal” and “external” partitions. So even without a removable storage medium, these two storage spaces always exist, and the API behavior is the same regardless of whether the external storage is removable.”

There are three distinct ways: getFileDirs(), getExternalFileDirs() and getExternalStorageDirectory

getFileDirs(): This is meant for saving file in internal storage (as in root storage). You can store files here but you won’t be able to access them from file explorer without root.

getExternalFileDirs(): This is for saving file in external storage under your app package name. It will most likely be Android/data/{your app package}/files/.

Note: When user uninstalls your app, files stored under directory from getFileDirs() and getExternalFileDirs() will be removed.

getExternalFileStorageDirectory: This is for saving file in primary shared/external storage directory. It will be stored directly under storage/emulated/0 and will be accessed publicly and shared with other apps like gallery, Google Drive,etc. Detail explanation can be found here.

Now, let’s use these and create files for launching implicit camera intent.

createImageFile()

In this function, we check whether there’s media storage mounted, if it is, we will be using shared external storage and if it isn’t, we will use external partition from permanent storage.

Note: when defining the directory path, it has to be matched with the one we define in file_paths.

getFileDir()

filesDir = new File(getFilesDir(),”Images”);

<files-path name=”my_internal_images” path=”Images/”/>

getExternalFilesDir()

filesDir = new File(getExternalFilesDir(null),”Example Images”)

<external-files-path name=”my_images” path=”Example Images/”/>

getExternalStorageDirectory()

filesDir = new File(Environment.getExternalStorageDirectory() + “/Example/Media”,”Example Images”);

<external-path name=”my_share_images” path=”Example/Media/Example Images/”/>

And if you want to use just Images folder, you can do also like this.

filesDir = new File(Environment.getExternalStorageDirectory(),”Images”);

<external-path name=”my_share_images” path=” Images/”/>

And the last thing is we gotta be careful about this line:

if(!filesDir.exists()) filesDir.mkdirs();

It basically says if this directory doesn’t exist, create one. filesDir.mkdir() is suitable if you want to create just one folder. But if you want to create file folder structure and have subfolders under folder, you have to use fileDir.mkdirs().

dispatchTakePictureIntent()

In this function, we will check whether the user’s Android SDK is less than Nougat or not. If the SDK is less than Nougat, we can just pass the file’s Uri in normal way with Uri.fromFile(file). If it isn’t, we have to get the Uri from FileProvider like this;

photoURI = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + “.fileprovider”, photoFile);

This will generate Uri like this: content://{applicationId}/my_share_images/fileName

Remember since we are using share external storage directory, fileprovider will pick up the associated path name from the file_paths. In this case, it is my_share_images. Feel free to test out with other directories. I have provided ways to do in above mentioned code.

And lastly, don’t forget to give temporary permission by setting flags Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION. Otherwise, the implicit intent won’t be able to open the file.

You can read about grant permission for files in details here.

Final thing: now we know how to create a file and pass the FilePrvoider Uri to the intent. What if we want to edit the photo or do some processing after the intent. In the documentation, it suggests that we should open the file like this:

But that would be an overkill considering we just create a file and now that we have a saved photo file from camera. And if we want to do some processing on it, we need to write another file? The hack around it would be like this:

If you look at it, you will see that it’s just redoing the steps from createImageFile() and get the last part from uri (FileProvider Uri).

(Remember this is to use explicitly in your app when you pass FileProvider Uri to another activity)

During my journey for exploring FileProvider, I found out that developers don’t understand the idea of FileProvider and using it in the wrong way. For example, if you want to scan the Download folder and select a file. You have a Uri from that file. But when you want to launch implicit gallery intent, you have to pass a FileProvider Uri into the intent. But you can’t use the file Uri you have from Download folder. It is not under your app authority and it is not under your app’s file path. If you want to open the file, you have to copy the file under your app’s file path. And get that copy file Uri with FileProvider and pass it to the intent.

I’m just sharing the things I found and if you have better ways let me know in the comments and if you enjoy the article, feel free to clap. 👏

External Links:

https://developer.android.com/training/secure-file-sharing/share-file.html

--

--