How do i request permissions when not on the UI thread? - android

I'm currently using the latest version of Xamarin.Forms (4.5.0.617) and Xamarin.Essentials (1.5.2).
I have a dependency injected service which is responsible for getting access to phone contacts, it's effectively a wrapper around the Xamarin.Essentials permissions code. I need to guarantee that the code is executed on the UI thread to avoid exceptions. I've got as far as the below code, however, it isn't working as i'd expect. In my app, the permissions popup appears and offers me the choice allow/deny but then all code after RequestAsync fails to execute like a response is never officially returned. The next time I run the app, it works straight away (so presumably permission in the background has been correctly recorded).
public async Task<bool> RequestAccessPhoneContacts()
{
PermissionStatus status = PermissionStatus.Denied;
status = await MainThread.InvokeOnMainThreadAsync<PermissionStatus>(async () =>
{
return await Permissions.RequestAsync<Permissions.ContactsRead>();
});
return (status == PermissionStatus.Granted);
}
I'm not sure if i've caused an issue with the way i'm trying to force the code onto the UI thread or whether i'm using the async code incorrectly... or (least likely) whether it's a bug in the Xamarin.Essentials Permissions code. I've only seen this behaviour on Android at the moment, but I haven't tested it on any others.
Any help greatly appreciated :)
thanks!

I had a similar issue when I called current location using Xamarin.Essentials. I got this exception:
Xamarin.Essentials.PermissionException: 'Permission request must be invoked on main thread.'
& this helped me resolve.
Call current location from main thread
private Task<Location> GetLastKnownLocation()
{
var locationTaskCompletionSource = new TaskCompletionSource<Location>();
Device.BeginInvokeOnMainThread(async () =>
{
locationTaskCompletionSource.SetResult(await Geolocation.GetLastKnownLocationAsync());
});
return locationTaskCompletionSource.Task;
}
And call above method inside Try catch block & also use configure await false
private async Task ExecuteGetGeoLocationCommand()
{
try
{
var location = await GetLastKnownLocation().ConfigureAwait(false);
}
catch (FeatureNotSupportedException fnsEx) {}
catch (Exception ex){}
}

Turns out I hadn't followed the documentation properly when setting this up.
After adding this into my main activity, everything kicks back to life.
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
This information came out of me raising a bug on the Xamarin.Essentials github -
https://github.com/xamarin/Essentials/issues/1227

Related

Xamarin Forms - How to check setting of RequestIgnoreBatteryOptimizations permission on Android

(See UPDATE below)
I have a Xamarin Forms app on Android which uses the Xamarin.Essentials library.
The app requires to run in the background to be fed location data (not particularly relevant to the question in hand, but included for context), and so must not be put to sleep by any battery optimisations that the OS might attempt.
I know that the user can manually opt out specific apps from Battery Optimizations, but as it is so crucial to the successful operation of the app, I would like the app to be able to :
check the Battery Optimization Opt-out permission status to ensure it is appropriately set,
and/or
force Android to opt the app of any battery optimizations.
I have added an entry into AndroidManifest.xml, but it doesn't seem to help, with the newly-installed app defaulting to being Battery Optimized.
AndroidManifest.xml
The manifest contains the following entry:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Xamarin.Essentials
This library gives access to a number of permission-related info on the device, but Battery Optimizations doesn't seem to be one of them.
Device being used
I don't know if it's relevant, but I am testing on a Samsung Galaxy S20 Ultra.
Can anyone offer any advice?
UPDATE Aug 28 2021
Following the advice from contributors and with reference to the docs at https://learn.microsoft.com/en-us/xamarin/essentials/permissions?tabs=android#extending-permissions ...
In My Shared Code
public interface IRequestIgnoreBatteryOptimizationPermission
{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
In My Android Project
[assembly: Xamarin.Forms.Dependency(typeof(RequestIgnoreBatteryOptimizationPermission))]
namespace MyAndroidProject
{
public class RequestIgnoreBatteryOptimizationPermission : Permissions.BasePlatformPermission, IRequestIgnoreBatteryOptimizationPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)>
{
(Android.Manifest.Permission.RequestIgnoreBatteryOptimizations, true)
}.ToArray();
}
}
On Shared App Initialization
// Ensure Required Permissions have been granted
var requestIgnoreBatteryOptimizationPermission = DependencyService.Get<IRequestIgnoreBatteryOptimizationPermission>();
var status = requestIgnoreBatteryOptimizationPermission.CheckStatusAsync().Result;
if (status != PermissionStatus.Granted)
{
status = requestIgnoreBatteryOptimizationPermission.RequestAsync().Result;
}
Result...
On calling CheckStatusAsync, the result comes back as Granted.
But the app settings still say otherwise...
I've tried it on both a physical device (Samsung Galaxy S20 Ultra) and on an Android Emulator (Pixel 2 API 28), with same result on both.
From this document, there are two ways to set Battery Optimization
An app can fire the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent to take the user directly to the Battery Optimization, where they can add the app.
An app holding the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission can trigger a system dialog to let the user add the app to the exemption list directly, without going to settings. The app fires a ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS Intent to trigger the dialog.
I use PowserManager.isIgnoringBatteryOptimizations to check Battery Optimization.
Firstly, add RequestIgnoreBatteryOptimizations in AndroidManifest.xml
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Then creating Interface in Shared code.
public interface IBattery
{
void getbattery();
}
Implementing this interface in Android platform.
[assembly: Dependency(typeof(ImplementBattery))]
namespace FormsSample.Droid
{
public class ImplementBattery : IBattery
{
public void getbattery()
{
Intent intent = new Intent();
String packageName = MainActivity.mac.PackageName;
PowerManager pm = (PowerManager)MainActivity.mac.GetSystemService(Context.PowerService);
if (pm.IsIgnoringBatteryOptimizations(packageName))
intent.SetAction(Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings);
else
{
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
}
MainActivity.mac.StartActivity(intent);
}
}
}
Creating static Mainactivity field in Mainactivity.cs.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static MainActivity mac;
protected override void OnCreate(Bundle savedInstanceState)
{
initFontScale();
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
mac = this;
Now, using DependencyService to fire.
private void Button_Clicked(object sender, EventArgs e)
{
DependencyService.Get<IBattery>().getbattery();
}
In your MainActivity add two properties:
private const int RequestPermissionsId = 0
and
private readonly string[] ManifestPermissions =
{
Manifest.Permission.RequestIgnoreBatteryOptimizations
// Add here other permissions you want to check
}
Then override the OnStart method and force permission check there:
public override void OnStart()
{
base.OnStart();
if ((int)Build.VERSION.SdkInt >= 23)
{
if (CheckSelfPermission(Manifest.Permission.RequestIgnoreBatteryOptimizations != Permission.Granted)
RequestPermissions(ManifestPermissions, RequestPermissionsId);
}
}
And of course, remember that you must have the OnRequestPermissionsResult method implemented:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
Well, I am guessing you are trying to check it directly through shared code
The easiest way to do it would be to extend the needed permission in native using essentials.
Check the status and request permission through an interface:
public interface IRequestIgnoreBatteryOptimizations
{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
Implement the native part for the same:
public class IgnoreBatteryOptimizationsPlatformPermission : Xamarin.Essentials.Permissions.BasePlatformPermission, IRequestIgnoreBatteryOptimizations
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new (string, bool)[] { (Manifest.Permission.RequestIgnoreBatteryOptimizations, true) };
}
Then register it in your Native XF caller class(MainActivity, Appdelegate(for some other permission))
DependencyService.Register<IRequestIgnoreBatteryOptimizations, IgnoreBatteryOptimizationsPlatformPermission>();
And then in your XF class use this method:
public static async Task<PermissionStatus> CheckAndRequestBatteryOptimizations()
{
var batteryOptimizationsPermission = DependencyService.Get<IRequestIgnoreBatteryOptimizations>();
var status = await batteryOptimizationsPermission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await batteryOptimizationsPermission.RequestAsync();
}
return status;
}
And then request for it whenever you like:
var status= await CheckAndRequestBatteryOptimizations();
More information here: https://learn.microsoft.com/en-us/xamarin/essentials/permissions?tabs=android#extending-permissions

FusedLocationProviderClient RemoveLocationUpdatesAsync Task does not complete

I am implementing a Xamarin Android Location tracker and added following code to stop location updates.
public Task StopAsync()
{
if(IsTracking)
{
IsTracking = false;
perviousStopTask = fusedLocationProviderClient.RemoveLocationUpdatesAsync(this);
}
return perviousStopTask;
}
Above method, I assign Task returns from RemoveLocationUpdatesAsync method to perviousStopTask and return it from StopAsync method. But when I try to await this StopAsync method it does not complete.
The Xamarin LocationSample project is placing blame on Google Play Services for this problem. The sample does not currently use await when calling RemoveLocationUpdatesAsync.
TODO: We should await this call but there appears to be a bug in Google Play Services where the first time removeLocationUpdates is called, the returned Android.Gms.Tasks.Task never actually completes, even though location updates do seem to be removed and stop happening. For now we'll just fire and forget as a workaround.
I am not sure whether Fused Location Provider is thread safe or not. However, you could try some of the following things.
It seems like you are returning a completed or a null when IsTracking is false, so I would change the method to:
public Task StopAsync()
{
if (IsTracking)
{
IsTracking = false;
return fusedLocationProviderClient.RemoveLocationUpdatesAsync(this);
}
return Task.CompletedTask;
}
If you are not really interested in returning back to the context you called the method from, make sure you call .ConfigureAwait(false):
await StopAsync().ConfigureAwait(false);

OnRequestPermissionsResult is not called in PageRenderer (Xamarin.Android)

I have a VideoCallPageRenderer like below
VideoCallPageRenderer : PageRenderer, Android.Support.V4.App.ActivityCompat.IOnRequestPermissionsResultCallback
{
public const int REQUEST_MIC = 0;
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
// Other codes
RequestMicPermission();
}
public void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
if (requestCode == REQUEST_MIC)
{
if (grantResults.Length == 1 && grantResults[0] == Permission.Granted)
{
}
}
}
private void RequestMicPermission()
{
Android.Support.V4.App.ActivityCompat.RequestPermissions((Activity)Xamarin.Forms.Forms.Context, new string[] { Android.Manifest.Permission.RecordAudio }, REQUEST_MIC);
}
}
Here RequestMicPermission works fine because I can see the pop up coming on screen asking for permission. But after I allow or deny OnRequestPermissionsResult is not called.
Any help? It would be very hard to try to override it in the Activity.
For anyone else having an issue here, I was not seeing the OnRequestPermissionsResult being called either. But I had the debugger attached, the handler wasn't doing anything, and even though the breakpoint was valid, it was never hit.
I finally did a clean and rebuild and voila, the breakpoint was hit.
Just in case someone else was tearing their hair out like myself :).
For future readers that encountered this problem too, a good approach is abstract the permissions features in a service and register it with Xamarin FormĀ“s Dependencyservice.
You can resolve this service in your renderer, or any non-activity class, to use it. Also resolve it in MainActivity, override OnRequestPermissionsResult, and call yourService.OnPermissionResult to communicate the result.
As Koushik says ActivityCompat.IOnRequestPermissionsResultCallback doesnt work because we call RequestPermissions with reference to MainActivity, so the result is obtained in this activity.
You can see an implementation example in my Github:
GpsService
MainActivity

Xamarin Forms - Lagging ScrollView after updating UI with HTTP POST Request Results

I'm working in the PCL with the last stable version of Xamarin Forms. For the moment, the targeted platform is Android.
First, I get the results from my HTTP POST request which is working perfectly fine.
Then, when I try to update the UI, the scrollview in which the results are displayed is lagging when I scroll down.
I think it might be a threading problem : the update of the UI may not be executed in the right thread.
I tried to use Device.BeginInvokeOnMainThread(() => { //Update the UI }); but it is not working.
Thanks.
Here is some of my code to help us :
Main Method:
private async void Search() {
ResponseService results = await libSearch.getResults(this.searchEntry.Text);
if (results.error != true) {
List<Choice> list = (List<Choice>)results.obj;
if(list.Count != 0) {
foreach(Choice choice in list) {
Device.BeginInvokeOnMainThread(() => {
addNewChoice(choice);
});
}
}
}
AddNewChoice Method:
private void addNewChoiceOnUI(Choice choice) {
ChoiceTemplate Choice = new ChoiceTemplate(choice.name, choice.img, choice.address);
this.Choices.Children.Add(Choice);
}
The Web Service Call Method seems to work. It looks like this :
public async Task<ResponseService> getResults(string zipcode) {
(...)
JObject results = await JsonWebRequest.getDataFromService(queryString).ConfigureAwait(false);
(...)
As you can see, I'm using an await on the web service call method and on the main method only. I'm also using ConfigureAwait(false); on the web service call method. I already tried numerous solutions. I would be so grateful if we could find a solution to avoid the lag.

How to handle permission requests outside Activity and Fragment?

I'm developing a custom compound View that needs to access external storage. How can I implement the permission handling without involving outside parties, i.e. Activity or Fragment?
I get that I can request the permissions using the View's context, but how can I handle onRequestPermissionsResult() inside the View? Is it even possible?
If it's not possible, what would be the most elegant solution to handle something like this?
I'm developing a custom compound View that needs to access external storage
IMHO, that's an architecture bug. A View is for displaying stuff to the user, and sometimes for collecting low-level input events and turning them into higher-order constructs (e.g., clicks, swipes). A View should not have any connection to files, databases, etc. See the MVC, MVP, MVVM, and similar GUI architecture patterns.
WebView, which does not abide by this, causes problems (e.g., doing disk I/O on the main application thread) as a result.
How can I implement the permission handling without involving outside parties, i.e. Activity or Fragment?
You can't. It is the responsibility of the activity or fragment to request the permission, presumably before your view needs this data.
what would be the most elegant solution to handle something like this?
Extract the data-access portion of this View into something else that is managed by the activity or fragment, where the threading, permissions, and other work associated with that data access can be managed.
You can't work with permissions without the instance of the activity, but you can do your code prettier. If you want to send a request and handle it in one place, then you can use the example below.
Just create something looks like BaseActivity and put there such code
public class PermActivity extends Activity {
interface OnPermissionCallback{
void requestResult(String[] permissions, int[] grantResults);
}
private SparseArray<OnPermissionCallback> permissionCallback = new SparseArray<>();
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
permissionCallback.get(requestCode).requestResult(permissions, grantResults);
}
public void addPermissionCallback(int requestCode, OnPermissionCallback callback){
permissionCallback.put(requestCode, callback);
}
}
And now in our client code, we can do something like that
class SomeClasThatWorksWithPerms{
private PermActivity activity;
public SomeClasWorksWithPerms(PermActivity activity) {
this.activity = activity;
}
void foo(){
if (ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
// do something
}else {
activity.addPermissionCallback(0, (perms, grantResults) -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
foo(); // try one more
}
});
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 0);
}
}
}
I have used spareArray and indexation by the request code but you can use another way of storing callbacks.
It's very simple example, you can see something more serious there
https://github.com/mrizyver/Fl_Launcher/blob/master/app/src/main/java/com/izyver/fllauncher/presentation/activity/FlActivity.kt - as you can see, it is activity
https://github.com/mrizyver/Fl_Launcher/blob/master/app/src/main/java/com/izyver/fllauncher/presentation/loaders/WallpaperLoader.kt - our client code that works with permissions
let us assume you need to call the requestPermissionLauncher from a dialog fragment when a user clicks on "OK" or some other button. here is the requestPermissionLauncher found in MainActivity or you can put it in any other activity where the dialog fragment is called from.
public ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
here is the code source if you want to refer https://developer.android.com/training/permissions/requesting
Then in your dialog fragment use the following code to call to the instance requestPermissionLauncher
((MainActivity)getContext()).requestPermissionLauncher.launch(Manifest.permission.[*your permission goes here*]);
It's only possible in Activities and Fragments.
What you can do is copy public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) in your View and call that method in the corresponding one in the Activity or Fragment where the Context is.

Categories

Resources