Step #2: Adding FileProvider
For the purposes of a “share” app bar item, we want the experience to be fairly seamless: the user clicks the item, then is prompted with options for sharing it. The flow should not be: the user clicks the item, goes through some UI to choose where to save it, then is prompted with options for sharing it. In other words, we should not be using ACTION_CREATE_DOCUMENT
as we are in the “save” scenario.
We can save the report to a file fairly easily. However, on modern versions of Android, we cannot share a file with other apps. However, Jetpack offers FileProvider
, which is way for us to serve files to other apps. All we need to do is add it to our manifest and configure it.
Open the app/src/main/AndroidManifest.xml
file and add this XML element to the manifest, below the two existing <activity>
elements:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
</provider>
FileProvider
is a ContentProvider
, which is an Android component that… provides content. Just as an <activity>
element identifies an Activity
in our app, a <provider>
element identifies a ContentProvider
in our app. In this case, instead of it being one that we wrote, we are going to use FileProvider
. As a result, our android:name
attribute has to be the fully-qualified class name to FileProvider
(androidx.core.content.FileProvider
).
The android:authorities
attribute indicates what name we wish to use for our provider. This name needs to be unique, and it fills a similar role as does a domain name in Web development. Here, we use ${applicationId}.provider
. The ${applicationId}
part is a “manifest placeholder” — a macro that will be expanded when our app is compiled and turned into our app’s application ID. If you click on the “Merged Manifest” sub-tab, you will see the results of this expansion:
By using ${applicationId}
, we are helping to ensure that our authority value is unique, as the applicationId
value itself is guaranteed to be unique on the device.
The android:exported="false"
value indicates that our provider is not to be exported, meaning that by default, other apps have no ability to access our provider’s content. This may seem silly, as the point of having this provider is to get our report to other apps. However, FileProvider
does not support android:exported="true"
. Instead, we use android:grantUriPermissions="true"
to indicate that we will grant rights to other apps on a case-by-case basis at runtime.
We need to tell our FileProvider
what files it should serve. To do this, we need to create an XML file with instructions, a bit reminiscent of how you configure a Web server to say what directories it should serve.
To that end, right-click over res/
and choose “New” > “Android Resource Directory” from the context menu. This brings up the “New Resource Directory” dialog:
In the “Resource type” drop-down, choose “xml”. Leave everything else alone, and click “OK” to create a res/xml/
directory in our project. This directory is good for holding arbitrary XML files — so long as they are well-formed XML, the build tools do not care about the exact contents of those files.
Then, right-click over the new res/xml/
directory and choose “New” > “XML resource file” from the context menu. Fill in provider_paths.xml
as the name, fill in paths
for the “Root element”, then click “OK” to create this file.
This should give you a file like this:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
</paths>
Replace that with:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared" path="shared" />
</paths>
The <paths>
list all of the locations that we want FileProvider
to serve. In our case, there is only one child element, so we will only serve from this one place.
The child element name — cache-path
— says that we start with the filesystem location that represents the “cache” portion of our app’s internal storage. This is the location identified by the getCacheDir()
method on Context
, and we will use that method to save our report to a file. The path
attribute further constrains FileProvider
to only serve files from the shared/
directory inside of getCacheDir()
. The name
attribute indicates that the Uri
values that FileProvider
uses to identify its content should have a shared
path segment that maps to this filesystem location.
Then, to teach FileProvider
about this XML, modify the manifest entry to have a child <meta-data>
element:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
In a manifest, <meta-data>
refers to additional information that the component can use to configure its operation, or that users of that component can use to know what that component is supposed to do. In this case, we are using it to configure the FileProvider
. The FileProvider
class knows to look up its android.support.FILE_PROVIDER_PATHS
metadata entry and read the <paths>
out of the associated XML resource. This way, we do not need to subclass FileProvider
and override methods to teach it what files to serve — it can handle that on its own via this metadata.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.