I'm making an Android App that adds a map to a activity, the user can center the map with their current location using the "location layer". to add the location button in the map with:
mMap.isMyLocationEnabled = true
I'm checking for the ACCESS_FINE_LOCATION permission inside onMapReady method and everything works fine, but I need finish the activiy and re-open it to see the changes (the location button).
So, I'm using onRequestPermissionsResult to check the user response, but when I call mMap.isMyLocationEnabled = true Android Studio says:
Call requires permisson which may be rejected by user: code shoud
explicitly check to see if permission is available (with
checkPermission) or explicitly handle a potencial SecurityException...
This is my code:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap.uiSettings.isCompassEnabled = true
if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),SOLICITA_UBICACION_CLAVE)
}else{
mMap.isMyLocationEnabled = true
}
mMap.uiSettings.isMyLocationButtonEnabled = true
mMap.uiSettings.isZoomControlsEnabled = true
// Add a marker in Sydney and move the camera
val sydney = LatLng(-34.0, 151.0)
mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
android.R.id.home -> {
onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when(requestCode){
SOLICITA_UBICACION_CLAVE -> {
if((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)){
mMap.isMyLocationEnabled = true
Toast.makeText(this#MapsActivity,"Permiso garantizado",Toast.LENGTH_SHORT).show()
}else{
mMap.isMyLocationEnabled = false
Toast.makeText(this#MapsActivity,"Permiso denegado",Toast.LENGTH_SHORT).show()
}
}
}
}
So, How can I call mMap.isMyLocationEnabled = true inside onRequestPermissionsResult?,
What's the purpose to check a permission inside when instruction for the request code if is supposed that is currently granted?
So, How can I call mMap.isMyLocationEnabled = true inside onRequestPermissionsResult?,
Do it the way that you are, and add the appropriate #SuppressLint annotation to suppress the warning. There should be a quick-fix for this in Android Studio that will add#SuppressLint with the right property.
Presumably, there is some bug or limitation in the Lint rule that is causing your problem. For example, this is a relatively old Lint check, and so perhaps it is not handling all Kotlin scenarios correctly.
Related
In Android 13, I need a basic flow to get permission for push notifications:
class MainActivity : ComponentActivity(), LocationListener {
val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.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
// feature 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.
}
}
private fun requestPushNotificationPermissions(){
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)) {
// granted
}else {
// not granted, ask for permission
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
This is what happened:
when user first installed the app, checkSelfPermission returns not granted, and we then lauch permission launcher
user sees the permission dialog in Android 13
user selects Allow
Expected: registerForActivityResult callback will be fired with isGranted true
Actual: registerForActivityResult callback is not fired.
Same if user selects Not Allow. callback is never fired. Why?
This is my dependencies:
implementation 'androidx.activity:activity-ktx:1.2.0-alpha07'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha07'
sadly can't help why it doesn't work. But I used EasyPermission for handling the permissons request and it works fine.
https://github.com/googlesamples/easypermissions
Turns out, the registerForActivityResult callback never fires, because somewhere in the Activity, there is this piece of old function "onRequestPermissionsResult" that is accidentally catching all permissions callback:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == locationPermissionCode) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Location permission granted
}
else {
//Location permission denied
}
}else{
//Notification permission callback accidentally landed here silently
}
}
Hope this helps someone.
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.
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)
I followed this tutorial to implement a new approach to request application permissions via Results API by RequestMultiplePermissions contract. Although the permission dialog is shown and permission result is propagated through the system to application preferences etc., my provided ActivityResultCallback is not notified at all.
Here are my source codes. I am aware I am not checking whether the user hasn't declined the permission already:
private fun checkPermissions() {
val permissionList = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION
)
val notGrantedPermissions = permissionList.map {
Pair(
it, ContextCompat.checkSelfPermission(
applicationContext,
it
)
)
}.filter {
it.second != PackageManager.PERMISSION_GRANTED
}
.map { it.first }
.toTypedArray()
if (notGrantedPermissions.isEmpty()) {
nextActivity()
} else {
requestPermissionLauncher.launch(notGrantedPermissions)
}
}
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result ->
info("> requestPermissionLauncher - ${result.values}")
if (result.values.all { it }) {
nextActivity()
} else {
longToast("All permissions are required for app to work correctly")
checkPermissions()
}
}
Did I miss anything in the documentation?
Library version: androidx.activity:activity-ktx:1.2.0-alpha06
MinSdkVersion: 21
TargetSdkVersion: 29
I was up against this same issue tonight. I'm betting you're either extending AppCompatActivity or something similar. The issue with that is, when super.onRequestPermissionsResult is invoked, it falls to the FragmentActivity implementation which does not, itself, invoke the super method so the chain dies there. The quick solution is to extend ComponentActivity directly. However, if this is not feasible for your solution, you can override the method as follows:
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
activityResultRegistry.dispatchResult(requestCode, Activity.RESULT_OK, Intent()
.putExtra(EXTRA_PERMISSIONS, permissions)
.putExtra(EXTRA_PERMISSION_GRANT_RESULTS, grantResults))
}
The above is a direct port from the ComponentActivity method.
I've made a LocationManager to handle permission issues and return lastLocation from FusedLocationProviderClient. It uses RxJava heavily to avoid callback hell. Here's the code:
object LocationManager {
// using coarse location to not ask for GPS enabling
// after changing to ACCESS_FINE_LOCATION don't forget to check GPS settings
private const val LOCATION_PERMISSION = Manifest.permission.ACCESS_COARSE_LOCATION
private var permissionSubject = AsyncSubject.create<Boolean>()
fun loadLastLocation(activity: Activity): Observable<Location?> =
checkPermission(activity).flatMap { hasPermission ->
// check permission, then request last location
Log.d("qwerty", "hasPermission=$hasPermission")
if (hasPermission) requestLastLocation(activity)
else Observable.error<Location>(Exception("Permission not granted"))
}
private fun checkPermission(activity: Activity): Observable<Boolean> =
if (activity.hasPermission(LOCATION_PERMISSION)) Observable.just(true)
else requestPermission(activity)
private fun requestPermission(activity: Activity): Observable<Boolean> = permissionSubject.apply {
// result will be posted to subject later
Log.d("qwerty", "requestPermission $LOCATION_PERMISSION")
ActivityCompat.requestPermissions(activity, arrayOf(LOCATION_PERMISSION), RequestCode.LOCATION_PERMISSION)
}
// call this from hosting activity or you can never get lastLocation
fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {
if (requestCode == RequestCode.LOCATION_PERMISSION) {
permissionSubject.apply {
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
Log.d("qwerty", "onRequestPermissionsResult=$granted")
onNext(granted)
onComplete()
}
}
// skip other request codes
}
#SuppressLint("MissingPermission")
// check ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION before calling this
private fun requestLastLocation(activity: Activity): Observable<Location?> = PublishSubject.create<Location>().apply {
Log.d("qwerty", "requestLastLocation")
LocationServices.getFusedLocationProviderClient(activity).lastLocation
.addOnSuccessListener { location ->
Log.d("qwerty", "lastLocation=$location")
onNext(location)
onComplete()
}
.addOnFailureListener { error ->
Log.d("qwerty", "lastLocation error: ${error.message}")
onError(error)
}
}
}
And fragment calls LocationManager like this:
LocationManager.loadLastLocation(activity!!)
.subscribe(
{ location -> Log.d("qwerty", "fragment got location $location")},
{ error -> error.printStackTrace() }
)
The problem is Observable stucks in infinite loop trying to get permission. Here's what I get in logs, infinite times:
qwerty: requestPermission android.permission.ACCESS_COARSE_LOCATION
qwerty: hasPermission=false
System.err: java.lang.Exception: Permission not granted...
qwerty: onRequestPermissionsResult=false
Can someone tell me what's wrong with this code?
as far as I can see from this code, it should work properly. Please check in your manifest that it tag contain the same permission that you are requesting. And second one, please check in you Location manager imports, that proper Manifest class is imported (android.Manifest not your.app.package.Manifest).