Search code examples
androidkotlinandroid-10.0scoped-storage

Android 10 Impossible to delete multiple files (Scoped Storage)


I have been reading the android documentation and I can't find a solution to delete multiple files without multiple dialogs in Android 10 (API Level 29)
I found the following in the Android documentation:

  • API Level < 29: Delete files without dialog, permission granted on app startup
  • API Level > 29: Delete files by showing all files inside a dialog which the user has to confirm
  • API level = 29: Delete files by showing a permission dialog for each file

This is very frustrating not only for me but for every user who has API level 29, because there are some use cases where up to 100+ files must be deleted.

Entry Point when wanting to delete media

Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
    deleteMediaR(activity, uris)
}
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q -> {
    deleteMediaQ(activity, uris)
}
else -> {
    deleteMediaDefault(activity, uris)
}

Above API Level 29

@RequiresApi(Build.VERSION_CODES.R)
private fun deleteMediaR(activity: Activity, uris: ArrayList<String>) {
    val contentResolver = activity.contentResolver
    val collection: ArrayList<Uri> = ArrayList()
    collection.addAll(uris.map { uri -> Uri.parse(uri) })
    val pendingIntent = MediaStore.createDeleteRequest(contentResolver, collection)
    activity.startIntentSenderForResult(
        pendingIntent.intentSender, 42, null, 0, 0, 0, null)
}

API Level 29

@RequiresApi(Build.VERSION_CODES.Q)
private fun deleteMediaQ(activity: Activity, uris: ArrayList<String>) {
    try {
        deleteMediaDefault(activity, uris)
    } catch (exception: Exception) {
        if (exception is RecoverableSecurityException) {
            val pendingIntent: PendingIntent = exception.userAction.actionIntent
            activity.startIntentSenderForResult(pendingIntent.intentSender,
                42, null, 0, 0, 0, null)
        }
    }
}

Below API Level 29

contentResolver.delete(uri, where, media))

This is a very frustrating problem only concerning Android 10. I'm assuming Google forgot to implement this feature into their API. However, I'm hoping there is a proper solution as this breaks the app when using Android 10.


Solution

  • You can do it 2 ways, actually.

    1. Using mediastore :

    You can easily delete any media file using Mediastore delete API like this,

    ActivityResultLauncher<IntentSenderRequest> deleteLauncher
                = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
                new ActivityResultCallback<ActivityResult>() {
                    @Override
                    public void onActivityResult(ActivityResult result) {
                        if (result.getResultCode() == RESULT_OK) {
                            // Media files deleted successfully. Do your stuff.
                        }
                    }
                });
    
    
    @RequiresApi(api = Build.VERSION_CODES.R)
        private void deleteAPI30(ArrayList<Media> mediaList, Context context) throws IntentSender.SendIntentException {
            ContentResolver contentResolver = context.getContentResolver();
            List<Uri> uriList = new ArrayList<>();
            for (int i = 0; i < mediaList.size(); i++) {
                uriList.add(mediaList.get(i).getAppendedIdUri());
            }
            Collections.addAll(uriList);
            PendingIntent pendingIntent = MediaStore.createDeleteRequest(contentResolver, uriList);
            IntentSenderRequest senderRequest = new IntentSenderRequest.Builder(pendingIntent.getIntentSender())
                    .setFillInIntent(null)
                    .setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION, 0)
                    .build();
            deleteLauncher.launch(senderRequest);
        }
    

    P.S. You need to get the directory access permission using SAF directory chooser if things don't go off easy.

    2. Deleting using storage access framework (SAF) :

    public void deleteMediaSAF(ArrayList<Media> mediaList) {
            DocumentFile documentFile = DocumentFile.fromTreeUri(this, getContentResolver().getPersistedUriPermissions().get(0).getUri());
            for (int i = 0; i < mediaList.size(); i++) {
                File file = new File(mediaList.get(i).getPath());
                DocumentFile nextDocument = documentFile.findFile(file.getName());
                try {
                    DocumentsContract.deleteDocument(getContentResolver(), nextDocument.getUri());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
    
            mediaList = loadMedia(DIR_NAME);
            adapter.setMediaList(mediaList);
            adapter.notifyDataSetChanged();
            adapter.deselectAll();
            if (mediaList.size() > 0) {
                hideEmptyView();
            } else {
                showEmptyView();
                menuItem.setVisible(false);
            }
        }
    

    You can use these methods at for different APIs at the same time if possible. For an instance, using Mediastore API for Android 11 & above and using SAF API for Android 10.