I usually add a post_notification permission request to my app.
The strange thing is that when I request directly, the dialog will not pop up.
But if I create the Notification channel first, I can jump out of the dialog normally,
Has anyone encountered the same situation? Or is there something wrong with my settings? Thanks!
AndroidManifest.xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Fragement:
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
super.onViewCreated(view, savedInstanceState)
requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
LogUtil.d(TAG, "Grant POST_NOTIFICATION permission")
} else {
LogUtil.d(TAG, "Denied POST_NOTIFICATION permission")
}
}
}
#RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onResume()
{
if (ContextCompat.checkSelfPermission(
activity!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS,
) == PackageManager.PERMISSION_DENIED
) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
Situation 1
if I add createNotificationChannel() before request permisssion,it work.
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
"Important Notification Channel",
NotificationManager.IMPORTANCE_HIGH,
).apply {
description = "This notification contains important announcement, etc."
}
notificationManager.createNotificationChannel(channel)
}
Situation 2
When I mark createNotificationChannel(),the dialog would not showing anymore.
After adding the Log, I found that,
After executing requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS),
there is no log jumping out,
just get the return of registerForActivityResult = false,
but I opened the app for the first time, and I have not rejected the notifcaiton permission.
I want to know how to fix it?
Step # 1
Can you change this
== PackageManager.PERMISSION_DENIED
to
!= PackageManager.PERMISSION_GRANTED
Step # 2
Ask for shouldShowRequestPermissionRationale, if user denies permission for once, on this official documentation
Related
I am programming an app that connects to a device via Bluetooth, but every time I want to do something with the BluetoothDevice I have to insert a permission check like this (Kotlin):
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
42
)
}
Is there a workaround with one single permission check in the beginning of the app?
Thank you!
We have to check permission granted, otherwise it may crash your app.
but we can do in very handy way in Kotlin.
Follow below steps...
In your MainActivity or Very first activity, ask Bluetooth permission like below.
Create Permission Callback in Activity.
private val requestPermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.all { it.value }) Toast.makeText(
this,
"Permission Granted",
Toast.LENGTH_SHORT
).show()
else Toast.makeText(this, "not accepted all the permissions", Toast.LENGTH_LONG).show()
}
Request a permission in onCreate method of Activity.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//your code
requestPermissionsLauncher.launch(
arrayOf(android.Manifest.permission.BLUETOOTH_CONNECT)
) //asking permission whatever we need to run app.
}
Create a kotlin Extension function to make sure to run only on Bluetooth permission is Granted.
fun <T> Context.runOnBluetoothPermission(block: () -> T) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
block()
} else {
Toast.makeText(
this,
"Bluetooth permission need to work this.",
Toast.LENGTH_SHORT
).show()
}
}
Use it extension function wherever you need.
example :
In SecondActivity.kt
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//ui functions
//apicalls if any
//functions that only run on Bluetooth permission
runOnBluetoothPermission{
getAllBluetoothDevices()
}
}
private fun getAllBluetoothDevices(){
//your code to get all bluetooth devices.
}
}
The user can revoke the permission at any time, by going to the app settings. When the permission is revoked, the activity will be recreated. This means you have to check at least once after onCreate and before using the permission, if you still have the permission.
TL;DR
No, your app might crash.
I come to you in a time of great need. I am currently learning to use Kotlin for app development and as a "project" per-say, I am working on a simple "File manager". The current problem I am experiencing is that I am unable to read the directories and the files.
Using API 26
Using Kotlin
Using ViewModel
The permissions in the AndroidManifest.xml are set
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
The permission request in runtime is called in MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkPermission()) {
//permission allowed
val path = Environment.getExternalStorageDirectory().path
val filesAndFolders: Array<File>? = File(path).listFiles()
Log.d("FILETAG", path) // /storage/emulated/0
Log.d("FILETAG", filesAndFolders.toString()) // null
Log.d("FILETAG", File(path).exists().toString()) // true
Log.d("FILETAG", File(path).canRead().toString()) // false
} else {
//permission not allowed
requestPermission()
}
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow()
}
}
}
private fun checkPermission(): Boolean {
val result =
ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
)
return result == PackageManager.PERMISSION_GRANTED
}
private fun requestPermission(){
if(ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)){
Toast.makeText(this, "Storage permission is required", Toast.LENGTH_SHORT).show()
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
111
)
}
}
As commented in the code, the file array is returned as a "null", though the files seem to exist but are unreadable.
Additionally, I have tried executing this code from an inside of the fragment, but with the exact same results, though am required to read the files in a fragment rather than inside the MainActivity (But I first need to get this part of my code working before I move on to the fragments) and list the files in a RecyclerView.
This is my first question on Stackoverflow, if I missed any essential detail, let me know.
Please grant me your infinite knowledge, thank you.
I followed instructions to add HealthConnect service to the app, however, once the user approves requested permissions, there is no easy way for the user to modify them. They have to go to PlayStore, find HealthConnect, and open it (the icon was recently removed from the launcher).
How does one open health connect with Intent in Android? The following code does NOT work once the permissions are approved requestPermissions.launch(PERMISSIONS).
This is how complete code looks like:
class HealthConnectFragment: Fragment(R.layout.fragment_health_connect) {
private val analyticsHelper: analyticsHelper by inject()
private var _binding: FragmentHealthConnectBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
// Create the permissions launcher.
private val requestPermissionActivityContract = createRequestPermissionResultContract()
// Create the permissions launcher.
private val requestPermissions =
registerForActivityResult(requestPermissionActivityContract) { granted ->
if (granted.containsAll(HealthConnectApi.PERMISSIONS)) {
Timber.e("1 ALL PERMISSIONS GRANTED")
// Permissions successfully granted
} else {
// Lack of required permissions
Timber.e("1 LACKING PERMISSIONS")
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentHealthConnectBinding.bind(view)
analyticsHelper.logScreenView(
Event.AppSetup.HEALTH_CONNECT_SETUP_FRAGMENT,
HealthConnectFragment::class.simpleName!!)
// Set click listener depending on the permissions.
binding.connectButton.setOnClickListener {
if (HealthConnectClient.isAvailable(requireContext())) {
Timber.e("YAYYA")
launchHealthConnectPermissions()
} else {
// ...
Timber.e("NAYYA")
}
}
}
private fun launchHealthConnectPermissions() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
requestPermissions.launch(healthConnectApi.PERMISSIONS)
}
}
To summarize, when the app has not approved permissions this action takes me to HealthConnect app to approve the permissions. When the app has granted all permission, clicking on the button does nothing. I would like to ALWAYS launch the HealthConnect app.
Thank you
Users can also access Health Connect by navigating to Settings > Apps > Health Connect, or from their Quick Settings menu.
To open the settings with an intent, you can use the following:
val intent = context.packageManager.getLaunchIntentForPackage("com.google.android.apps.healthdata")
if (HealthConnectClient.isAvailable(context) && intent !== null) {
context.startActivity(intent)
}
I have an activity that requires camera permission.
this activity can be called from several user configurable places in the app.
The rationale dialog and permission dialog themselves should be shown before the activity opens.
right now I am trying to handle these dialogs in some kind of extension function.
fun handlePermissions(context: Context, required_permissions: Array<String>, activity: FragmentActivity?, fragment: Fragment?): Boolean {
var isGranted = allPermissionsGranted(context, required_permissions)
if (!isGranted) {
//null here is where I used to pass my listener which was the calling fragment previously that implemented an interface
val dialog = DialogPermissionFragment(null, DialogPermissionFragment.PermissionType.QR)
activity?.supportFragmentManager?.let { dialog.show(it, "") }
//get result from dialog? how?
//if accepted launch actual permission request
fragment?.registerForActivityResult(ActivityResultContracts.RequestPermission()) { success ->
isGranted = success
}?.launch(android.Manifest.permission.CAMERA)
}
return isGranted
}
But I am having trouble to get the dialog results back from the rationale/explanation dialog.
My research until now brought me to maybe using a higher order function, to pass a function variable to the dialog fragment that returns a Boolean value to the helper function. But I am absolutely unsure if thats the right thing.
I thought using my own code would be cleaner and less overhead, could I achieve this easier when using a framework like eazy-permissions? (is Dexter still recommendable since its no longer under development)
is that even a viable thing I am trying to achieve here?
One approach that I've implemented and seems viable to use is this:
Class PermissionsHelper
class PermissionsHelper(private val activity: Activity) {
fun requestPermissionsFromDevice(
arrayPermissions: Array<String>, permissionsResultInterface: PermissionsResultInterface
) {
setPermissionResultInterface(permissionsResultInterface)
getMyPermissionRequestActivity().launch(arrayPermissions)
}
fun checkPermissionsFromDevice(permission: String): Boolean {
return ContextCompat.checkSelfPermission(activity, permission) ==
PackageManager.PERMISSION_GRANTED
}
}
Class PermissionsResultInterface to the helper class be able to communicate with the activity.
interface PermissionsResultInterface {
fun onPermissionFinishResult(permissions: MutableMap<String, Boolean>)
}
Usage with this approach to remove all files from app directory:
private fun clearFiles(firstCall: Boolean = false) {
if (verifyStoragePermissions(firstCall)) {
val dir = File(getExternalFilesDir(null).toString())
removeFileOrDirectory(dir)
Toast.makeText(
applicationContext,
resources.getString(R.string.done),
Toast.LENGTH_SHORT
).show()
}
}
private fun verifyStoragePermissions(firstCall: Boolean = false): Boolean {
val arrayListPermissions = arrayOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
for (permission in arrayListPermissions) {
if (!PermissionsHelper(this).checkPermissionsFromDevice(permission)) {
if (firstCall) PermissionsHelper(this)
.requestPermissionsFromDevice(arrayListPermissions, this)
else PermissionsDialogs(this).showPermissionsErrorDialog()
return false
}
}
return true
}
override fun onPermissionFinishResult(permissions: MutableMap<String, Boolean>) {
clearFiles()
}
With this approach you are able to call the permissions helper and using the result interface, after each of the answers from user, decide wether you still need to make a call for permissions or show a dialog to him.
If you need any help don't hesitate to contact me.
In Android 11, when user select "deny" option for more than once for any permission request, then system will mark it as "Permanently denied".Once permanently denied, user has to enable in settings.From this time shouldShowRequestPermissionRationale() start's to return false
Three options are available for permission window , "Deny","Allow All time","Allow only this time". But in settings "Deny","Allow all the time","Ask every time" are present.
How to find when user selects "Ask me every time" from settings, because, checkSelfPermission() returns PackageManager.PERMISSION_DENIED,and shouldShowRequestPermissionRationale() return false. In this time I want to show permission window, instead of move to settings. Something similar to google map permission
Using the new ActivityResultsContract you can do this in the following manner
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
}
}
}
In OnCreate()[Make sure you ask permission in OnCreate , else application will crash] , ask the permission :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermissionLauncher.launch(REQUIRED_PERMISSIONS)
}
Ask requiredPermissions in the following manner :
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
)
Actually there is one more scenario:
if the user let you request the permission but don't chose any option and dismissed the dialog by tapping outside, the request finishes with denied and shouldShowRequestPermissionRationale() returns false.
That is the exact same behaviour as if the user selects don't ask again.
the permission where requested once, it results in denied and we should not show a explanation.
therefore we have to track if shouldShowRequestPermissionRationale() has returned true for once. if it switches back to false its denied permanent.
Use the following method in your activity (Camera permission is used in this example):
private fun requestPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
//Permission is denied
} else {
//ask permission
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA)
}
}
}
You only need to check the shouldShowRequestPermissionRationale() after user deny the permission
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
} else {
if(shouldShowRequestPermissionRationale(Manifest.permission.YOUR_RUNTIME_PERMISSION)){
//user hasn't denied permission permanently
}else{
//user has denied permanently,
//you should try to redirect user to settings
//to ask user grant it manually
}
}
}
requestPermissionLauncher.launch(Manifest.permission.YOUR_RUNTIME_PERMISSION)