Android Check if Don t Ask Again

Runtime permissions are very common characteristic implemented in Android applications. However, for some reason, they always catch me off-guard and I routinely plant bugs into my projects when using them. Peradventure it's just me, but I doubt information technology.

Therefore, I decided to summarize what I know about runtime permissions in Android in this mail service and utilize it as a reference in the future.

Runtime Permissions

First of all, I desire to provide a general context for runtime permissions equally a user-facing feature. If you're already familiar with this mechanism, you can skip right to the more than technical word below.

Earlier Android M (API 23), the arrangement prompted users to approve all the permissions that apps required earlier the installation. Therefore, the moment your app had fabricated it into users' devices, you could exist sure that it had all the permissions declared in its manifest file:

<uses-permission android:proper noun="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Our lives, equally developers, were simple back then because all nosotros had to practice was adding the to a higher place lines into app's manifest, and that's most information technology.

However, this "static" permissions model was a problem for user's privacy. Since users had to approve all the permissions at once (otherwise they couldn't install the app), this mechanism was driveling and applications asked for way as well many permissions. In addition, most users didn't review apps' permissions thoroughly enough (or didn't review them at all), and so malicious players could easily gain access to users' private data.

That's why Google introduced so-called "runtime permissions" mechanism in Android Grand. Following this change, Android apps wouldn't become all the permissions upon installation, but, instead, would demand to enquire for users' blessing of and so-called "unsafe" permissions at runtime. Furthermore, unlike with legacy static permissions, users can deny usage of individual runtime permissions and the applications are expected to handle this situation gracefully (due east.1000. disable parts of the functionality).

In my stance, introduction of runtime permissions was the biggest change in Android applications' compages e'er. Undoubtedly, it was a positive alter for the users, simply it added a considerable amount of work for Android developers.

Requesting a Runtime Permission

It's important to remember that even if you lot request a specific permission at runtime, y'all must all the same declare it in the manifest file. If you lot forget doing that, the system will automatically deny your runtime request for that permission. That's actually annoying and I routinely waste time debugging this problem when I ask for additional runtime permissions.

In one case you added the permission declaration to the manifest, the side by side step is to request that permission in code. For example, that's how yous'd practice that inside Activity:

individual static final int REQUEST_CODE_LOCATION = 1; ... ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_LOCATION);

Notice that the method is called ActivityCompat.requestPermissions and accepts an array of permission names. Therefore, you can ask for more than one permission at a time (discussed later). The concluding argument is but a number associated with a specific request. That's and then-called "async completion token" pattern and you can utilize this number to map from the effect to a specific request at a later stage.

The higher up arroyo will work in whatever component where you can go a reference to Activity object. The upshot of this asking volition be delivered to the specific Activity that you laissez passer into ActivityCompat.requestPermissions every bit the showtime argument (discussed after).

However, when you request runtime permissions from inside Fragment, ordinarily you'd similar the result to be delivered to that same Fragment. In such a case, don't use ActivityCompat, but simply telephone call Fragment's requestPermissions method:

private static concluding int REQUEST_CODE_LOCATION = i; ... requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_LOCATION);

Following the same API calls, the organisation will testify a dialog to the user which will prompt them to either grant or deny a specific permission:

After the user will choose either of the options, the system will route the result of this request back to your application.

Deprecation of requestPermissions Method

Unfortunately, if you read the documentation of Fragment.requestPermissions method, yous'll notice that information technology's already deprecated. The docs recommend using Fragment'due south registerForActivityResult method, combined with ActivityResultContracts.RequestMultiplePermissions object instead. Nonetheless, if you try to utilize this approach with "stable" versions of fragmentx and activityx today (as of this writing, these are fragmentx:1.2.v and activityx:one.ane.0), your code won't compile. What'south going on here?

Turns out that the to a higher place APIs will be introduced only in the adjacent versions of fragmentx and activityx. Therefore, today, they will work simply with non-stable artifacts (as of this writing, these are fragmentx:1.three.0-rc01 and activityx:1.2.0-rc01). The official docs just describe non-stable APIs. That's appaling, of course, but, given what's going on in Android ecosystem, isn't too surprising.

I will probably write another post most this new approach once the APIs reach stable milestones, but I can already say that they look more complex and dirtier to me. And so, if you accept masochistic tendencies, use them. Otherwise, read on to discover how to avoid all this mess.

Handling the Issue of a Runtime Permission Request

The consequence of runtime permission request will exist delivered to onRequestPermissionsResult method inside either target Activity (if you used ActivityCompat.requestPermissions method), or source fragment (if y'all used Fragment'southward requestPermissions method). Therefore, to handle this result, you'll need to override this method:

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);     // handle the result }

The commencement argument, requestCode, is the "async completion token" that you supplied when asking for a permission. If y'all need to differentiate betwixt multiple flows that request permissions, you can bank check the value of this argument here. The second argument, permissions, is an assortment containing the names of the requested permissions and the tertiary argument, grantResults, is an assortment containing values that indicate whether the user granted the corresponding permissions.

The full implementation of onRequestPermissionsResult for a single permission will usually await similar to this:

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);          if (permissions.length == 0 || grantResults.length == 0) {         // permissions request cancelled     }      switch (requestCode) {         case REQUEST_CODE_LOCATION:             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {                 // permission granted             } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) {                 // permission denied             } else {                 // permission denied and don't ask for information technology again             }             intermission;         default:             throw new RuntimeException("unhandled permissions request code: " + requestCode);     } }

Let'due south become over what happens here.

If either permissions or grantResults arrays are empty, it means that the request was cancelled by the system. That's relatively rare corner instance which I frequently forget near, but it withal must be accounted for. In my opinion, in this instance, in most cases, y'all can but asking the same permissions over again.

Then, by inspecting the values in grantResults, you tin find out whether user granted the requested permission. If they did, dandy! If not, so in that location are two unlike cases you'll need to account for.

When ActivityCompat.shouldShowRequestPermissionRationale returns truthful, it means that y'all're allowed to ask for the same permission once more. However, before y'all do, think advisedly whether the requested permission is important enough to bother the user afterward they've already declined information technology once. If that's the case, then it's as well recommended to show "permission rationale" before yous ask for that permission once again. This tin can be a simple dialog explaining to the user why your app needs that specific permission. Hopefully, afterward reading this message, the user will approve your request.

However, if ActivityCompat.shouldShowRequestPermissionRationale returns simulated, information technology means that, in addition to denying that permission request, the user also checked "Don't ask again" checkbox. In this case, you lot can't inquire for this permission anymore and if you do, your request will exist denied automatically by the system (without even prompting the user). Your app should basically keep working without that permission going forrard. If your app simply can't piece of work without the permission in question, too bad for the user. In this instance, y'all should probably bear witness a message on the screen and explicate to the user that the app won't function properly. If they'll desire to grant you that permission after all, they'll need to do it manually in settings app.

Requesting Multiple Runtime Permissions at Once

Equally I already mentioned above, y'all tin request multiple permissions at in one case:

private static final int REQUEST_CODE_PERMISSIONS = 1; ... String[] permissions = new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.GET_PHONE_STATE}; ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_PERMISSIONS);

The handling logic inside onRequestPermissionsResult becomes a bit more involved if information technology needs to handle multiple permissions (added for loop):

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);      if (permissions.length == 0 || grantResults.length == 0) {         // permissions request cancelled     }      for (int i = 0; i < permissions.length; i++) {         switch (requestCode) {             case REQUEST_CODE_LOCATION:                 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {                     // permission i granted                 } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {                     // permission i denied                 } else {                     // permission i denied and don't enquire for it again                 }                 break;             default:                 throw new RuntimeException("unhandled permissions request code: " + requestCode);         }     } }

Requesting multiple permissions at once can be useful if any of your app's features requires more than than one permission, or if you want to request some permissions in accelerate (e.g. during app's commencement startup).

Clean Design for Runtime Permissions

The approaches described above work and will exercise the trick. Even so, in my feel, runtime permissions management logic tin can go out of hand very quickly. When this happens, it becomes a source of bugs and maintainability overhead. The main reason for such an unfortunate effect is code duplication.

If you look at the previous code snippet, it doesn't look that bad on its own. However, imagine that you have 10 screens in your app that request various permissions. In this case, most of that "boilerplate" logic will be duplicated in 10 Activities or Fragments (or whatever component corresponds to "a screen" in your app). If you add the facts that there might be more than one permission request code on each "screen" and in that location will be additional app-specific logic there, you can imagine how ugly the permissions management logic can get. Or, peradventure, you lot've already seen this ugliness in a existent Android project, so yous don't even need to engage your imagination.

Therefore, in my projects, to avoid all the above problems, I e'er introduce PermissionsHelper abstraction and MyPermission enum.

The main goal of PermissionsHelper is to encapsulate the mutual "boilerplate" logic and expose a more convenient API.

The main goal of the additional enum is to supercede "dirty" Android strings with type-safe enum values. Since these values will be specific to your app, it will let you to meet, at a glance, which runtime permissions the app requires and likewise search for all the places where y'all inquire for specific permissions.

Once you add these classes to your codebase, you can request permissions in this style:

MyPermission[] permissions = new MyPermission[] {MyPermission.FINE_LOCATION, MyPermission.READ_PHONE_STATE}; if (!mPermissionsHelper.hasAllPermissions(permissions)) {     mPermissionsHelper.requestAllPermissions(permissions, 1); }

In order to become the results in either Activity or Fragment, you'll need to annals them equally listeners first:

@Override protected void onStart() {     super.onStart();     mPermissionsHelper.registerListener(this); }  @Override protected void onStop() {     super.onStop();     mPermissionsHelper.unregisterListener(this); }

Then, in one case the user will provide the required inputs, either of these two callbacks will exist invoked:

@Override public void onRequestPermissionsResult(int requestCode, PermissionsHelper.PermissionsResult event) {     if (!result.deniedDoNotAskAgain.isEmpty()) {         // handle permission(s) that were denied and shouldn't ask again     }           if (!result.denied.isEmpty()) {         // handle permission(s) that were denied     }      if (!result.granted.isEmpty()) {         // handle permission(southward) that were granted     }  }  @Override public void onPermissionsRequestCancelled(int requestCode) {     // handle permissions request cancellation }

Equally you hopefully see, this approach is simpler, more readable and leaves much less space for misunderstandings and bugs.

All the same, there are no complimentary lunches in software blueprint and architecture, so here is the take hold of: in club for PermissionsHelper to function properly, you'll need to make sure that all clients inside the scope of a single Activity share the same instance of this object. That's something you can achieve with Dagger using Activeness-scoped Component, or using Hilt's ActivityComponent. If you lot don't utilize proper DI in your app, y'all tin theoretically hack this up with Activity-scoped ViewModel.

Speaking of ViewModel, note that PermissionsHelper keeps a reference to Activity object. Therefore, information technology requires a bit more circumspection if y'all also utilize ViewModels in your projection (I never use them).

Lastly, you'll also need to delegate the call to onRequestPermissionsResult from the enclosing Activity to PermissionsHelper:

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     // must delegate to PermissionsHelper because this object functions equally a central "hub"     // for permissions management in the scope of this Action     mPermissionsHelper.onRequestPermissionsResult(requestCode, permissions, grantResults); }

Additional benefits of using PermissionsHelper and MyPermission are:

  • Use exactly the same arroyo in Activities, Fragments and other types of "controllers".
  • Brand your unit tests simpler and more expressive (if yous unit test your lawmaking).
  • Encapsulate usage of Android permissions APIs in a single course.

Note that concluding benefit. Given our previous discussion of the deprecation of the current APIs for permissions direction and introduction of new ones, the value of PermissionsHelper increases x-fold. In projects where I use this brainchild, the aforementioned deprecation volition touch on only the internal implementation of PermissionsHelper itself (and several lines of code outside of it). The clients of this class won't even know near all this mess. In projects that lack this level of abstraction, the refactoring tin impact tens of classes and hundreds of lines of code, which volition increment the effort and the risk of introducing bugs many-fold.

Therefore, in my feel, the above slight complications when usign PermissionsHelper are ridiculously low toll to pay for the cleanup of runtime permissions management logic in Android apps.

Permission Requests Using Kotlin Coroutines

Here and there on the internet I saw examples that demonstrate how to use Kotlin Coroutines to replace standard callback method that delivers permissions effect with intermission. If I'thousand not mistaken, I even saw something along these lines from Google.

I don't call back it's a good thought. The lawmaking related to runtime permissions is usually complex enough on its own, so throwing in additional complexity to spare several lines of lawmaking doesn't brand sense to me.

Conclusion

That'southward it about runtime permissions in Android. Thanks for reading and I hope you found this article interesting and useful.

As usual, if y'all have any comments or questions, leave them below.

ruffpallogn.blogspot.com

Source: https://www.techyourchance.com/runtime-permissions-android/

0 Response to "Android Check if Don t Ask Again"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel