Skip to content

Commit

Permalink
added rationale support
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzofelletti committed Dec 5, 2022
1 parent 4fb2675 commit b435dcb
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 23 deletions.
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ plugins {

android {
namespace 'com.lorenzofelletti.permissionsexample'
compileSdk 32
compileSdk 33

defaultConfig {
applicationId "com.lorenzofelletti.permissionsexample"
minSdk 26
targetSdk 32
targetSdk 33
versionCode 1
versionName "1.0"

Expand All @@ -34,7 +34,7 @@ android {

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:allowBackup="true"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.lorenzofelletti.permissionsexample

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.lorenzofelletti.permissions.PermissionManager
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.checkPermissions
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.doOnDenied
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.doOnGranted
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.showRationaleDialog
import com.lorenzofelletti.permissions.dispatcher.RequestResultsDispatcher.Companion.withRequestCode

class MainActivity : AppCompatActivity() {
Expand All @@ -21,6 +23,7 @@ class MainActivity : AppCompatActivity() {
permissionsUtilities.buildRequestResultsDispatcher {
withRequestCode(POSITION_REQUEST_CODE) {
checkPermissions(POSITION_REQUIRED_PERMISSIONS)
showRationaleDialog(message = "Location permission is required to use this feature")
doOnGranted {
Log.d(TAG, "Location permission granted")
}
Expand All @@ -33,12 +36,17 @@ class MainActivity : AppCompatActivity() {
permissionsUtilities.checkRequestAndDispatch(
POSITION_REQUEST_CODE
)

val button: Button = findViewById(R.id.button)
button.setOnClickListener {
permissionsUtilities.checkRequestAndDispatch(
POSITION_REQUEST_CODE
)
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionsUtilities.dispatchOnRequestPermissionsResult(requestCode, grantResults)
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
tools:layout_editor_absoluteX="159dp"
tools:layout_editor_absoluteY="238dp" />

</androidx.constraintlayout.widget.ConstraintLayout>
8 changes: 4 additions & 4 deletions permissions/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ plugins {

android {
namespace 'com.lorenzofelletti.permissions'
compileSdk 32
compileSdk 33

defaultConfig {
minSdk 26
targetSdk 32
targetSdk 33

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand Down Expand Up @@ -40,7 +40,7 @@ android {

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
testImplementation 'junit:junit:4.13.2'
Expand All @@ -53,7 +53,7 @@ publishing {
release(MavenPublication) {
groupId 'com.github.lorenzofelletti'
artifactId 'permissions'
version '0.2.0'
version '0.3.0'

afterEvaluate {
from components.release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
import com.lorenzofelletti.permissions.dispatcher.RequestResultsDispatcher
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl

Expand All @@ -22,7 +23,7 @@ import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl
*
* @param activity The [Activity] that needs to manage the permissions
*/
class PermissionManager(private val activity: Activity) {
class PermissionManager(val activity: Activity) {
private lateinit var requestResultsDispatcher: RequestResultsDispatcher

/**
Expand All @@ -33,7 +34,7 @@ class PermissionManager(private val activity: Activity) {
*/
@PermissionDispatcherDsl
fun buildRequestResultsDispatcher(init: RequestResultsDispatcher.() -> Unit) {
requestResultsDispatcher = RequestResultsDispatcher().apply(init)
requestResultsDispatcher = RequestResultsDispatcher(this).apply(init)
}

/**
Expand All @@ -47,19 +48,31 @@ class PermissionManager(private val activity: Activity) {
*
* @param requestCode The request code associated to the permissions
*/
fun checkRequestAndDispatch(requestCode: Int) {
fun checkRequestAndDispatch(requestCode: Int, comingFromRationale: Boolean = false) {
val permissions = requestResultsDispatcher.getPermissions(requestCode)
?: throw UnhandledRequestCodeException(requestCode)
val permissionsNotGranted = permissions.filter { permission ->
ActivityCompat.checkSelfPermission(
activity.baseContext, permission
activity, permission
) != PackageManager.PERMISSION_GRANTED

}.toTypedArray()

if (permissionsNotGranted.isNotEmpty()) {
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
} else {
if (permissionsNotGranted.isEmpty()) {
// All permissions are granted
requestResultsDispatcher.getOnGranted(requestCode)?.invoke()
} else {
// Some permissions are not granted
val shouldShowRationale = permissionsNotGranted.any { permission ->
shouldShowRequestPermissionRationale(activity, permission)
}

if (shouldShowRationale && !comingFromRationale) {
requestResultsDispatcher.getOnShowRationale(requestCode)
?.invoke(permissionsNotGranted.toList(), requestCode)
} else {
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.lorenzofelletti.permissions.dispatcher

import android.app.Activity
import android.app.AlertDialog
import com.lorenzofelletti.permissions.PermissionManager
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl

Expand All @@ -9,7 +12,10 @@ import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl
* It contains the permissions associated to the request, and the actions to be dispatched
* in case the permissions are granted or denied.
*/
class DispatcherEntry : PermissionDispatcher() {
class DispatcherEntry(
private val manager: PermissionManager,
private val requestCode: Int
) : PermissionDispatcher() {
/**
* The permissions associated to this entry
*/
Expand All @@ -28,6 +34,12 @@ class DispatcherEntry : PermissionDispatcher() {
var onDenied: () -> Unit = {}
private set

/**
* The rationale to be shown
*/
var onShowRationale: (List<String>, requestCode: Int) -> Unit = { _, _ -> }
private set

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down Expand Up @@ -74,5 +86,39 @@ class DispatcherEntry : PermissionDispatcher() {
fun DispatcherEntry.doOnDenied(onDenied: () -> Unit) {
this.onDenied = onDenied
}

/**
* Allows to show a rationale to the user if [Activity.shouldShowRequestPermissionRationale]
* returns true for any of the permissions.
*
* Use this method if [DispatcherEntry.showRationaleDialog] does not fit your needs.
*
* @param onShowRationale
*/
@PermissionDispatcherDsl
fun DispatcherEntry.rationale(onShowRationale: (List<String>, Int) -> Unit) {
this.onShowRationale = onShowRationale
}

/**
* Shows a rationale dialog to the user.
* If the user clicks on the positive button, the permission request will be performed, else
* not.
*
* @param message the message to be shown
*/
@PermissionDispatcherDsl
fun DispatcherEntry.showRationaleDialog(message: String) {
rationale { _, _ ->
AlertDialog.Builder(manager.activity)
.setMessage(message)
.setPositiveButton("OK") { _, _ ->
manager.checkRequestAndDispatch(requestCode, comingFromRationale = true)
}
.setNegativeButton("Cancel", null)
.create()
.show()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.lorenzofelletti.permissions.dispatcher

import android.app.Activity
import android.app.AlertDialog
import android.content.pm.PackageManager
import com.lorenzofelletti.permissions.PermissionManager
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.rationale
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl

/**
* A dispatcher for the results of permission requests.
*/
class RequestResultsDispatcher : PermissionDispatcher() {
class RequestResultsDispatcher(private val manager: PermissionManager) : PermissionDispatcher() {
private val entries: MutableMap<Int, DispatcherEntry> = mutableMapOf()

fun dispatchAction(requestCode: Int, grantResults: IntArray): (() -> Unit)? =
Expand All @@ -21,6 +25,9 @@ class RequestResultsDispatcher : PermissionDispatcher() {

internal fun getOnGranted(requestCode: Int): (() -> Unit)? = entries[requestCode]?.onGranted

internal fun getOnShowRationale(requestCode: Int): ((List<String>, Int) -> Unit)? =
entries[requestCode]?.onShowRationale

/**
* Checks the results of a permission request
*
Expand All @@ -43,8 +50,10 @@ class RequestResultsDispatcher : PermissionDispatcher() {
* @param init A lambda that initializes the entry
*/
@PermissionDispatcherDsl
fun RequestResultsDispatcher.withRequestCode(requestCode: Int, init: DispatcherEntry.() -> Unit) {
entries[requestCode] = DispatcherEntry().apply(init)
fun RequestResultsDispatcher.withRequestCode(
requestCode: Int, init: DispatcherEntry.() -> Unit
) {
entries[requestCode] = DispatcherEntry(manager, requestCode).apply(init)
}
}
}

0 comments on commit b435dcb

Please sign in to comment.