Skip to content

Commit b435dcb

Browse files
added rationale support
1 parent 4fb2675 commit b435dcb

File tree

9 files changed

+111
-23
lines changed

9 files changed

+111
-23
lines changed

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ plugins {
55

66
android {
77
namespace 'com.lorenzofelletti.permissionsexample'
8-
compileSdk 32
8+
compileSdk 33
99

1010
defaultConfig {
1111
applicationId "com.lorenzofelletti.permissionsexample"
1212
minSdk 26
13-
targetSdk 32
13+
targetSdk 33
1414
versionCode 1
1515
versionName "1.0"
1616

@@ -34,7 +34,7 @@ android {
3434

3535
dependencies {
3636

37-
implementation 'androidx.core:core-ktx:1.7.0'
37+
implementation 'androidx.core:core-ktx:1.9.0'
3838
implementation 'androidx.appcompat:appcompat:1.5.1'
3939
implementation 'com.google.android.material:material:1.7.0'
4040
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
6+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
7+
58
<application
6-
android:allowBackup="true"
9+
android:allowBackup="false"
710
android:dataExtractionRules="@xml/data_extraction_rules"
811
android:fullBackupContent="@xml/backup_rules"
912
android:icon="@mipmap/ic_launcher"

app/src/main/java/com/lorenzofelletti/permissionsexample/MainActivity.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.lorenzofelletti.permissionsexample
22

3-
import androidx.appcompat.app.AppCompatActivity
43
import android.os.Bundle
54
import android.util.Log
5+
import android.widget.Button
6+
import androidx.appcompat.app.AppCompatActivity
67
import com.lorenzofelletti.permissions.PermissionManager
78
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.checkPermissions
89
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.doOnDenied
910
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.doOnGranted
11+
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.showRationaleDialog
1012
import com.lorenzofelletti.permissions.dispatcher.RequestResultsDispatcher.Companion.withRequestCode
1113

1214
class MainActivity : AppCompatActivity() {
@@ -21,6 +23,7 @@ class MainActivity : AppCompatActivity() {
2123
permissionsUtilities.buildRequestResultsDispatcher {
2224
withRequestCode(POSITION_REQUEST_CODE) {
2325
checkPermissions(POSITION_REQUIRED_PERMISSIONS)
26+
showRationaleDialog(message = "Location permission is required to use this feature")
2427
doOnGranted {
2528
Log.d(TAG, "Location permission granted")
2629
}
@@ -33,12 +36,17 @@ class MainActivity : AppCompatActivity() {
3336
permissionsUtilities.checkRequestAndDispatch(
3437
POSITION_REQUEST_CODE
3538
)
39+
40+
val button: Button = findViewById(R.id.button)
41+
button.setOnClickListener {
42+
permissionsUtilities.checkRequestAndDispatch(
43+
POSITION_REQUEST_CODE
44+
)
45+
}
3646
}
3747

3848
override fun onRequestPermissionsResult(
39-
requestCode: Int,
40-
permissions: Array<out String>,
41-
grantResults: IntArray
49+
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
4250
) {
4351
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
4452
permissionsUtilities.dispatchOnRequestPermissionsResult(requestCode, grantResults)

app/src/main/res/layout/activity_main.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,12 @@
1515
app:layout_constraintStart_toStartOf="parent"
1616
app:layout_constraintTop_toTopOf="parent" />
1717

18+
<Button
19+
android:id="@+id/button"
20+
android:layout_width="wrap_content"
21+
android:layout_height="wrap_content"
22+
android:text="Button"
23+
tools:layout_editor_absoluteX="159dp"
24+
tools:layout_editor_absoluteY="238dp" />
25+
1826
</androidx.constraintlayout.widget.ConstraintLayout>

permissions/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ plugins {
66

77
android {
88
namespace 'com.lorenzofelletti.permissions'
9-
compileSdk 32
9+
compileSdk 33
1010

1111
defaultConfig {
1212
minSdk 26
13-
targetSdk 32
13+
targetSdk 33
1414

1515
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1616
consumerProguardFiles "consumer-rules.pro"
@@ -40,7 +40,7 @@ android {
4040

4141
dependencies {
4242

43-
implementation 'androidx.core:core-ktx:1.7.0'
43+
implementation 'androidx.core:core-ktx:1.9.0'
4444
implementation 'androidx.appcompat:appcompat:1.5.1'
4545
implementation 'com.google.android.material:material:1.7.0'
4646
testImplementation 'junit:junit:4.13.2'
@@ -53,7 +53,7 @@ publishing {
5353
release(MavenPublication) {
5454
groupId 'com.github.lorenzofelletti'
5555
artifactId 'permissions'
56-
version '0.2.0'
56+
version '0.3.0'
5757

5858
afterEvaluate {
5959
from components.release

permissions/src/main/java/com/lorenzofelletti/permissions/PermissionManager.kt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.app.Activity
44
import android.content.Context
55
import android.content.pm.PackageManager
66
import androidx.core.app.ActivityCompat
7+
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
78
import com.lorenzofelletti.permissions.dispatcher.RequestResultsDispatcher
89
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl
910

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

2829
/**
@@ -33,7 +34,7 @@ class PermissionManager(private val activity: Activity) {
3334
*/
3435
@PermissionDispatcherDsl
3536
fun buildRequestResultsDispatcher(init: RequestResultsDispatcher.() -> Unit) {
36-
requestResultsDispatcher = RequestResultsDispatcher().apply(init)
37+
requestResultsDispatcher = RequestResultsDispatcher(this).apply(init)
3738
}
3839

3940
/**
@@ -47,19 +48,31 @@ class PermissionManager(private val activity: Activity) {
4748
*
4849
* @param requestCode The request code associated to the permissions
4950
*/
50-
fun checkRequestAndDispatch(requestCode: Int) {
51+
fun checkRequestAndDispatch(requestCode: Int, comingFromRationale: Boolean = false) {
5152
val permissions = requestResultsDispatcher.getPermissions(requestCode)
5253
?: throw UnhandledRequestCodeException(requestCode)
5354
val permissionsNotGranted = permissions.filter { permission ->
5455
ActivityCompat.checkSelfPermission(
55-
activity.baseContext, permission
56+
activity, permission
5657
) != PackageManager.PERMISSION_GRANTED
58+
5759
}.toTypedArray()
5860

59-
if (permissionsNotGranted.isNotEmpty()) {
60-
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
61-
} else {
61+
if (permissionsNotGranted.isEmpty()) {
62+
// All permissions are granted
6263
requestResultsDispatcher.getOnGranted(requestCode)?.invoke()
64+
} else {
65+
// Some permissions are not granted
66+
val shouldShowRationale = permissionsNotGranted.any { permission ->
67+
shouldShowRequestPermissionRationale(activity, permission)
68+
}
69+
70+
if (shouldShowRationale && !comingFromRationale) {
71+
requestResultsDispatcher.getOnShowRationale(requestCode)
72+
?.invoke(permissionsNotGranted.toList(), requestCode)
73+
} else {
74+
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
75+
}
6376
}
6477
}
6578

permissions/src/main/java/com/lorenzofelletti/permissions/dispatcher/DispatcherEntry.kt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.lorenzofelletti.permissions.dispatcher
22

3+
import android.app.Activity
4+
import android.app.AlertDialog
5+
import com.lorenzofelletti.permissions.PermissionManager
36
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher
47
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl
58

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

37+
/**
38+
* The rationale to be shown
39+
*/
40+
var onShowRationale: (List<String>, requestCode: Int) -> Unit = { _, _ -> }
41+
private set
42+
3143
override fun equals(other: Any?): Boolean {
3244
if (this === other) return true
3345
if (javaClass != other?.javaClass) return false
@@ -74,5 +86,39 @@ class DispatcherEntry : PermissionDispatcher() {
7486
fun DispatcherEntry.doOnDenied(onDenied: () -> Unit) {
7587
this.onDenied = onDenied
7688
}
89+
90+
/**
91+
* Allows to show a rationale to the user if [Activity.shouldShowRequestPermissionRationale]
92+
* returns true for any of the permissions.
93+
*
94+
* Use this method if [DispatcherEntry.showRationaleDialog] does not fit your needs.
95+
*
96+
* @param onShowRationale
97+
*/
98+
@PermissionDispatcherDsl
99+
fun DispatcherEntry.rationale(onShowRationale: (List<String>, Int) -> Unit) {
100+
this.onShowRationale = onShowRationale
101+
}
102+
103+
/**
104+
* Shows a rationale dialog to the user.
105+
* If the user clicks on the positive button, the permission request will be performed, else
106+
* not.
107+
*
108+
* @param message the message to be shown
109+
*/
110+
@PermissionDispatcherDsl
111+
fun DispatcherEntry.showRationaleDialog(message: String) {
112+
rationale { _, _ ->
113+
AlertDialog.Builder(manager.activity)
114+
.setMessage(message)
115+
.setPositiveButton("OK") { _, _ ->
116+
manager.checkRequestAndDispatch(requestCode, comingFromRationale = true)
117+
}
118+
.setNegativeButton("Cancel", null)
119+
.create()
120+
.show()
121+
}
122+
}
77123
}
78124
}

permissions/src/main/java/com/lorenzofelletti/permissions/dispatcher/RequestResultsDispatcher.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.lorenzofelletti.permissions.dispatcher
22

3+
import android.app.Activity
4+
import android.app.AlertDialog
35
import android.content.pm.PackageManager
6+
import com.lorenzofelletti.permissions.PermissionManager
7+
import com.lorenzofelletti.permissions.dispatcher.DispatcherEntry.Companion.rationale
48
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher
59
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcherDsl
610

711
/**
812
* A dispatcher for the results of permission requests.
913
*/
10-
class RequestResultsDispatcher : PermissionDispatcher() {
14+
class RequestResultsDispatcher(private val manager: PermissionManager) : PermissionDispatcher() {
1115
private val entries: MutableMap<Int, DispatcherEntry> = mutableMapOf()
1216

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

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

28+
internal fun getOnShowRationale(requestCode: Int): ((List<String>, Int) -> Unit)? =
29+
entries[requestCode]?.onShowRationale
30+
2431
/**
2532
* Checks the results of a permission request
2633
*
@@ -43,8 +50,10 @@ class RequestResultsDispatcher : PermissionDispatcher() {
4350
* @param init A lambda that initializes the entry
4451
*/
4552
@PermissionDispatcherDsl
46-
fun RequestResultsDispatcher.withRequestCode(requestCode: Int, init: DispatcherEntry.() -> Unit) {
47-
entries[requestCode] = DispatcherEntry().apply(init)
53+
fun RequestResultsDispatcher.withRequestCode(
54+
requestCode: Int, init: DispatcherEntry.() -> Unit
55+
) {
56+
entries[requestCode] = DispatcherEntry(manager, requestCode).apply(init)
4857
}
4958
}
5059
}

0 commit comments

Comments
 (0)