2

I am confused on how I am supposed to request permissions access on the main thread of an android app if it needs to be async. I want to do this as soon as my application opens, but as is I am getting the following error: "Permission request must be invoked on main thread." How do I do this? My understanding is that RequestAsync requires a separate thread (Since it's an async method call).

public partial class SplashPage : ContentPage
{
    PermissionStatus LocationPermission;

    public SplashPage()
    {
        InitializeComponent();
        LocationPermission = PermissionStatus.Unknown;
    }

    protected override void OnAppearing()
    {
        var result = Task.Run(async () => await CheckLocationPermission());
        result.Wait();
        var resultval = result.Result;

        result = Task.Run(async () => await RequestLocationPermission());
        result.Wait();
    }

    public async Task<PermissionStatus> CheckLocationPermission()
    {
        LocationPermission = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
        return LocationPermission;
    }

    public async Task<PermissionStatus> RequestLocationPermission()
    {
        try
        {
            if (LocationPermission == PermissionStatus.Granted)
                return LocationPermission;

            if (Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>())
            {
                await Shell.Current.DisplayAlert("Needs Permissions", "BECAUSE!!!", "OK");
            }

            LocationPermission = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
        }
        catch (Exception ex)
        {
            //Error that permissions request must be on main thread **
            Console.WriteLine(ex.Message);
        }

        return LocationPermission;
    }
}

AppShell.xaml:

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="CellularSignal1.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:CellularSignal1"
    Shell.FlyoutBehavior="Disabled">
    
    <TabBar x:Name="MyTabBar">
        <Tab x:Name="CellularInfo" Title="Cellular Info">
            <ShellContent ContentTemplate="{DataTemplate local:SplashPage}" Route="SplashPage"/>
            <ShellContent ContentTemplate="{DataTemplate local:CellularInfo}" Route="CellularInfo"/>
            <ShellContent ContentTemplate="{DataTemplate local:BSInfo}" Route="BSInfo"/>
        </Tab>
    </TabBar>
</Shell>

AppShell.xaml.cs:

namespace CellularSignal1;

#if ANDROID
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
    }
}
    
#endif
Julian
  • 5,290
  • 1
  • 17
  • 40
rickster26ter1
  • 373
  • 3
  • 10
  • Why are you instantiating the AppShell on Android only using preprocessor directives, if you don't mind me asking? – Julian Feb 26 '23 at 19:56
  • I'm not sure what you mean. Are you referencing the ShellContent stuff? I guess it's because it's the only way I know to create tab pages. – rickster26ter1 Feb 26 '23 at 20:30
  • 1
    *"separate thread .. since it's an async method call"* No, don't be confused by older docs and examples that run `Async` methods on separate threads. `Task`-returning `Async` methods are designed for use with c# `async`-`await` paradigm. The first code snippet in ewerspej's answer shows this. Because `OnAppearing` is already on `MainThread`, that is all that is needed in this case. – ToolmakerSteve Feb 26 '23 at 20:39
  • @ToolmakerSteve Exactly. I only included the `InvokeOnMainThreadAsync()` example for completeness's sake. – Julian Feb 26 '23 at 20:41
  • @ewerspej thank you for your answer. Your question above has to do with my next objective: controlling 'CellularInfo' above based on permissions given. Right now `AppShell` owns it, but if I put code to reference it inside AppShell's constructor, SplashPage gets run after AppShell's constructor. – rickster26ter1 Feb 26 '23 at 21:12
  • @rickster26ter1 That's technically a new question. Your problem with the permissions should be solved with the provided answers. If you want to use your `SplashPage` as a loading screen, then you need to set it separately. You can set `App.MainPage = new SplashPage();` while the app is loading and once everything is loaded and all permissions have been requested you can set `App.MainPage = new AppShell();`. Please open a separate question for this, if you haven't solved that issue already. – Julian Feb 28 '23 at 15:01

2 Answers2

6

You should make the OnAppearing() method override async and await the calls. There's no need for the thread pool thread created by Task.Run():

protected override async void OnAppearing()
{
    var resultCheck = await CheckLocationPermission();
    var resultRequest = await RequestLocationPermission();
}

In general, when you encounter an error like the one above referring to the main thread, it doesn't mean that the code requires its own thread, but rather that the code must be executed on the MainThread, which is a special thread.

You can invoke code on the main thread from any other thread, which isn't the main thread, like this:

private async Task SomeMethodAsync()
{
    await MainThread.InvokeOnMainThreadAsync(async () => 
    {
        var resultCheck = await CheckLocationPermission();
        var resultRequest = await RequestLocationPermission();
    });
}

Note: In your case, the latter shouldn't be necessary, because OnAppearing() already executes on the main thread.

Julian
  • 5,290
  • 1
  • 17
  • 40
3

You are using Essentials, so you could InvokeOnMainThreadAsync. I usually use this helper to do the same:

public static class PermissionsHelper
{
    public static async Task<PermissionStatus> CheckAndRequestPermissionAsync<TPermission>()
        where TPermission : BasePermission, new()
    {
        return await MainThread.InvokeOnMainThreadAsync(async () =>
        {
            TPermission permission = new TPermission();
            var status = await permission.CheckStatusAsync();
            if (status != PermissionStatus.Granted)
            {
                status = await permission.RequestAsync();
            }

            return status;
        });
    }
}

Usage:

var status = await PermissionsHelper.CheckAndRequestPermissionAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted)
{
    //whatever you like 
}
halfer
  • 19,824
  • 17
  • 99
  • 186
FreakyAli
  • 13,349
  • 3
  • 23
  • 63