Mvvmcross: Ask for permissions

Created on 2 Sep 2017  路  12Comments  路  Source: MvvmCross/MvvmCross

This is a continuation of https://github.com/MvvmCross/MvvmCross-Plugins/issues/82.

This is still a task that we need to do and would be required for the following plugins

  • [ ] PictureChooser (ask for Camera permission)
  • [ ] File
  • [ ] GeoLocation

We should look into taking a dependency on @jamesmontemagno's Permissions plugin and use that in the core for each of these plugins.

feature up-for-grabs

Most helpful comment

Been looking at James his plugin and how to wrap that. There's a few things I'm not completely happy about.

What if a plugin is used in a background service?

This design is based on the assumption that we should request the permission when a consumer calls a method of the plugin. But we never know if that's the appropriate moment since a plugin can be used from a background service thus permissions need to be requested earlier. This essentially invalidates the whole design below. The alternative is maybe adding a tiny wrapper over James his plugin. Question is if that is worth the effort. Essentially this would then be merely implementing sort of the PermissionRequestDecoratorBase class design described in the implementation below.

Requirements:

  • Android needs OnRequestPermissionResult override in main activity.
  • Android could require the app to show a permission request rationale. Since this is presentation it should be held out of the plugin and made configurable, though we could provide a standard implementation.
  • Both: When the permission is rejected you need another callback where you could do things like:

    • Present a localized message.

    • Disable the feature in your app.

Implementation

Create Task PermissionRequestDecoratorBase.Request(params Permission[] permissions)

  • If it:

    • Requires ShouldShowRequestPermissionRationaleAsync: Call into configurable callback.

    • succeeds: Call into implementation

    • Fails call into callback

Create Plugin Specific PermissionRequestDecorator : PermissionRequestDecoratorBase

Have portable class decorator object for each plugin that wraps the real services does the actual permission request using the plugin

Async compatibility

The permission plugin api is async. Not all the plugin interface are. We could:

  • Only implement this on the async interfaces for now.
  • Synchronize this with a TaskCompletionSource.
  • Not implement this as designed here but come up with a completely different solution??

Implement IMvxConfigurablePlugin to

Provide (platform specific) PermissionRequestSetup that offers consumer to hook in (platform) specific callbacks when permission rationale needs to be given or when the user rejects the request.

Change Plugin.Load()

Adjust to wrap the actual implementation in the decorator.

Notes on reuse

This design has a part has some infrastructure that would be reused by the plugings. The PermissionRequestDecoratorBase and the PermissionRequestSetup. Current architecture doesn't facilitate that as far as I can see. This would then require:

  • another project
  • or linked code that is compiled

Notes on using James Montemagno Plugin

  • The James Montemagno plugin also depends on his other CurrentActivity plugin while MvvmCross has it's own implementation of that.
  • iOS api has this aspect were you to have to add the the NSCalenderUsageDescription. James hasn't found a workaround for that yet.

All 12 comments

Been looking at James his plugin and how to wrap that. There's a few things I'm not completely happy about.

What if a plugin is used in a background service?

This design is based on the assumption that we should request the permission when a consumer calls a method of the plugin. But we never know if that's the appropriate moment since a plugin can be used from a background service thus permissions need to be requested earlier. This essentially invalidates the whole design below. The alternative is maybe adding a tiny wrapper over James his plugin. Question is if that is worth the effort. Essentially this would then be merely implementing sort of the PermissionRequestDecoratorBase class design described in the implementation below.

Requirements:

  • Android needs OnRequestPermissionResult override in main activity.
  • Android could require the app to show a permission request rationale. Since this is presentation it should be held out of the plugin and made configurable, though we could provide a standard implementation.
  • Both: When the permission is rejected you need another callback where you could do things like:

    • Present a localized message.

    • Disable the feature in your app.

Implementation

Create Task PermissionRequestDecoratorBase.Request(params Permission[] permissions)

  • If it:

    • Requires ShouldShowRequestPermissionRationaleAsync: Call into configurable callback.

    • succeeds: Call into implementation

    • Fails call into callback

Create Plugin Specific PermissionRequestDecorator : PermissionRequestDecoratorBase

Have portable class decorator object for each plugin that wraps the real services does the actual permission request using the plugin

Async compatibility

The permission plugin api is async. Not all the plugin interface are. We could:

  • Only implement this on the async interfaces for now.
  • Synchronize this with a TaskCompletionSource.
  • Not implement this as designed here but come up with a completely different solution??

Implement IMvxConfigurablePlugin to

Provide (platform specific) PermissionRequestSetup that offers consumer to hook in (platform) specific callbacks when permission rationale needs to be given or when the user rejects the request.

Change Plugin.Load()

Adjust to wrap the actual implementation in the decorator.

Notes on reuse

This design has a part has some infrastructure that would be reused by the plugings. The PermissionRequestDecoratorBase and the PermissionRequestSetup. Current architecture doesn't facilitate that as far as I can see. This would then require:

  • another project
  • or linked code that is compiled

Notes on using James Montemagno Plugin

  • The James Montemagno plugin also depends on his other CurrentActivity plugin while MvvmCross has it's own implementation of that.
  • iOS api has this aspect were you to have to add the the NSCalenderUsageDescription. James hasn't found a workaround for that yet.

Thanks @ferrydeboer this is a very nice write-up on your investigation of handling permissions.

There could be some collaboration on the current activity plugin stuff as well. It makes it easy to get the current activity, however a bit harder for plugin wrappers.

A plugin should not ask for a permission by itself as it may be run in background. But it should be able to check for required permissions and report it to its caller instead. It could also include code to ask for permissions but should never call it by itself.

This is a proposition for an activity aware permission on Android, which can be used when a viewmodel or plugin needs to interactively ask the user for permissions.

Usage would be:

csharp var activity = Mvx.Resolve<IMvxAndroidCurrentTopActivity>()?.Activity; if (activity is IActivityWithPermissionsCallback callbackActivity) { var (permissions, grantResults) = await callbackActivity.StartRequestPermissions(permissionList.ToArray()); var permissionsGranted = grantResults?.Length!=permissionList.Count ? (bool?)null : grantResults.All(r => r == Permission.Granted); }

Code:

csharp public interface IActivityWithPermissionsCallback { Task<Tuple<string[], Permission[]>> StartRequestPermissions(string[] permissions); }

In your activity (or base activity):

````csharp
#region Ask permissions
private readonly IDictionary private int nextFreeRequestCode = 0x3621;

    int AddOnRequestPermissionsResult(Action<string[], Permission[]> action)
    {
        var code = nextFreeRequestCode++;
        onRequestPermissionsResultList.Add(code, action);
        return code;
    }

    void RemoveOnRequestPermissionsResult(int requestCode)
    {
        onRequestPermissionsResultList.Remove(requestCode);
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        if (onRequestPermissionsResultList.TryGetValue(requestCode, out var action))
            action(permissions, grantResults);
    }

    async Task<Tuple<string[], Permission[]>> IActivityWithPermissionsCallback.StartRequestPermissions(string[] permissions)
    {
        var tcs = new TaskCompletionSource<Tuple<string[], Permission[]>>();
        int requestCode = 0;
        requestCode = AddOnRequestPermissionsResult((grantedPermissions, grantResults) =>
        {
            RemoveOnRequestPermissionsResult(requestCode);
            tcs.TrySetResult(Tuple.Create(grantedPermissions, grantResults));
        });

        RunOnUiThread(() =>
        {
            RequestPermissions(permissions, requestCode);
            //ActivityCompat.RequestPermissions(this, permissions,requestCode);
        });

        return await tcs.Task;
    }
    #endregion

````

@softlion What is potentially interesting about your concept is that you're flagging the context(activity) that requires a permission. This is Android specific code though. The whole purpose of the Permissions library is that you can use it from platform agnostic code.

What might be a possibility is to have a marker (interface or attribute) on ViewModels or any other class for that matter. We could leverage the Container to inspect the presence of this marker and request the specified permissions when a componnent is istantiated and it requires the permission.

The are two possible drawbacks to this solution:

  • At the moment is that the container does not seem to provide an extension points during Construction of components. We could inspect the presence of the marker on the CallbackWhenRegistered possibly so it won't require it do be done every time..
  • It does couple the plugin quite tightly to the Mvx Container implementation.

@jamesmontemagno thanks for tuning in on this conversation! I guess you mean providing a simple extension point like a callback so we can provide the Mvvm imlementation for resolving the current activity?

Yeah, my api for current activity is a single auto property to get and set the activity. I lay down a "mainapplication.cs", however it can be completely removed. What I could do is.... Create a core plugin that is just the api and the another that pulls this in and also lays down the mainapplication.cs file? Thoughts on that?

Any evolution of this feature ?

@na2axl no, but we're looking for someone to take it on and contribute!

Or you could use the plugin @jamesmontemagno wrote and call it just before you use any of our plugins. Haven't had any issues with that.

@na2axl does that satisfy what you're after?
@Cheesebaron is there anything else you feel we want to look at on this issue?

Nop. I'd say we close it.

Was this page helpful?
0 / 5 - 0 ratings