Opening Local Files

PSPDFKit supports opening files from the local file system of your device. This article provides best practices for working with local files in your apps.

Never Hardcode Absolute Paths

Directories where your app stores files are not guaranteed to be stable between different devices or even between app restarts, and paths to your app’s files may change over time if the app is moved to an adopted storage device. We recommend using methods from Context to access different special file system directories. More specifically:

  • Use Context#getFilesDir to get the path to the file system directory where your internal files are stored. Use this path instead of hardcoding the device-dependent path /data/data/<application_package/files.
  • Use Context#getExternalFilesDir(null) to get the path to the primary shared/external storage device where your application can place the persistent files it owns. Use this path instead of hardcoding the device-dependent path /storage/emulated/0/.

💡 Tip: If you need to persist paths to files (for example, in a database or in settings), only relative paths should be persisted.

Accessing the External Storage

Android devices support shared “external storage” that can be used for storing files. These can be removable (for example, SD cards or USB hard drives) or non-removable (internal). External storage can become unavailable for multiple reasons and does not enforce any security restrictions for accessing your files. Make sure that your application handles these situations correctly.

Storage Access Framework

Use the Storage Access Framework if you are running on Android 4.4+ and you want to allow users to browse and open documents from device storage.

💡 Tip: You can find a complete example of how to access files through the Storage Access Framework inside ExternalDocumentExample of the Catalog app.

Permissions to External Storage

In order to read or write files on the external storage, your app must acquire READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permissions. To acquire these permissions, add them to your manifest:

Copy
1
2
3
4
5
6
7
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.app.package">

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

If your app targets Android 6.0+, these permissions are not granted to your app automatically after installation. You need to explicitly ask for them in your activity:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // Request permission here.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_ASK_FOR_PERMISSION)
        }
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    if (requestCode == REQUEST_ASK_FOR_PERMISSION) {
        if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission has been granted
        } else {
            if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // User denied permission.
            } else {
                // User asked to "Never ask again" when denying permission.
            }
        }
    }
}
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Request permission here.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_ASK_FOR_PERMISSION);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_ASK_FOR_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission has been granted.
        }  else {
            if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // User denied permission.
            } else {
                // User asked to "Never ask again" when denying permission.
            }
        }
    }
}

💡 Tip: Since Android 4.4+, these permissions have no longer been required when accessing external files of your app (stored in the Context#getExternalFilesDir(null) directory).

Scoped Directory Access

Please note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions allow access to all public directories on the external storage, which could make your users worried or suspicious.

Use Scoped Directory Access if you are running on Android 7.0+ and you only need to access a specific directory on the external storage. This provides a simple permissions UI that clearly states which directory your application is requesting access to.