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
We should look into taking a dependency on @jamesmontemagno's Permissions plugin and use that in the core for each of these plugins.
Been looking at James his plugin and how to wrap that. There's a few things I'm not completely happy about.
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.
Have portable class decorator object for each plugin that wraps the real services does the actual permission request using the plugin
The permission plugin api is async. Not all the plugin interface are. We could:
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.
Adjust to wrap the actual implementation in the decorator.
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:
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
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:
@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.
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
PermissionRequestDecoratorBaseclass design described in the implementation below.Requirements:
Implementation
Create Task PermissionRequestDecoratorBase.Request(params Permission[] permissions)
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:
Implement IMvxConfigurablePlugin to
Provide (platform specific)
PermissionRequestSetupthat 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
PermissionRequestDecoratorBaseand thePermissionRequestSetup. Current architecture doesn't facilitate that as far as I can see. This would then require:Notes on using James Montemagno Plugin