Skip to content

Legacy Usage [2.x]

Giorgio Antonioli edited this page Mar 3, 2021 · 2 revisions

This page will help you to discover all the supported features of this library before the version 3.x. The main sections are the following:

Configuration

The main method to start the creation of a request is the extension function permissionsBuilder() that can be used from an Activity or a Fragment. The only necessary configuration that must be provided to create a new PermissionRequest is the permission's set that must be requested. Depending on your needs, you can provide one or more permissions for each request.

Example:

// This is the most basic request.
val request = permissionsBuilder(Manifest.permission.CAMERA).build()

You can define also a few others customizations that are optional. If not specified, the defaults are used.

Example:

// This is the most customized request.
val request = permissionsBuilder(Manifest.permission.CAMERA, Manifest.permission.SEND_SMS)
        // Specifies a custom RuntimePermissionHandlerProvider.
        .runtimeHandlerProvider(MyRuntimePermissionHandlerProvider())
        // Specifies a custom PermissionNonceGenerator.
        .nonceGenerator(MyPermissionNonceGenerator())
        // Build the request
        .build()

To find more information about RuntimePermissionHandlerProvider, go here.

To find more information about PermissionNonceGenerator, go here.

Callbacks

You can receive the callbacks on these permissions' events:

  • accepted → notified when some permissions are accepted.
  • denied → notified when some permissions are denied.
  • permanently denied → notified when some permissions are permanently denied. This can happen only since Android M when the user checks the never ask again checkbox in the dialog managed by the OS.
  • should show rationale → notified when some permissions needs a rationale that must be displayed to the user. This can happen only since Android M when the user denies the permissions. In this callback it's provided also a PermissionNonce that can be used to interact one more time with the RuntimePermissionHandler. By default the PermissionNonce requests the permissions again when used.

None of the callbacks are strictly necessary, use the ones you need.

There are different ways to receive callbacks about permissions' events that are equally managed, so choose the one you prefer most.

1. DSL

Recommended if you need different callbacks and you haven't a centralized management of the events.

request.listeners {
    onAccepted { permissions -> /* Execute your code. */ }

    onDenied { permissions -> /* Execute your code. */ }

    onPermanentlyDenied { permissions -> /* Execute your code. */ }

    onShouldShowRationale { permissions, nonce -> /* Execute your code. */ }
}

2. Builder's extensions

Recommended if you need only few callbacks and you haven't a centralized management of the events.

request.onAccepted { permissions -> /* Execute your code. */ }
    .onDenied { permissions -> /* Execute your code. */ }
    .onPermanentlyDenied { permissions -> /* Execute your code. */ }
    .onShouldShowRationale { permissions, nonce -> /* Execute your code. */ }

3. Normal listeners

Recommended if you have a centralized management of the events.

// It must implement [PermissionListener.AcceptedListener].
request.acceptedListener(this)
// It must implement [PermissionListener.DeniedListener].
request.deniedListener(this)
// It must implement [PermissionListener.PermanentlyDeniedListener].
request.permanentlyDeniedListener(this)
// It must implement [PermissionListener.RationaleListener].
request.rationaleListener(this)

You can also combine these styles if you need a more complex configuration.

Example:

// In this case you can use a specific callback when a group of permissions are 
// accepted and a shared callback when a group of permissions are permanently denied.
request.onAccepted { permissions -> /* Execute your code. */ }
    .permanentlyDeniedListener(MySharedPermanentlyDeniedListener())

All the listeners can be detached with a single method or with a specific method based on the type of the listener.

Example:

// Detach only the listeners that you need.
request.detachAcceptedListener()
request.detachDeniedListener()
request.detachPermanentlyDeniedListener()
request.detachRationaleListener()
// Detach all the listeners.
request.detachAllListeners()

Runtime handler

To manage the runtime permissions since Android M there are two main components:

  • RuntimePermissionHandlerProvider
  • RuntimePermissionHandler

RuntimePermissionHandlerProvider

This provider is used to provide an instance of RuntimePermissionHandler when the request is built. The provided instance of RuntimePermissionHandler must be available instantly. You can cache the instance of RuntimePermissionHandler to use it for all the requests, but you need to manage its lifecycle manually.

RuntimePermissionHandler

Used to handle the runtime permissions since Android M. This component must persist across configuration changes or save its state because the permissions' request is partially handled by the OS. The entire lifecycle of the runtime permissions is specified inside this component, so, in order to implement a custom lifecycle, you need to specify a custom RuntimePermissionHandlerProvider that will provide your custom RuntimePermissionHandler.

Default lifecycle

Considering a lifecycle the group of phases that passes from RuntimePermissionHandler.requestPermissions() till the end of RuntimePermissionHandler.onRequestPermissionsResult(), the default handler notifies the RuntimePermissionHandler.Listener for maximum one event during the lifecycle. For example if a permission's request contains two permissions and the user accepts only one of them, the RuntimePermissionHandler.Listener won't be notified on the accepted permissions, but only on the denied one.

This is done following the consideration that a permissions' request must contain only the permissions that are related to a single functionality, so the functionality mustn't be available if the user doesn't "resolve" the permissions.

Every state needs a PermissionRequest's listener attached to be handled, otherwise the application will proceed to the next state, if any.

The available states are the following.

  • rationale: happens when a permission is denied and a rationale is needed. It can be notified before the request is sent and after the result is received.
  • denied: happens when a permission is denied and the rationale state isn't handled. It can be notified after the result is received.
  • permanently denied: happens when the user selects the "never ask again" checkbox and the rationale/denied state isn't handled. It can be notified after the result is received.
  • accepted: happens when the user accepts all the permissions. It can be notified before the request is sent or after the result is received.

Nonce

A nonce is an object that can be used to interact one more time with the RuntimePermissionHandler. There are two main components regarding the handling of the nonce:

  • PermissionNonceGenerator
  • PermissionNonce

PermissionNonceGenerator

Used to generate a PermissionNonce. The instance of the provided PermissionNonce mustn't be cached because it must be used only one time.

PermissionNonce

When used, it interacts only one time with the RuntimePermissionHandler. If you use a PermissionNonce more than once, a PermissionNonceUsedException is thrown.

By default, when a PermissionNonce is used, the same set of permissions is requested again to the RuntimePermissionHandler.

Example

request.onShouldShowRationale { permissions, nonce ->
    // Show a dialog that permits the usage of the nonce.
    AlertDialog.Builder(this)
            .setTitle("Request again the permissions")
            .setPositiveButton(android.R.string.ok) { _, _ ->
                // Use the nonce when the user presses on the positive button.
                // By default, the [permissions] are requested again.
                nonce.use()
            }
            .show()
}

Injection

This library is built also to easily inject the PermissionRequest instances inside the components which uses them.

Example

If you use Dagger, you can provide the implementation of a PermissionRequest and use that request inside of a presenter without relying on the Android framework. In this way, the permissions' management can be easily mocked and tested.

Memory leaks

By default, the attached listeners are retained during orientation change. This could bring to a potential memory leak if you don't manage your listeners properly.

To avoid memory leaks, it's recommended to remove the attached listeners when the instance that holds the PermissionRequest is destroyed or to attach the listeners in a method always executed during the the instance's lifecycle.

Example

Considering an Activity that wants to check the status of a permission when an action is done by the user, it's recommended to build the request and attach its listeners at Activity.onCreate() and send it when the action is done. This avoids a potential memory leak when the Activity is rotated and doesn't require the detaching of the listeners.

Under the hoods

By default this library uses ContextCompat APIs to check the permissions below Android M and a Fragment to check the runtime permissions since Android M. The Fragment is used to filter the callbacks to onRequestPermissionsResult().

The usage of a Fragment has not a real performance impact because:

  • it's light-weight
  • it hasn't no UI at all
  • it's shared between permissions' requests
  • it's retained across configuration changes