Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent AlertDialog from displaying behind dim of another DialogFragment

I am creating a BottomSheetDialogFragment that allows the user to either take a photo or choose one from their library. To access either feature the WRITE_EXTERNAL_STORAGE permission is required.

Therefore, I would like to ask for permission from the BottomSheetDialogFragment, preventing the user from clicking on anything else until permission is granted. If I request permission in onViewCreated, the permissions dialog displays fine:

Permission Request

Though, if permission is denied and the user tries again I attempt to display the rationale in an AlertDialog, but the dialog gets blocked; presumably by the dim from the BottomSheetDialogFragment:

Permission Rationale

I think this is caused by the animation of the BottomSheetDialogFragment, which does not display the background dim until the fragment finishes its animation. This coincidentally happens after onViewCreated. Does anyone know if there is a way to force the AlertDialog to the front without closing or dismissing the BottomSheetDialogFragment? Or if there is a way to listen for the BottomSheetDialogFragment animation to finish?

I know I could request permission before I add the BottomSheetDialogFragment, but I would rather ask for it with the dialog in view to provide some context for the user.


Here is the Fragment:

public class ImageChooserDialogFragment extends BottomSheetDialogFragment {

    public interface OnImageChosenListener {
        void onImageChosen(Uri data);
    }

    private static final String PREFIX_IMAGE_CAPTURE = "IMG_";

    private static final int REQUEST_PERMISSION_CAMERA  = 0;
    private static final int REQUEST_PERMISSION_STORAGE = 1;

    private static final int REQUEST_IMAGE_CAPTURE      = 2;
    private static final int REQUEST_IMAGE_SELECTION    = 3;

    private static final String[] PERMISSIONS_CAMERA = new String[] {
            Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA
    };

    private static final String[] PERMISSIONS_STORAGE = new String[] {
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    private boolean hasFeatureCamera;

    private Uri mCurrentPhotoResource;

    private View mView;

    private OnImageChosenListener mOnImageChosenListener;

    public static ImageChooserDialogFragment newInstance() {
        return new ImageChooserDialogFragment();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Timber.d("onAttach");

        if(context instanceof OnImageChosenListener) {
            mOnImageChosenListener = (OnImageChosenListener) context;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Fragment parent = getParentFragment();

        if(parent != null) {
            onAttachToFragment(parent);
        }

        PackageManager manager = getContext().getPackageManager();
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            hasFeatureCamera = manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
        } else {
            hasFeatureCamera = manager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
                    || manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
        }
    }

    public void onAttachToFragment(Fragment fragment) {
        if(fragment instanceof OnImageChosenListener) {
            mOnImageChosenListener = (OnImageChosenListener) fragment;
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mView = inflater.inflate(R.layout.dialog_image_chooser, container, false);
        ButterKnife.bind(this, mView);

        if(!hasFeatureCamera) {
            mView.setVisibility(View.GONE);
        }

        return mView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) {
            requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE);
        } else if(!hasFeatureCamera) {
            dispatchImageSelectionIntent();
        } else {
            displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch(requestCode) {
            case REQUEST_PERMISSION_CAMERA:
                if(PermissionUtil.verifyPermissions(grantResults)) {
                    dispatchImageCaptureIntent();
                } else {
                    displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_camera);
                } break;
            case REQUEST_PERMISSION_STORAGE:
                if(PermissionUtil.verifyPermissions(grantResults)) {
                    if(!hasFeatureCamera) {
                        dispatchImageSelectionIntent();
                    }
                } else {
                    displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage);
                } break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_IMAGE_CAPTURE:
                if(resultCode == Activity.RESULT_OK) {
                    handleImageCaptureResult(data);
                } else {
                    destroyTemporaryFile();
                } break;
            case REQUEST_IMAGE_SELECTION:
                if(resultCode == Activity.RESULT_OK) {
                    handleImageSelectionResult(data);
                } break;
            default:
                super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @OnClick(R.id.photo_take)
    public void onClickCapture() {
        if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_CAMERA)) {
            requestPermissionsWithRationale(REQUEST_PERMISSION_CAMERA, PERMISSIONS_CAMERA);
        } else {
            dispatchImageCaptureIntent();
        }
    }

    @OnClick(R.id.photo_choose)
    public void onClickChoose() {
        if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) {
            requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE);
        } else {
            dispatchImageSelectionIntent();
        }
    }

    private void dispatchImageCaptureIntent() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        if(intent.resolveActivity(getContext().getPackageManager()) != null) {
            try {
                File image = createTemporaryFile();
                Uri data = FileProvider.getUriForFile(getContext(), "com.example.app.fileprovider", image);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, data);
                startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
            } catch (IOException exception) {
                Timber.w(exception, "Error occurred while creating image file");
            }
        } else {
            // TODO: handle no application to handle intent
        }
    }

    private void dispatchImageSelectionIntent() {
        final Intent intent = new Intent(Intent.ACTION_PICK,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        if(intent.resolveActivity(getContext().getPackageManager()) != null) {
            startActivityForResult(intent, REQUEST_IMAGE_SELECTION);
        } else {
            // TODO: handle no application to handle intent
        } dismiss();
    }

    private void dispatchDetailSettingsIntent() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", getContext().getPackageName(), null));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        if(intent.resolveActivity(getContext().getPackageManager()) != null) {
            startActivity(intent);
        } else {
            // TODO: handle no application to handle intent
        } dismiss();
    }

    private void dispatchMediaScanIntent(Uri data) {
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, data);
        getContext().sendBroadcast(intent);
    }

    private void displayPermissionsRationale(int requestCode) {
        switch (requestCode) {
            case REQUEST_PERMISSION_CAMERA:
                Timber.d("Request Image capture rationale");
                DialogFactory.createRationaleAlert(getContext(),
                        R.string.title_dialog_rationale_camera,
                        R.string.msg_dialog_rationale_camera).show();
                break;
            case REQUEST_PERMISSION_STORAGE:
                Timber.d("Request Image selection rationale");
                DialogFactory.createRationaleAlert(getContext(),
                        R.string.title_dialog_rationale_storage,
                        R.string.msg_dialog_rationale_storage).show();
                break;
            default:
                Timber.d("No rationale");
        }
    }

    private void displayRequestPermissionsAlert(@StringRes int message) {
        Snackbar.make(mView, message, Snackbar.LENGTH_LONG)
                .setAction(R.string.action_settings, view -> dispatchDetailSettingsIntent()).show();
        dismiss();
    }

    private void requestPermissionsWithRationale(int requestCode, @NonNull String[] permissions) {
        Timber.d("Request permissions with rationale");
        if(PermissionUtil.shouldShowRequestPermissionsRationale(this, permissions)) {
            Timber.d("Display rationale");
            displayPermissionsRationale(requestCode);
        } else {
            Timber.d("Request Permissions");
            requestPermissions(permissions, requestCode);
        }
    }

    private File createTemporaryFile() throws IOException {
        String fileName = PREFIX_IMAGE_CAPTURE /*+ TimeUtil.getTimeStamp()*/;

        File directory = getContext().getExternalFilesDir(Environment.DIRECTORY_DCIM);
        File file = File.createTempFile(fileName, ".jpeg", directory);

        mCurrentPhotoResource = Uri.fromFile(file);

        return file;
    }

    private void destroyTemporaryFile() {
        File file = new File(mCurrentPhotoResource.getPath());

        if(file.delete()) {
            Timber.i("Temporary file deleted");
        } else {
            Timber.w("Failed to delete temporary file: " + file);
        }
    }

    private void handleImageCaptureResult(Intent intent) {
        if(mCurrentPhotoResource != null) {
            dispatchMediaScanIntent(mCurrentPhotoResource);

            if (mOnImageChosenListener != null) {
                mOnImageChosenListener.onImageChosen(mCurrentPhotoResource);
            } else {
                Timber.w("Parent Activity or Fragment does not implement OnImageChosenListener; captured result cannot be used");
            }
        }
    }

    private void handleImageSelectionResult(Intent intent) {
        Timber.d("Selection: " + intent.getData());
        if(mOnImageChosenListener != null) {
            mOnImageChosenListener.onImageChosen(intent.getData());
        } else {
            Timber.w("Parent Activity or Fragment does not implement OnImageChosenListener; selected result cannot be used");
        }
    }

}

And the DialogFactory class:

public final class DialogFactory {

    public static AlertDialog createRationaleAlert(
            Context context, @StringRes int title, @StringRes int message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context)
                .setTitle(title).setMessage(message);
        return createRationaleAlert(context, builder);
    }

    public static AlertDialog createRationaleAlert(
            Context context, CharSequence title, CharSequence message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context)
                .setTitle(title).setMessage(message);
        return createRationaleAlert(context, builder);
    }

    private static AlertDialog createRationaleAlert(
            Context context, AlertDialog.Builder builder) {
        builder.setPositiveButton(R.string.btn_try, (dialog, which) -> {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            context.startActivity(intent);
        }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> {
            dialog.cancel();
        });

        return builder.create();
    }

}

The issue occurs when the Fragment calls DialogFactory.createRationaleAlert().show().

like image 682
Bryan Avatar asked Oct 28 '25 14:10

Bryan


1 Answers

After much deliberation, I finally found a solution. The trick is to implement the DialogInterface.OnShowListener, and create the new Dialog in the onShow() callback:

public class ImageChooserDialogFragment extends BottomSheetDialogFragment
        implements DialogInterface.OnShowListener {

    @Override @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        super.onCreateDialog(savedInstanceState);
        getDialog().setOnShowListener(this);
    }

    @Override
    public void onShow(DialogInterface dialog) {
        if(!PermissionUtil.checkPermissions(getContext(), PERMISSIONS_STORAGE)) {
            requestPermissionsWithRationale(REQUEST_PERMISSION_STORAGE, PERMISSIONS_STORAGE);
        } else {
            displayRequestPermissionsAlert(R.string.msg_snackbar_permissions_storage);
        }
    }

    // ...

}
like image 58
Bryan Avatar answered Oct 30 '25 04:10

Bryan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!