Permissive
Permissive is a lightweight, yet powerful Android library, that helps you restore control over permissions handling introduced in Android Marshmallow (API 23).
The Permissive library perfectly fits into Material Design - Permissions pattern, by providing a simple API to accomplish tasks requiring sensitive permissions.
All screenshots were taken from sample app, which provides exemplary implementation of popular use-cases using the Permissive library.
Features
- Backward compatibility down to Android API 8 (not tested on earlier versions)
- Java8 ready (supports retrolambda expressions)
- Thread-safe API
- Queueing permissive
Requests
andActions
- Not using transparent magic Activities
- No messing with your Activities (well, actually adds a fragment, but it should not bother you in most cases)
- No additional dependencies
- No code generation
- Unique Espresso compatible Permissive Testing library (experimental)
Getting started
Setup
- Add it in your
build.gradle
at the end of repositories:
repositories {
...
maven {
url "https://jitpack.io"
}
}
- Add the core dependency:
dependencies {
compile 'com.github.jksiezni.permissive:permissive:0.2'
}
- (Optional) Add an extra library with helper fragments:
dependencies {
// includes helper fragments based on Android Support v4 implementation (minSdkVersion 8)
compile 'com.github.jksiezni.permissive:permissive-fragments:0.2'
and/or
// includes helper fragments based on native implementation (minSdkVersion 11)
compile 'com.github.jksiezni.permissive:permissive-fragments-v13:0.2'
}
Usage
Requesting permissions
Permissive.Request
allows to simply ask user for a permission and (if allowed) do the task. To request a permission you just need to create and execute Permissive.Request
:
new Permissive.Request(Manifest.permission.ACCESS_FINE_LOCATION).execute(getActivity());
Add callback listeners that return results of the request. Also, you can ask for more permissions with a single Request:
new Permissive.Request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
.whenPermissionsGranted(new PermissionsGrantedListener() {
@Override
public void onPermissionsGranted(String[] permissions) throws SecurityException {
// given permissions are granted
}
}
)
.whenPermissionsRefused(new PermissionsRefusedListener() {
@Override
public void onPermissionsRefused(String[] permissions) {
// given permissions are refused
}
}
)
.execute(getActivity());
Providing rationale
When requesting permissions, a rationale may be required. According to Material Design guidelines, you should provide a proper rationale depending on clarity and importance of permission type you are requesting. Permissive API is very flexible and allows you to implement all strategies you may need when requesting permissions. See Request patterns suggested by Google.
Rationale
Executing requests with To show a rationale simply add a new Rationale
callback to the Permissive.Request
:
new Permissive.Request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withRationale(/*YourRationale*/)
.whenPermissionsGranted(/*listener*/)
.whenPermissionsGranted(/*listener*/)
.execute(getActivity());
You can also register a global Rationale
, which will be used automatically when requesting permission:
// registar global rationale Permissive.registerGlobalRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*YourRationale*/);
... // perform a request new Permissive.Request(Manifest.permission.WRITE_EXTERNAL_STORAGE).execute(getActivity());
Note: A locally added rationale callback always takes precedence over global rationales.
Rationale
Building Rationale
implementation depends on the request pattern you choose to follow. Of course, it may vary from app to app, so Permissive library does not enforce you to use any of them. Instead, it provides some helpers, that make it easier to build a well tailored Rationale
.
- This is an example of
Rationale
implementation using AlertDialog:
new Permissive.Request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withRationale(new Rationale() {
@Override
public void onShowRationale(Activity activity, String[] allowablePermissions, PermissiveMessenger messenger) {
new AlertDialog.Builder(activity)
.setTitle("Rationale title...")
.setMessage("A rationale message.")
.setPositiveButton(android.R.string.ok, null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog1) {
messenger.cancelPermissionsRequest();
}
}
)
.show();
}
}
).execute(getActivity());
The onShowRationale(Activity activity, String[] allowablePermissions, PermissiveMessenger messenger)
method is called when a rationale should be shown. The '''allowablePermissions''' argument gives you a hint, what permissions you can still ask for. It's a different approach to 'never ask again' problem, where you would expect to have a list of blocked permissions.
The PermissiveMessenger
object allows you to control current Request
. Use repeatPermissionsRequest() or cancelPermissionsRequest() methods to ask again for permissions or cancel ongoing request.
Note: Remember to call one of repeatPermissionsRequest() or cancelPermissionsRequest() methods, in order to continue processing requests. Otherwise your
Requests
andActions
will be dead-locked.
Additionally, Permissive comes with permissive-fragments library providing specialized fragments, that help simplify building rationales. The main advantage of using those fragments, is when you want to preserve your request and rationale across runtime changes. Here's an example of a previous AlertDialog which is encapsulated in RationaleDialogFragment
:
public class ExampleRationaleFragment extends RationaleDialogFragment implements DialogInterface.OnClickListener {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle("Rationale title...")
.setMessage("A rationale message.")
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.no, this)
.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
getPermissiveMessenger().repeatRequest();
break;
case DialogInterface.BUTTON_NEGATIVE:
getPermissiveMessenger().cancelRequest();
break;
}
}
}
... new Permissive.Request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withRationale(new ExampleRationaleFragment())
.execute(getActivity());
Permissive.Action
Running a The Permissive.Action
simply checks what permissions are granted or refused to the app. So, the main difference from Permissive.Request
is that it doesn't show any dialogs to the user. Also, it is executed in a queue as every other Permissive.Request
, what provides a big advantage when used with requests. In effect, every action will wait until other Actions or Requests are completed. Actions can be used in background tasks (like Services), where no Activity exists, but a sensitive permission is still required.
// here using Java 8 lambdas new Permissive.Action<>(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.whenPermissionsGranted(this::onPermissionsGranted)
.whenPermissionsRefused(this::onPermissionsRefused)
.execute(getActivity());
Advanced usage
Showing rationale first
For some use-cases the Rationale
should be displayed before asking for a permission, for instance when educating users about unclear permissions. It can be done simply by adding showRationaleFirst() call to the Permissive.Request
:
new Permissive.Request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
.showRationaleFirst(true)
.withRationale(new Rationale() {
/*...*/
}
)
.execute(getActivity());
Checking permission in-place
Basically, it's a clone of Context.checkSelfPermission() method:
if(Permissive.checkPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
// permission is granted
}
License
Copyright 2016 Jakub Ksi??niak 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.