PEKO
PErmissions with KOtlin
Android Permissions with Kotlin Coroutines or LiveData
No more callbacks, builders, listeners or verbose code for requesting Android permissions.
Get Permission Request Result asynchronously with one function call.
Thanks to Kotlin Coroutines, permissions requests are async and lightweight (no new threads are used/created).
Or if you don't use Coroutines, and don't want to manage Lifecycles ... receive Permission Results with LiveData.
Installation
Hosted on Maven Central
implementation 'com.markodevcic:peko:2.1.2'
What is new
Peko Version 2
now uses Android X packages, Kotlin v1.4.0 and Coroutines 1.3.9.
Breaking changes from Peko Version 1.0
-
PermissionRequestResult
is renamed toPermissionResult
and is now a sealed class.PermissionResult
has a sealed class hierarchy of following types:PermissionResult.Granted
-> returned when all requested permissions were grantedPermissionResult.Denied
-> returned when at least one of the permissions was deniedPermissionResult.Denied.NeedsRationale
-> subclass ofPermissionResult.Denied
, returned when Android OS signals that at least one of the permissions needs to show a rationalePermissionResult.Denied.DeniedPermanently
-> subclass ofPermissionResult.Denied
, returned when no permissions need a Rationale and at least one of the permissions has a ticked Do Not Ask Again check boxPermissionResult.Denied.JustDenied
-> subclass ofPermissionResult.Denied
, returned when previous two cases are not the cause, for example if you forget to register the Permission in AndroidManifestPermissionResult.Cancelled
-> returned when Android System cancels the request, ie returned -
PermissionRationale
interface was removed. Library does not show Permission Rationales anymore. You can check now ifPermissionResult
is of typePermissionResult.NeedsRationale
and implement the rationale yourself. -
Added support for requesting permissions with LiveData
Peko Version 1.0
uses AppCompat libraries and is here.
Example
In an Activity or a Fragment that implements CoroutineScope
interface:
launch {
val result = Peko.requestPermissionsAsync(this, Manifest.permission.READ_CONTACTS)
if (result is PermissionResult.Granted) {
// we have contacts permission
} else {
// permission denied
}
}
Or use one of the extension functions on an Activity or a Fragment:
launch {
val result = requestPermissionsAsync(Manifest.permission.READ_CONTACTS)
if (result is PermissionResult.Granted) {
// we have contacts permission
} else {
// permission denied
}
}
Request multiple permissions:
launch {
val result = requestPermissionsAsync(Manifest.permission.READ_CONTACTS, Manifest.permission.CAMERA)
if (result is PermissionResult.Granted) {
// we have both permissions
} else if (result is PermissionResult.Denied) {
result.deniedPermissions.forEach { p ->
// this one was denied
}
}
}
Denied Result has three subtypes which can be checked to see if we need Permission Rationale or user Clicked Do Not Ask Again.
launch {
val result = requestPermissionsAsync(Manifest.permission.BLUETOOTH, Manifest.permission.CAMERA)
when (result) {
is PermissionResult.Granted -> { } // woohoo, all requested permissions granted
is PermissionResult.Denied.JustDenied -> { } // at least one permission was denied, maybe we forgot to register it in the AndroidManifest?
is PermissionResult.Denied.NeedsRationale -> { } // user clicked Deny, let's show a rationale
is PermissionResult.Denied.DeniedPermanently -> { } // Android System won't show Permission dialog anymore, let's tell the user we can't proceed
is PermissionResult.Cancelled -> { } // interaction was interrupted
}
}
If you want to know which permissions were denied, they are a property of Denied
class.
class Denied(val deniedPermissions: Collection<String>)
LiveData
Hate Coroutines? No problem ... just create an instance of PermissionsLiveData
and observe the results with your LifecycleOwner
In a ViewModel ... if you need to support orientation changes, or anywhere else if not (Presenter)
val permissionLiveData = PermissionsLiveData()
fun checkPermissions(vararg permissions: String) {
permissionLiveData.checkPermissions(*permissions)
}
In your LifecycleOwner
, for example in an Activity
override fun onCreate(savedInstanceState: Bundle?) {
viewModel = ViewModelProviders.of(this).get(YourViewModel::class.java)
// observe has to be called before checkPermissions, so we can get the LifecycleOwner
viewModel.permissionLiveData.observe(this, Observer { r: PermissionResult ->
// do something with permission results
})
}
private fun askContactsPermissions() {
viewModel.checkPermissions(Manifest.permission.READ_CONTACTS)
}
Screen rotations
Library has support for screen rotations. To avoid memory leaks, all Coroutines that have not completed yet, should be cancelled in the onDestroy
function. When you detect a orientation change, cancel the Job
of a CoroutineScope
with an instance of ActivityRotatingException
. Internally, this will retain the current request that is in progress. The request is then resumed with calling resumeRequest
method.
Example:
First:
// job that will be cancelled in onDestroy
private val job = Job()
private fun requestPermission(vararg permissions: String) {
launch {
val result = Peko.requestPermissionsAsync(this@MainActivity, *permissions)
// check granted permissions
}
}
Then in onDestroy
of an Activity:
if (isChangingConfigurations) {
job.cancel(ActivityRotatingException()) // screen rotation, retain the results
} else {
job.cancel() // no rotation, just cancel the Coroutine
}
And when this Activity gets recreated in one of the Activity lifecycle functions, e.g.onCreate
:
// check if we have a request already (or some other way you detect screen orientation)
if (Peko.isRequestInProgress()) {
launch {
// get the existing request and await the result
val result = Peko.resumeRequest()
// check granted permissions
}
}
LiveData and screen rotations
You don't have to do anything, this logic is already inside the PermissionsLiveData
class. You just have to call observe in the onCreate
method and of course use androidx.lifecycle.ViewModel
.
License
Copyright 2021 Marko Devcic
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.