I'm developing an android app in which I have to ask for permissions at runtime. I'm wondering about the best way to implement that using Model-View-Presenter architecture.
My initial thought was to have the presenter call a component responsible for permissions(say a PermissionHandler
), and update view accordingly.
The issue is that the code to check for permissions is tightly coupled with the Activity class. Here are some of the methods involved that require an Activity or Context:
ContextCompat.checkSelfPermission()
ActivityCompat.shouldShowRequestPermissionRationale()
ActivityCompat.requestPermissions()
onRequestPermissionsResult()
(callback)This means I would have to pass an activity object to the presenter, which I didn't like much because I've heard that keeping your presenter free from Android code is good for testing.
Due to that, I then thought about handling permissions at view level(in an activity), but then I guess this would hurt the purpose of leaving the view responsible only for UI updates, without business logic.
I'm not sure what would be the best approach to tackle that keeping the code as decoupled and maintainable as possible. Any ideas?
Requesting Android Runtime Permissions For this the following method needs to be called on every permission. checkSelfPermission(String perm); It returns an integer value of PERMISSION_GRANTED or PERMISSION_DENIED.
Runtime permissions prevent apps from gaining access to private data without a user's consent, and provide them with additional context and visibility into the types of permissions that applications are either seeking, or have been granted.
I don't think you should ask permission again if the user denies, all you can do is, don't take the app forward , just show them a toast or a dialog telling them that this permission is required for the app to work correctly and ask them to grant permission in the settings.
Starting in Android 11, whenever your app requests a permission related to location, microphone, or camera, the user-facing permissions dialog contains an option called Only this time. If the user selects this option in the dialog, your app is granted a temporary one-time permission.
What I would do is:
The view will implement:
public Activity getViewActivity();
The presenter will implement:
public void requestPermissions(); public void onPermissionsResult();
Inside requestPermissions
, the presenter will do: getViewActivity().checkSelfPermission; getViewActivity.requestPermissions(); etc.
The view will call inside the onRequestPermissionsResult
callback to presenter.onPermissionsResult();
With this all the logic will be implemented inside the presenter.
In my opinion, your presenter is decoupled: it won't depend on any view implementation (it will only depend on the view interface).
"I've heard that keeping your presenter free from Android code is good for testing." I don't understand this part. If the code is good, it can be tested without any problem.
If you still want to be able to mock permission access/requests, you still create something like a PermissionHandler
, but only reference it inside your view class. For example -
Interface:
public interface PermissionsHandler { boolean checkHasPermission(AppCompatActivity activity, String permission); void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode); }
Production implementation:
public class PermissionsHandlerAndroid implements PermissionsHandler { @Override public boolean checkHasPermission(AppCompatActivity activity, String permission) { return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED; } @Override public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){ ActivityCompat.requestPermissions(activity, permissions, requestCode); } }
Mocked class (for example, to test and make sure your activity correctly handles onRequestPermissionsResult
)
public class PermissionsHandlerMocked implements PermissionsHandler { @Override public boolean checkHasPermission(AppCompatActivity activity, String permission) { return false; } @Override public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){ int[] grantResults = new int[permissions.length]; for (int i = 0; i < permissions.length; i++) { grantResults[i] = PackageManager.PERMISSION_GRANTED } activity.onRequestPermissionResult(requestCode, permissions, grantResults); } }
Then in your activity:
PermissionsHandler permissionsHandler; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); permissionsHandler = Injection.providePermissionsHandler(); //or however you choose to inject your production vs mocked handler. } //method from your view interface, to be called by your presenter @Override void requestLocationPermission() { permissionsHandler.requestPermision((AppCompatActivity) this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_LOCATION}; }
fobo66, you can always make the view implement much more generic methods like checkLocationPermissionGranted()
and requestLocationPermission()
. Then your view implementation can reference the activity as needed, and your presenter never has to touch the activity reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With