Opening Local Files

PSPDFKit supports opening files from 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 - paths to your app's files may change over time if the app is moved to an adopted storage device. We recommend to use methods from Context to access different special filesystem directories. Specifically:

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

💡 Tip: If you need to persist paths to files (for example in 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 hardrives) or internal (non-removable). External storage can become unavailable for multiple reasons and does not enforce any security restrictions to access your files. Make sure that your application handles these situations correctly.

Storage Access Framework

Use 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 on how to access files through Storage Access Framework inside the 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 you 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 permissions 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: These permissions are not required when accessing external files of your app (stored in directory Context#getExternalFilesDir(null)) since Android 4.4+.

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.

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. Scoped directory access provides a simple permissions UI that clearly states what directory your application is requesting access to.