I am working with camera permissions. when the user clicks on take photo button, user will be shown with run time permissions menu and lets say user deny's it and then clicks on take photo button, run time permissions will be shown second time.
after this clicking the take photo button nothing happens.
What I want to do is, after the second attempt, i want to show a popup telling the user to go to settings to change the permissions.
How can I know if the user has denied the permission twice.
this is what I have coded so far
takePhotoBtn.setOnClickListener {
takePhoto()
}
private fun takePhoto() {
activity?.let {
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
}
}
private val permReqLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val granted = permissions.entries.all {
it.value == true
}
if (granted) {
capturePhoto()
}
}
private fun capturePhoto() {
onUtilityBillTypeListener.onUtilityBillTypePhotoLink(true)
}
where is the right place to add this dinielDialog
private fun showPermissionDeniedDialog() {
AlertDialog.Builder(this.requireContext())
.setTitle("Permission Denied")
.setMessage("Permission is denied, Please allow permissions from App Settings.")
.setPositiveButton("Settings",
DialogInterface.OnClickListener { dialogInterface, i ->
// send to app settings if permission is denied permanently
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", getPackageName(this.requireContext()), null)
intent.data = uri
startActivity(intent)
})
.setNegativeButton("Cancel", null)
.show()
}
You could write something like this
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
if (permissionDeniedFlag) {
showPermissionDeniedDialog()
} else {
permissionDeniedFlag = True
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
With an initial declaration of var permissionDeniedFlag = False.
On your code where you're getting permission result you can do it like this :
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
isGranted: Boolean ->
if (isGranted) //perform action
else {
val builder = AlertDialog.Builder(requireContext())
builder.setTitle("Permission Required!")
builder.setMessage("We need permission in order to perform this action.")
builder.setPositiveButton("OK") { dialog, _ ->
showPermRationale()
dialog.cancel()
}
builder.setNegativeButton("CANCEL") { dialog, _ ->
dialog.cancel()
}
builder.show()
}
}
private fun showPermRationale() {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts(
"package", requireActivity().packageName, null)
intent.data = uri
requireActivity().startActivity(intent)
}
resolved it by adding this code
private fun takePhoto() {
activity?.let {
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this.requireActivity(),
Manifest.permission.CAMERA
)
) {
showPermissionDeniedDialog()
}
else{
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
}
}
}
Related
I'm using the accompanist library for handling permissions in jetpack compose. The sample code in the docs doesn't have a scenario to handle permissions such as checking permission on button clicks.
So My scenario is I wanted to check runtime permission on the button click and if the permission is granted do the required work or show the snackbar if not granted. But I can't figure out how can i check if permission was denied permanently or not.
I want a similar behavior like this library has https://github.com/Karumi/Dexter
val getImageLauncher = rememberLauncherForActivityResult(
contract = GetContent()
) { uri ->
uri?.let {
viewModel.imagePicked.value = it.toString()
}
}
// Remember Read Storage Permission State
val readStoragePermissionState = rememberPermissionState(
permission = READ_EXTERNAL_STORAGE
) { result ->
if (result) {
getImageLauncher.launch("image/*")
} else {
// How can i check here if permission permanently denied?
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(
context.getString(R.string.read_storage_denied)
)
}
}
}
Here's the code of the button on which when I click I want to check the permission
SecondaryOutlineButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
buttonText = stringResource(
id = R.string.upload_image
),
buttonCornerRadius = 8.dp,
) {
readStoragePermissionState.launchPermissionRequest()
}
For those looking for a similar scenario. To handle permissions in jetpack compose properly I followed the below steps:
When the button is clicked first check if the permission is already granted. If it's already granted then simply do the work you needed to do.
If it's not granted we will check the case for shouldShouldRational is false. If it's false we have two scenarios to check because the shouldShowRationale is false in two cases. First when the permission is permanently denied. Second when permission is not even asked at once. For managing, if permission is permanently denied or not I have used shared preferences. I have written extension functions for that which tell us if the permission is asked for once.
For the above first case, I'll show the snack bar telling the user that you permanently denied the permission open settings to allow the permission. For the above second case, I will launch the request for showing the system permission dialog and update the shared preference via the extension function.
And for the case in which shouldShowRationale is true. I'll show a snack bar to the user explaining why permission is required. Along with the action, to again request the system permission dialog.
Finally whenever permission is granted I can do the work needed in the rememberPermissionState callback.
val context = LocalContext.current
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
val getImageLauncher = rememberLauncherForActivityResult(
contract = GetContent()
) { uri ->
uri?.let {
viewModel.imagePicked.value = it.toString()
}
}
// Remember Read Storage Permission State
val readStoragePermissionState = rememberPermissionState(
permission = READ_EXTERNAL_STORAGE
) { granted ->
if (granted) {
getImageLauncher.launch("image/*")
}
}
Button Composable
SecondaryOutlineButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
buttonText = stringResource(
id = R.string.upload_image
),
buttonCornerRadius = 8.dp,
) {
// This is onClick Callback of My Custom Composable Button
with(readStoragePermissionState) {
when {
// If Permission is Already Granted to the Application
status.isGranted -> {
getImageLauncher.launch("image/*")
}
// If Permission is Asked First or Denied Permanently
!status.shouldShowRationale -> {
context.isPermissionAskedForFirstTime(
permission = permission
).also { result ->
if (result) {
launchPermissionRequest()
context.permissionAskedForFirsTime(
permission = permission
)
} else {
coroutineScope.launch {
with(scaffoldState.snackbarHostState) {
val snackbarResult =
showSnackbar(
message = context.getString(
R.string.read_storage_denied
),
actionLabel = context.getString(
R.string.settings
)
)
when (snackbarResult) {
// Open this Application General Settings.
SnackbarResult.ActionPerformed -> {
context.openApplicationSettings()
}
SnackbarResult.Dismissed -> Unit
}
}
}
}
}
}
// If You should Tell User Why this Permission Required
status.shouldShowRationale -> {
coroutineScope.launch {
with(scaffoldState.snackbarHostState) {
val snackbarResult = showSnackbar(
message = context.getString(
R.string.read_storage_rational
),
actionLabel = context.getString(
R.string.allow
)
)
when (snackbarResult) {
// Request for System Permission Dialog Again.
SnackbarResult.ActionPerformed -> {
launchPermissionRequest()
}
SnackbarResult.Dismissed -> Unit
}
}
}
}
else -> Unit
}
}
}
Extension Functions
fun Context.isPermissionAskedForFirstTime(
permission: String
): Boolean {
return getSharedPreferences(
packageName, MODE_PRIVATE
).getBoolean(permission, true)
}
fun Context.permissionAskedForFirsTime(
permission: String
) {
getSharedPreferences(
packageName, MODE_PRIVATE
).edit().putBoolean(permission, false).apply()
}
fun Context.openApplicationSettings() {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.parse("package:${packageName}")
})
}
I'm using implementation "com.google.accompanist:accompanist-permissions:0.25.0"
I used Philipp Lackner's tutorial for this. He creates an extension method in case the permission is permanently denied.
So in your button Code you would have a method doing this:
Manifest.permission.CAMERA -> {
when {
perm.status.isGranted -> {
PermissionText(text = "Camera permission accepted.")
}
perm.status.shouldShowRationale -> {
PermissionText(text = "Camera permission is needed to take pictures.")
}
perm.isPermanentlyDenied() -> {
PermissionText(text = "Camera permission was permanently denied. You can enable it in the app settings.")
}
}
}
And the extension would be:
#ExperimentalPermissionsApi
fun PermissionState.isPermanentlyDenied(): Boolean {
return !status.shouldShowRationale && !status.isGranted
}
Here is the code that does exactly what you are asking:
Click a button (FAB), if the permission is already granted, start working. If the permission is not granted, check if we need to display more info to the user (shouldShowRationale) before requesting and display a SnackBar if needed. Otherwise just ask for the permission (and start work if then granted).
Keep in mind that it is no longer possible to check if a permission is permanently denied. shouldShowRationale() works differently in different versions of Android. What you can do instead (see code), is to display your SnackBar if shouldShowRationale() returns true.
#Composable
fun OptionalPermissionScreen() {
val context = LocalContext.current.applicationContext
val state = rememberPermissionState(Manifest.permission.CAMERA)
val scaffoldState = rememberScaffoldState()
val launcher = rememberLauncherForActivityResult(RequestPermission()) { wasGranted ->
if (wasGranted) {
// TODO do work (ie forward to viewmodel)
Toast.makeText(context, "📸 Photo in 3..2..1", Toast.LENGTH_SHORT).show()
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState,
floatingActionButton = {
val scope = rememberCoroutineScope()
val snackbarHostState = scaffoldState.snackbarHostState
FloatingActionButton(onClick = {
when (state.status) {
PermissionStatus.Granted -> {
// TODO do work (ie forward to viewmodel)
Toast.makeText(context, "📸 Photo in 3..2..1", Toast.LENGTH_SHORT).show()
}
else -> {
if (state.status.shouldShowRationale) {
scope.launch {
val result =
snackbarHostState.showSnackbar(
message = "Permission required",
actionLabel = "Go to settings"
)
if (result == SnackbarResult.ActionPerformed) {
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.packageName, null)
)
startActivity(intent)
}
}
} else {
launcher.launch(Manifest.permission.CAMERA)
}
}
}
}) {
Icon(Icons.Rounded.Camera, contentDescription = null)
}
}) {
// the rest of your screen
}
}
Video of how this works by clicking here.
this is part of a blog post I wrote on permissions in Jetpack Compose.
I tried to implement request permissions for writing and reading from storage. Everything worked good but today Android showed me that the method onRequestPermissionsResult(...) is deprecated. There are so many questions about this topic in StackOverflow, but unfortunately, they are outdated.
I called the methods below in a fragment.
It was suggested simply to call:
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE)
instead of my approach:
ActivityCompat.requestPermissions(getActivity(),
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE))
But both of them show that onRequestPermissionsResult(...) is deprecated.
Here is my onRequestPermissionsResult(...)-method:
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode == StorageKeys.STORAGE_PERMISSION_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
exportBibTex.createBibFile();
exportBibTex.writeBibFile(exportBibTex
.getBibDataLibrary(libraryModel, bookDao, noteDao));
Toast.makeText(getContext(),
getString(R.string.exported_file_stored_in) + '\n'
+ File.separator + StorageKeys.DOWNLOAD_FOLDER + File.separator + fileName
+ StorageKeys.BIB_FILE_TYPE, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), R.string.storage_permission_denied,
Toast.LENGTH_SHORT).show();
}
}
}
Here is a simple alert dialog, in which I call the onRequestPermissionsResult(...):
private void showRequestPermissionDialog() {
AlertDialog.Builder reqAlertDialog = new AlertDialog.Builder(getContext());
reqAlertDialog.setTitle(R.string.storage_permission_needed);
reqAlertDialog.setMessage(R.string.storage_permission_alert_msg);
reqAlertDialog.setPositiveButton(R.string.ok,
(dialog, which) -> ActivityCompat.requestPermissions(getActivity(),
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
StorageKeys.STORAGE_PERMISSION_CODE));
reqAlertDialog.setNegativeButton(R.string.cancel,
(dialog, which) -> dialog.dismiss());
reqAlertDialog.create().show();
}
Is there any alternative for onRequestPermissionsResult(...), that I can use?
The onRequestPermissionsResult() method is deprecated in androidx.fragment.app.Fragment.
So you may use registerForActivityResult() method instead of onRequestPermissionsResult().
You can refer this URL.
Following is Kotlin code, but you can refer it:
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// PERMISSION GRANTED
} else {
// PERMISSION NOT GRANTED
}
}
// Ex. Launching ACCESS_FINE_LOCATION permission.
private fun startLocationPermissionRequest() {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
I added java code from following URL.
How to get a permission request in new ActivityResult API (1.3.0-alpha05)?
private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
new ActivityResultCallback<Boolean>() {
#Override
public void onActivityResult(Boolean result) {
if (result) {
// PERMISSION GRANTED
} else {
// PERMISSION NOT GRANTED
}
}
}
);
// Ex. Launch the permission window -- this is in onCreateView()
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION);
}
});
You can also request multiple permissions:
val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.d("DEBUG", "${it.key} = ${it.value}")
}
}
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
A simple way in Kotlin
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
class MyFragment : Fragment() {
companion object {
val TAG: String = MyFragment::class.java.simpleName
var PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permReqLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val granted = permissions.entries.all {
it.value == true
}
if (granted) {
displayCameraFragment()
}
}
private fun takePhoto() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
displayCameraFragment()
return
}
activity?.let {
if (hasPermissions(activity as Context, PERMISSIONS)) {
displayCameraFragment()
} else {
permReqLauncher.launch(
PERMISSIONS
)
}
}
}
// util method
private fun hasPermissions(context: Context, permissions: Array<String>): Boolean = permissions.all {
ActivityCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
private fun displayCameraFragment() {
// open camera fragment
}
}
Alternative registerForActivityResult
Example use:
private fun permissionSetup() {
val permission = ContextCompat.checkSelfPermission(
requireContext(), Manifest.permission.READ_CONTACTS)
if (permission != PackageManager.PERMISSION_GRANTED) {
permissionsResultCallback.launch(Manifest.permission.READ_CONTACTS)
} else {
println("Permission isGranted")
}
}
private val permissionsResultCallback = registerForActivityResult(
ActivityResultContracts.RequestPermission()){
when (it) {
true -> { println("Permission has been granted by user") }
false -> {
Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
}
}
Register the permissions callback in your activity or fragment. which handle user permission
Example-Storage permission
private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result) {
//Permission granted
} else {
//permission denied
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, WRITE_EXTERNAL_STORAGE)) {
//show permission snackbar
} else {
//display error dialog
}
}
});
Ask for the permission.The registered ActivityResultCallback gets the result of this request.
if (checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else {
//Permission already granted
}
This works for me - (kotlin):
private fun checkPermissions() {
if (mContext?.let {
ContextCompat.checkSelfPermission(
it,
READ_EXTERNAL_STORAGE
)
} != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Request Permissions")
requestMultiplePermissions.launch(
arrayOf(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE))
} else {
Log.d(TAG, "Permission Already Granted")
}
}
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
Log.d(TAG, "${it.key} = ${it.value}")
}
if (permissions[READ_EXTERNAL_STORAGE] == true && permissions[WRITE_EXTERNAL_STORAGE] == true) {
Log.d(TAG, "Permission granted")
} else {
Log.d(TAG, "Permission not granted")
}
}
Most of the answers address the OP requirement. But I have found few things that are missing so I thought to provide a complete example (in Koltin)
class ProfileFragment : Fragment(){
private lateinit var permissionRequest : ActivityResultLauncher<Array<String>>
companion object {
val LOCATION_PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
}
private fun getGpsLocation() {
if(activity != null){
permissionRequest.launch(LOCATION_PERMISSIONS)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.locBtn.setOnClickListener { getGpsLocation() }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerPermissionRequest()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false)
return binding.root
}
private fun registerPermissionRequest(){
var permissionCount = 0
permissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
if(it.value){
permissionCount++
}
}
if(permissionCount == 2){
getMyLocation()
}
}
}
}
The things that are missing are
A)Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()). Other wise it won't work and the app would crash.
Error:
java.lang.IllegalStateException: Fragment ProfileFragment{bf12414} (210ad5a1-3286-4586-a48f-deac1d8e3eef
id=0x7f09008b) is attempting to registerForActivityResult after being
created. Fragments must call registerForActivityResult() before they
are created (i.e. initialization, onAttach(), or onCreate()).
B) It is recommended to request permission when it is really needed. In my example when user clicks on Button with id locBtn, permission dialog is shown rather than showing when activity/fragment is created.
Please refer to the official documentation: https://developer.android.com/training/permissions/requesting
Review the following, which can be found in the documentation.
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
val requestPermissionLauncher =
registerForActivityResult(RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
}
After which, launch the request
when {
ContextCompat.checkSelfPermission(
CONTEXT,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
}
//Is not needed for it to work, but is a good practice as it plays a role
//in letting user know why the permission is needed.
shouldShowRequestPermissionRationale(...) -> {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...)
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION)
}
}
Suppose we need to take some permissions like audio and camera. Then we can put it on a variable of array.
private val REQUESTED_PERMISSIONS =
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
This registerForActivityResult invoke when we launch with multiple permission array.
private val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.d("DEBUG", "${it.key} = ${it.value}")
if (!it.value){
return#registerForActivityResult
}
}
init()
}
Now we make a function to check permission is already granted or not.
private fun checkSelfPermission(): Boolean {
return !(ContextCompat.checkSelfPermission(
requireContext(),
REQUESTED_PERMISSIONS[0]
) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(
requireContext(),
REQUESTED_PERMISSIONS[1]
) != PackageManager.PERMISSION_GRANTED)
}
Now we check permission is granted or not from onCreate method. If permission is not granted then we invoke requestMultiplePermissions.launch(REQUESTED_PERMISSIONS) like
super.onCreate(savedInstanceState)
if (!checkSelfPermission()) {
requestMultiplePermissions.launch(REQUESTED_PERMISSIONS)
}
}
You can use some external library for permission handling to reduce some boilerplate code. I use Dexter library. Rx Permissions is also good choice if you are using RxJava2.
Try this in in your pubspec.yamal under flutter section:
plugin:
androidPackage: com.ly.permission
pluginClass: PermissionPlugin
It works with me.
Since introducing new location permission changes on Android 11 (https://developer.android.com/about/versions/11/privacy/location) there is a need to more carefully work with permissions. We are now able to request background location permission only twice
I want to show the user a dialog dependent on the state of their location permission.
The issue is that system handles click outside of the dialog the same as denying the permission, but it apparently doesn't count it in PermissionsUtil.shouldShowRequestStoragePermissionRationale limit. So it is then hard to distinguish which state the user is in.
My initial question was: How to deal with this specific situation?
But I guess more useful is the general question:
How to recognize if the user pressed outside of the system permission dialog or if he directly denied the permission?.
Here is how I solved it:
private val btRequestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val allPermissionsGranted = permissions.values.none { !it }
if (allPermissionsGranted) {
callback?.invoke().apply { this#PermissionActivity.callback = null }
} else if (Manifest.permission.BLUETOOTH_CONNECT in requiredPermissions || Manifest.permission.ACCESS_FINE_LOCATION in requiredPermissions) {
if (!shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT) && !shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) && (this.getPreferenceBoolean("NEVER_SHOW_AGAIN", false) || shouldShowRationaleBefore)) {
this.getPreferenceBoolean("NEVER_SHOW_AGAIN", true)
//A dialog box with GO TO SETTING button
} else {
shouldShowRationaleBefore = true
requestBluetoothPermissions()
}
} else
requestBluetoothPermissions()
}
private var shouldShowRationaleBefore = false
protected fun requestBluetoothPermissions(callBack: (() -> Unit)? = null) {
this.callback = callBack
when {
areAllPermissionsGranted() -> {
callBack?.invoke()
}
shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT) -> {
//A dialog box with CONTINUE button and callBack to btRequestPermissionLauncher.launch(...)
}
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) -> {
//A dialog box with CONTINUE button and callBack to btRequestPermissionLauncher.launch(...)
}
!areAllPermissionsGranted() -> {
if (!shouldShowRationaleBefore)
btRequestPermissionLauncher.launch(requiredPermissions)
else {
private var shouldShowRationale = false
//A dialog box with CONTINUE button and callBack to btRequestPermissionLauncher.launch(...)
//This is where the click outside the permission dialog box is handled
}
}
}
}
Based on the result per permission you can add it to a list and then you could know if the user denied / rejected the permissions completely . It can be done in the following way :
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result: MutableMap<String, Boolean> ->
val deniedList: List<String> = result.filter {
!it.value
}.map {
it.key
}
when {
deniedList.isNotEmpty() -> {
val map = deniedList.groupBy { permission ->
if (shouldShowRequestPermissionRationale(permission)) DENIED else EXPLAINED
}
map[DENIED]?.let {
// request denied , request again
}
map[EXPLAINED]?.let {
//request denied ,send to settings
}
}
else -> {
//All request are permitted
}
}
}
I have multiple ImageViews in a fragment in my application. I want the user to select an image for each imageView. The question is, I don't know for which ImageView the user selected an image.
I thought that if I add a request code, I can fix this problem according to the code received.
I can't add request code to the method I'm using for the user to select an image.
I do a permission check first when the button is pressed , I open the gallery if it's permission granted. :
// First ImageView onClick
bind.addQuestionImage.setOnClickListener{
if (!checkPermission())
requestSinglePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery()
}
// Second ImageView onClick
bind.addAnswerImage.setOnClickListener{
if (!checkPermission())
requestSinglePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery()
}
My Permission request method :
private fun checkPermission() = ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
The method where I listen to the response to the request :
private val requestSinglePermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
toaster("Permission Granted.")
} else {
toaster("Permission Denied.")
}
}
I open the gallery if the user has permission :
private fun openGallery(){
val galleryIntent = Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
getContent.launch(galleryIntent)
}
I get the content in this method :
// get content
private val getContent =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
val myUri : Uri? = it.data?.data
// Which ImageView is this incoming uri ?
}
How can I solve this problem ? Or is there a better method ?
If you have two ImageView, you have to create two registerForActivityResult variables because there is no request code
// First ImageView onClick
bind.addQuestionImage.setOnClickListener{
if (!checkPermission())
firstPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery()
}
// Second ImageView onClick
bind.addAnswerImage.setOnClickListener{
if (!checkPermission())
secondPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery()
}
And
private val firstPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
toaster("Permission Granted.")
} else {
toaster("Permission Denied.")
}
}
private val secondPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
toaster("Permission Granted.")
} else {
toaster("Permission Denied.")
}
}
You can add the data you require to the Intent, like so:
private fun openGallery(source: String){
val galleryIntent = Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply{
putExtra(IMAGE_VIEW_SOURCE, source)
}
getContent.launch(galleryIntent)
}
and then read it on your response:
private val getContent =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
val intent : Intent? = it.data
val myUri : Uri? = intent?.data
val source = intent?.getStringExtra(IMAGE_VIEW_SOURCE)
source?.let{
if(it == ANSWER_BUTTON){
//.. do stuff
}
else if(it == QUESTION_BUTTON) {
//do other stuff
}
}
}
and your calling code:
// First ImageView onClick
bind.addQuestionImage.setOnClickListener{
if (!checkPermission())
requestSinglePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery(QUESTION_BUTTON)
}
// Second ImageView onClick
bind.addAnswerImage.setOnClickListener{
if (!checkPermission())
requestSinglePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
else openGallery(ANSWER_BUTTON)
}
Of course QUESTION_BUTTON, ANSWER_BUTTON and IMAGE_VIEW_SOURCE would be constants you define
Before a few days, the code is working fine not having issues with permissions.
I am not able to grant permission at run-time also having issues while granting permissions from the setting. (App permission detail page).
var permissions = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
}
Requesting For Permissions on the appropriate button click.
permissionResultLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
var allPermissionGranted = true
permissions.entries.forEach {
if (!it.value) {
Log.e(TAG, "Permission ${it.key} granted : ${it.value}")
allPermissionGranted = false
}
}
if (!permissionDeniedDialog && !allPermissionGranted) {
showDialog(
"Required",
"App needs Bluetooth and Location permissions to scan bluetooth devices.",
"Later",
"Allow",
object : DialogView.ButtonListener {
override fun onNegativeButtonClick(dialog: AlertDialog) {
dialog.dismiss()
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", requireActivity().applicationContext.packageName, null))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
override fun onPositiveButtonClick(dialog: AlertDialog) {
dialog.dismiss()
requestRequirdPermissions()
}
})
} else {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", requireActivity().applicationContext.packageName, null))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
permissionDeniedDialog = true
}
permissionResultLauncher.launch(permissions)
Function for checking permissions
private fun checkPermission(): Boolean {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED) return false
}
return true
}
Can someone let me know why the above not working?. the system was on the result directly and the app was redirecting the app to the setting page, but I am not able to grant permission from the settings page too.
Is this OS Specific?, anyone having the same issues with ColorOS 11 ?.
Please guide me if anything is missing from my side.
Device: OPPO F17 Pro
OS: Color OS 11, Android 11 Based
Note:
Above code, with Samsung device, Android 11 Based (OneUI 3.1), App was not asking for runtime but after redirecting on setting page, I am granting location permission and app was working fine, regarding OPPO i am not able grant permission from setting page.
What tried :
var permissions = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_COARSE_LOCATION)
}
private fun requestRequirdPermissions() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
permissionResultLauncher.launch(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
}
else
{
permissionResultLauncher.launch(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION ))
}
}
Try this way may help you
fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var permissions: ArrayList<String> = ArrayList<String>()
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
val listPermissionsNeeded: ArrayList<String> = ArrayList()
for (p in permissions) {
var result = ContextCompat.checkSelfPermission(this, p!!)
if (result != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(p)
}
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(
this,
listPermissionsNeeded.toTypedArray(),
CODE
)
} else {
next()
}
} else {
next()
}
}
override fun onRequestPermissionsResult(
requestc: Int,
permissions: Array<String>,
grantRes: IntArray
) {
super.onRequestPermissionsResult(requestc, permissions, grantRes);
when (requestc) {
CODE -> {
var isGrant = true
if (grantRes.size > 0) {
for (i in 0 until grantResults.size) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
isGrant = false
}
}
}
if (isGrant) {
next()
} else {
checkRationalePermission(permissions)
}
}
}
}
var alertDialogRatinal: android.app.AlertDialog? = null
fun checkRationale(permissions: Array<String>) {
if (alertDialogRatinal != null && alertDialogRatinal!!.isShowing || permissions.size == 0) {
return
}
var someDenied = false
for (permission in permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this#Activity,
permission!!
)
) {
} else {
if (ActivityCompat.checkSelfPermission(
this#Activity,
permission
) == PackageManager.PERMISSION_GRANTED
) {
} else {
someDenied = true
}
}
}
if (someDenied) {
val alertDialogBuilder =
android.app.AlertDialog.Builder(this#Activity)
alertDialogRatinal = alertDialogBuilder.setTitle("Permissions Required")
.setMessage(
"Please open settings, go to permissions and allow them."
)
.setPositiveButton(
"Settings"
) { dialog, which ->
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts(
"package",
this#Activity.getPackageName(),
null
)
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivityForResult(intent, 100)
}
.setNegativeButton(
"Cancel"
) { dialog, which -> }
.setCancelable(false)
.create()
alertDialogRatinal!!.show()
}
}
My issue was fixed by the system reset, but according to comments and documents please follow the below guidelines for the location permission.
Thanks Pawel for comments,
Ref: https://stackoverflow.com/a/66321942/9909365
Background location permission does not work like other permissions. It's a request to elevate location permission from foreground-only to foreground & background.
User has to consciously select "Allow all the time" in order to do that and grant background location permission. Otherwise that permission is considered denied.
You cannot even request background location unless foreground location is already granted - when system permission activity shows up it should already have option 2 or 3 selected.
See https://developer.android.com/training/location/permissions#request-background-location
Note:
When your app is using this background location please be prepared with a short video that demonstrates the location-based feature in your app that requires access to location in the background (while the app is not in use).
See https://support.google.com/googleplay/android-developer/answer/9799150
private val requestIdMultiplePermissions = 1
private val permissionsRequest: ArrayList<String> =
arrayListOf(READ_CALENDAR
, CAMERA)
findViewById<Button>(R.id.multiplePermissionBtn).setOnClickListener {
if (checkMultipleRequestPermissions()) {
doOperation()
}
}
private fun doOperation() {
Toast.makeText(this, "Successfully granted", Toast.LENGTH_LONG).show()
}
private fun checkMultipleRequestPermissions(): Boolean {
val listPermissionsNeeded: MutableList<String> = ArrayList()
for (p in permissionsRequest) {
val result = ContextCompat.checkSelfPermission(this, p)
if (result != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(p)
}
}
if (listPermissionsNeeded.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
listPermissionsNeeded.toTypedArray(),
requestIdMultiplePermissions
)
return false
}
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == requestIdMultiplePermissions) {
if (grantResults.isNotEmpty()) {
var isGrant = true
for (element in grantResults) {
if (element == PackageManager.PERMISSION_DENIED) {
isGrant = false
}
}
if (isGrant) {
doOperation()
} else {
var someDenied = false
for (permission in permissions) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(
this,
permission
)
) {
if (ActivityCompat.checkSelfPermission(
this,
permission
) == PackageManager.PERMISSION_DENIED
) {
someDenied = true
}
}
}
if (someDenied) {
settingActivityOpen()
}else{
showDialogOK { _: DialogInterface?, which: Int ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> checkMultipleRequestPermissions()
DialogInterface.BUTTON_NEGATIVE -> { }
}
}
}
}
}
}
}
private fun settingActivityOpen() {
Toast.makeText(
this,
"Go to settings and enable permissions",
Toast.LENGTH_LONG
)
.show()
val i = Intent()
i.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
i.addCategory(Intent.CATEGORY_DEFAULT)
i.data = Uri.parse("package:$packageName")
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
startActivity(i)
}
private fun showDialogOK(okListener: DialogInterface.OnClickListener) {
MaterialAlertDialogBuilder(this)
.setMessage("All Permission required for this app")
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show()
}