I am working on integrating the geolocator plugin into my Xamarin.Forms app. I have installed the nuget package into .core, .droid & .ios. I haven't added it the the other projects in the solution, as I am on mac and they aren't supported.
I have added the example snippet (less the print to console lines), but it is throwing compiler errors. I have added using Geolocator; at the top, but the var position line throws error - the 'await' operator can only be used when its containing method is marked with the 'async' modifier - What have I done wrong?
I have included a screen shot below:
[![Compiler Errors][1]][1]
Any thoughts would be much appreciated.
UPDATE
My code now runs, I have the following structure:
namespace MyApp
{
public partial class HomePage : ContentPage
{
// Class Definitions
public HomePage(IAdapter adapter)
{
InitializeComponent();
this.adapter = adapter;
var LocManager = new CLLocationManager();
LocManager.AuthorizationChanged += (sender, args) => {
Debug.WriteLine ("Authorization changed to: {0}", args.Status);
};
if (UIDevice.CurrentDevice.CheckSystemVersion(8,0))
LocManager.RequestAlwaysAuthorization();
NewDeviceButton.Clicked += async (sender, e) => {
//Code which I would like to be able to use GetLatitude.
}
}
async Task<double> GetLongitude()
{
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 50;
var position = await locator.GetPositionAsync(timeoutMilliseconds: 10000);
var longitude = position.Longitude;
return longitude;
}
}
However, I get the following error message.
On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates! I originally only had the async method, but having seen the error message and reading your app note, I added the additional code at the top to try and authorise location services. However, I now get an error message saying Error CS0246: The type or namespace name 'CLLocationManager' could not be found. Are you missing an assembly reference? (CS0246) this shows on the var LocManager line. Why is this and what should I do to fix it?
It's normal an await method need to be run in an async method.
Therefore, you need to call your method in the OS projects.
Here the steps to work with geolocator plugin :
Create an interface on your xamarin form project:
public interface ILocation
{
Task<Position> GetLocation();
}
Create a class in your OS project ( Android, IOS or WP )
Here the exemple for Android
public class Location_Android : Activity, ILocation
{
public async Task<Position> GetLocation()
{
return await GetPosition();
}
private async Task<Position> GetPosition()
{
Position result = null;
try
{
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 50;
if (locator.IsListening != true)
{
locator.StartListening(minTime: 1000, minDistance: 0);
}
var position = await locator.GetPositionAsync(10000);
//You can use Xamarin.Forms.Maps.Position
//Here I use a personnal class
result = new Position(position.Longitude, position.Latitude);
}
catch (Exception e)
{
Log.Debug("GeolocatorError", e.ToString());
}
return result;
}
}
On your class call the method like that
var position = await DependencyService.Get<ILocation>().GetLocation();
Hope this can help you.
Related
I am working on a rather complex xamarin forms app for a while now and now I came over a problem related to the async initialization of the app. Some context before I come to the problem.
I am using the current version of Xamarin Forms, my main target is
xamarin android
I am building on top of the xamarin forms Shell
pattern
I am using a sqlite database for the main part of the apps
configuration
The app layout is mainly dependent on the configuration stored in the sqlite database (what I call a data driven app)
Therefore I am building the whole shell page layout not in xaml but in code behind of the AppShell.xaml.cs - in the constructure to be concrete (first weird point in my personal opinion).
So first remarkable point here - I need the fully loaded configuration BEFORE the AppShell is initiated.
At first a started with a mockup data service where the whole config was inside a large mocked object so it ran fully synchronized - everything perfect!
After switching over to the sqlite implementation where I am using async methods the problem started to show up because I try to load the configuration in the constructor (second weird point because such a task should never be part of a constructor in my point of view)
So my constructor of AppShellView.xaml looks like this.
public App()
{
InitializeComponent();
InitApp().SafeFireAndForget(onException: ErrorHandler);
MainPage = new AppShellView();
}
the InitApp Method where the database is queried looks like this.
private async Task InitApp()
{
_settingsService = ViewModelLocator.Resolve<ISettingsService>();
ViewModelLocator.UpdateDependencies(_settingsService.UseDemoMode);
_dataService = ViewModelLocator.Resolve<IDataService>();
ActiveConfiguration = await _dataService.GetActiveConfigurationAsync().ConfigureAwait(false);
//MainPage = new AppShellView(); this was added to reload the MainPage after the config was fully loaded
}
what obviously happens is that the MainPage = new AppShellView() in the App's constructor is called before the config is loaded - because its called in a fireandforget way. Therefore the following construction of the AppShellView does not work as intendet because the pages cannot be added because of the missing config. I hope this is clear so far.
To overcome this issue I added another MainPage = new AppShelLView() as last step of the InitApp method.
The constructor of the AppShellView looks like this:
public AppShellView()
{
InitializeComponent();
BuildMainNavigation();
BuildSettingsNavigation();
InitRoutes();
this.CurrentItem.CurrentItem = startSection;
}
This seems to work on thirst sight but triggers side effects when the app is started from AppLink Shortcuts and not to forget I ended up initializing the same object again and again which causes performance issues and simply smells...
So I hope I made my point clear so far - I know that I have a view issues in my implementation but I simply don't see the correct way of doing it.
What I would naturally would try to do is doing the configuration loading in the android init part behind the splash screen which I implemented like this: https://learn.microsoft.com/en-us/xamarin/android/user-interface/splash-screen but I also found no way to pass the data from the android Activity to the App class.
To sum up and ask a concrete question:
What is the best practice way of loading async configuration BEFORE initializing the Shell
#Jason: thanks for your fast response. So this idea seems promising, sounds like another Splash Screen but thats ok.
I tried that - for the initial setup that works fine.
App's constructor is simplified to this:
public App()
{
InitializeComponent();
MainPage = new Splashscreen();
}
The OnStart looks like this now:
protected async override void OnStart()
{
base.OnStart();
if (!IsInitiated)
{
_settingsService = ViewModelLocator.Resolve<ISettingsService>();
ViewModelLocator.UpdateDependencies(_settingsService.UseDemoMode);
_dataService = ViewModelLocator.Resolve<IDataService>();
ActiveConfiguration = await _dataService.GetActiveConfigurationAsync().ConfigureAwait(true);
MainPage = new AppShellView();
App.Current.UserAppTheme = _settingsService.OSAppTheme;
}
else
{
App.Current.UserAppTheme = _settingsService.OSAppTheme;
}
base.OnResume();
}
But I have app link functionality, where OnAppLinkRequestReceived is called.
protected override async void OnAppLinkRequestReceived(Uri uri)
{
if(MainPage is Splashscreen)
{
MainPage = new AppShellView();
}
IsInitiated = true;
var targets = uri.ToString().Replace(GlobalSetting.AppShortcutUriBase, "");
var subtargets = targets.Split('/');
App.CurrentPageId = new Guid(subtargets[0]);
if(subtargets.Length > 1 && !string.IsNullOrWhiteSpace(subtargets[1])){
await Shell.Current.GoToAsync($"//{App.CurrentPageId}?buildingPartId={new Guid(subtargets[1])}");
}
else
{
await Shell.Current.GoToAsync("//" + App.CurrentPageId);
}
}
The Problem is that its called before OnStart. So its calling the correct page first but the reinit in OnStarts resets the app to base.
I could overcome this issue with performing the init only of OnAppLinkRequestReceived is not called but then I have one last issue.
I call App.Current.UserAppTheme = _settingsService.OSAppTheme; as displayed above inside OnStart end the call inside the else black is failing with NullpointerException because App.Current seems to be null in this case. Do you know why?
Here are the final code bits that made the whole thing work. The key aspects are as follows:
Add another SplashScreen which is initiated inside App.xaml.cs constructor
Perform the long running async method in the OnStart()
Perform path based navigation which is triggered by an AppLinkRequest also inside the OnStart after storing the AppLinkRequest Uri inside a property
public App()
{
InitializeComponent();
_settingsService = ViewModelLocator.Resolve<ISettingsService>();
ViewModelLocator.UpdateDependencies(_settingsService.UseDemoMode);
_dataService = ViewModelLocator.Resolve<IDataService>();
AppLinkUri = null;
MainPage = new SplashScreen();
}
private async Task InitApp()
{
ActiveConfiguration = await _dataService.GetActiveConfigurationAsync().ConfigureAwait(true);
}
protected override async void OnStart()
{
base.OnStart();
if (ActiveConfiguration == null)
{
await InitApp().ConfigureAwait(true);
}
MainPage = new AppShellView();
if (AppLinkUri != null)
{
var targets = AppLinkUri.ToString().Replace(GlobalSetting.AppShortcutUriBase, "");
var subtargets = targets.Split('/');
App.CurrentPageId = new Guid(subtargets[0]);
if (subtargets.Length > 1 && !string.IsNullOrWhiteSpace(subtargets[1]))
{
await Shell.Current.GoToAsync($"//{App.CurrentPageId}?buildingPartId={new Guid(subtargets[1])}");
}
else
{
await Shell.Current.GoToAsync("//" + App.CurrentPageId);
}
}
base.OnResume();
}
protected override void OnAppLinkRequestReceived(Uri uri)
{
AppLinkUri = uri;
}
I'm trying to create an app. Here I'm trying to get the user's current location by clicking the button. But it produces an exception like Not Implemented exception - This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation
Already I did clean and rebuild the solution.
And I've enabled the permission for access_fine_location,
access_coarse_location.
I've added plugin.current and add an activity inside the mainactivity.cs
file.
string answer ="";
try
{
await CrossGeolocator.Current.StartListeningAsync(new
TimeSpan(20000),10);
if (CrossGeolocator.Current.IsListening)
{
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 50;
var position = await
locator.GetPositionAsync(TimeSpan.FromSeconds(20000));
string lat = position.Latitude.ToString();
string lon = position.Longitude.ToString();
answer = lat + lon;
}
else
{
answer= "Not listening";
}
}
catch (Exception ex)
{
answer= ex.Message;
}
I need the result which contains the longitude and latitude values.
What I've to do in my project?
Edited :
public async Task<String> GetLastKnownLocationAsync()
{
Position ObjPosition = null;
try
{
await DependencyService.Get<IGeolocator>().StartListeningAsync(new TimeSpan(20000), 10);
if (CrossGeolocator.Current.IsListening)
{
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 50;
ObjPosition = await DependencyService.Get<IGeolocator>().GetPositionAsync(TimeSpan.FromSeconds(20));
string lat = ObjPosition.Latitude.ToString();
string lon = ObjPosition.Longitude.ToString();
Info = lat + lon;
}
else
{
Info = "Not listening";
}
return Info;
}
catch (Exception ex)
{
Info = ex.Message;
return Info;
}
}
I'm getting error on the sixth line.
I have implemented GPS tracking functionality in my Xamarin.Forms application too. And as per my personal experience, Geolocator plugin doesn't work as expected with Xamarin.Forms and also have some issues and limitations too. This plugin is developed by taking reference of Xamarin Essentials Geolocation. I will suggest that you should use Xamarin Essentials instead of Geolocator plugin as it is well documented and easy to implement without any major issues. You can find step by step guid to implement Xamarin Essentials Geolocation from following link:
https://learn.microsoft.com/en-us/xamarin/essentials/geolocation?tabs=android
Did you install the NuGet package on both your shared project as well as your platform projects? The error message, in this case, is quite accurate. What basically happens here is that this plugin installs a form of dependency service that has a platform-specific implementation and just an interface on your shared project.
For some reason, your calls end up in the shared code, which only implements this exception to let you know you're in the wrong place. This is usually due to not having the package installed on your platform project, or, the package being "optimized away" by the linker. This tends to happen because the compiler notices there is no reference from your project to this library, so it strips it to let it take up less space.
To make sure this last thing doesn't happen, you can go into your platform project, in this case, Android and go into the MainActivity.cs and add a dummy reference to an object in this plugin. For instance, add this:
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
// THIS WAS ADDED
var foo = new Plugin.Geolocator.Abstractions.Address();
LoadApplication(new App());
}
This is actually the reason a lot of these libraries used to have an Init method.
Having said all this, I actually have to agree with Akshay in the other answer and if at all possible, you probably want to look at upgrading your project to use .NET Standard and move to the Xamarin.Essentials library which takes all of this pain away.
Here is a little advice for you, that I usually use: if you have an issue and can't resolve it, try to create a separate little project with step-by-step tutorial of how that works. Usually it works, and you can find out what exactly wrong with your main solution.
EDIT:
Link to DependencyService
Shortly, you have to create Interface that would be used by you, i.e. IMyInterface. After that, write platform-specific classes for Android/iOS with interface implementation. When you write it, you can use the methods like so:
DependencyService.Get<IMyInterface>().YourMethod().
EDIT 2:
You have to add this for your platform-specific classes:
[assembly: Dependency(typeof(MyCustomClass))] // Check this assembly
namespace ISSO_I.Droid.PlatformSpecific
{
public class MyCustomClass: IMyInterface
{
/// your code here
}
}
EDIT 3:
Call location Updates:
protected override void OnAppearing()
{
base.OnAppearing();
ToIssoView = false;
RequestLocationUpdates(); // Call this method
}
Method to request location updates:
public async void RequestLocationUpdates()
{
/// check permission for location updates
var hasPermission = await CommonStaffUtils.CheckPermissions(Permission.Location);
if (!hasPermission)
return;
if (CrossGeolocator.Current.IsListening) return;
MyMap.MyLocationEnabled = true;
CrossGeolocator.Current.PositionChanged += Current_PositionChanged;
CrossGeolocator.Current.PositionError += Current_PositionError;
await CrossGeolocator.Current.StartListeningAsync(TimeSpan.FromSeconds(1), 5);
}
public static async Task<bool> CheckPermissions(Permission permission)
{
var permissionStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(permission);
var request = false;
if (permissionStatus == PermissionStatus.Denied)
{
if (Device.RuntimePlatform == Device.iOS)
{
var title = $"{permission} Permission";
var question = $"To use this plugin the {permission} permission is required. Please go into Settings and turn on {permission} for the app.";
const string positive = "Settings";
const string negative = "Maybe Later";
var task = Application.Current?.MainPage?.DisplayAlert(title, question, positive, negative);
if (task == null)
return false;
var result = await task;
if (result)
{
CrossPermissions.Current.OpenAppSettings();
}
return false;
}
request = true;
}
if (!request && permissionStatus == PermissionStatus.Granted) return true;
{
var newStatus = await CrossPermissions.Current.RequestPermissionsAsync(permission);
if (!newStatus.ContainsKey(permission) || newStatus[permission] == PermissionStatus.Granted) return true;
var title = $"{permission} Permission";
var question = $"To use the plugin the {permission} permission is required.";
const string positive = "Settings";
const string negative = "Maybe Later";
var task = Application.Current?.MainPage?.DisplayAlert(title, question, positive, negative);
if (task == null)
return false;
var result = await task;
if (result)
{
CrossPermissions.Current.OpenAppSettings();
}
return false;
}
}
I am trying to get the gRPC C# example working inside WPF.
The same code which is working inside a Console Application is not working. What am I missing.
The minimal class which works in the Console App and does not work in WPF looks like this:
public class GrpcClientImpl
{
private GrpcService.GrpcService.GrpcServiceClient client;
public GrpcTestClientImpl()
{
var channel = new Channel("127.0.0.1:6980", ChannelCredentials.Insecure);
client = new GrpcService.GrpcService.GrpcServiceClient(channel);
ProcessFeed().Wait();
}
public async Task ProcessFeed()
{
try
{
using (var call = client.Feed(new FeedRequest()))
{
var responseStream = call.ResponseStream;
while (await responseStream.MoveNext())
{
var result = responseStream.Current;
Console.WriteLine("received result");
}
}
}
catch (RpcException e)
{
Console.WriteLine("RPC failed " + e);
throw;
}
}
}
The responseStream.MoveNext() is where it is hanging. It does not respond to sent items and it does also not trigger an exception if the gRPC server is not there. What have I missed?
The problem is the blocking call ProcessFeed().Wait(); within the constructor.
This post explains why:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
To solve the issue, call await ProcessFeed(); from outside (not in the costructor).
I have been trying to write Background Task that would show raw push notification as toast. I got push notifications working when app is running.
This is my background task class:
public sealed class BackgroundNotificationsTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
RawNotification notification = (RawNotification)taskInstance.TriggerDetails;
string content = notification.Content;
Debug.WriteLine("Background raw notification obtained!");
//SendNotification(content);
}
private void SendNotification(string text)
{
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
XmlNodeList elements = toastXml.GetElementsByTagName("text");
foreach (IXmlNode node in elements)
{
node.InnerText = text;
}
ToastNotification notification = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(notification);
}
}
Then I Register In MainPage.xaml.cs
private void RegisterTasks()
{
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
var taskRegistered = false;
var exampleTaskName = "NotificationsBackground";
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
if (task.Value.Name == exampleTaskName)
{
taskRegistered = true;
break;
}
}
if(!taskRegistered)
{
var builder = new BackgroundTaskBuilder();
builder.Name = exampleTaskName;
builder.TaskEntryPoint = "BackgroundTasks.NotificationsBackground";
builder.SetTrigger(new Windows.ApplicationModel.Background.PushNotificationTrigger());
try
{
BackgroundTaskRegistration task = builder.Register();
Debug.WriteLine("Background Task registered.");
}
catch (Exception e)
{
Debug.WriteLine("Background Task register exception: " + e.ToString());
}
}
}
Now in appxmanifest I have set 'Lock screen notifications' to Badge, then in Declarations I have added Background Task with properies Push notification selected and entry point set as BackgroundNotificationsTask.cs
![screen][2]
Am I doing something wron or is there something that I am missing?
EDIT:
Right now when i obtain push notification the app closes... anyone know why?
There are a couple of things you're doing wrong.
1) Put your BackgroundTask in a separate project
BackgroundTask projects should be Windows Runtime Components. Also make sure that your background task resides under an accessible namespace. Do not forget to reference the background task project from your app project.
2) Register the correct class
When registering your background task, always use the fully qualified class name and not the file name:
BackgroundTasks.BackgroundNotificationsTask
This is the entry point you'll have to use in the package manifest file and in your code (given that the task class is in the project explained under 1) and the namespace is called BackgroundTasks).
3) Call RequestAccessAsync()
Make sure you call this before registering any tasks:
BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
Edit: There is a pretty good walkthrough on MSDN https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh977055.aspx
I'm working on WinRT. If an unhandled exception is thrown I want to write the message text to the storage.
I added an Event handler in 'App.xaml.cs', see the code.
The exception is caught but the last line, where the file is written, crashes again -> 'exception'!
Why? Any idea?
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
this.UnhandledException += App_UnhandledException;
}
async void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;
StorageFile file= await folder.CreateFileAsync("crash.log",CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, e.Message); // <----- crash again -----
}
Thanks
Sunny
I've been wondering the same thing and stumbled across this quite early on in my search. I've figured out a way, hopefully this will prove useful to someone else too.
The problem is that await is returning control of the UI thread and the app's crashing. You need a deferral but there's no real way to get one.
My solution is to use the settings storage, instead. I'm assuming most people wanting to do this want to do something LittleWatson style, so here's some code modified from http://blogs.msdn.com/b/andypennell/archive/2010/11/01/error-reporting-on-windows-phone-7.aspx for your convenience:
namespace YourApp
{
using Windows.Storage;
using Windows.UI.Popups;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
public class LittleWatson
{
private const string settingname = "LittleWatsonDetails";
private const string email = "mailto:?to=you#example.com&subject=YourApp auto-generated problem report&body=";
private const string extra = "extra", message = "message", stacktrace = "stacktrace";
internal static void ReportException(Exception ex, string extraData)
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = extraData;
exceptionValues[message] = ex.Message;
exceptionValues[stacktrace] = ex.StackTrace;
}
internal async static Task CheckForPreviousException()
{
var container = ApplicationData.Current.LocalSettings.Containers;
try
{
var exceptionValues = container[settingname].Values;
string extraData = exceptionValues[extra] as string;
string messageData = exceptionValues[message] as string;
string stacktraceData = exceptionValues[stacktrace] as string;
var sb = new StringBuilder();
sb.AppendLine(extraData);
sb.AppendLine(messageData);
sb.AppendLine(stacktraceData);
string contents = sb.ToString();
SafeDeleteLog();
if (stacktraceData != null && stacktraceData.Length > 0)
{
var dialog = new MessageDialog("A problem occured the last time you ran this application. Would you like to report it so that we can fix the error?", "Error Report")
{
CancelCommandIndex = 1,
DefaultCommandIndex = 0
};
dialog.Commands.Add(new UICommand("Send", async delegate
{
var mailToSend = email.ToString();
mailToSend += contents;
var mailto = new Uri(mailToSend);
await Windows.System.Launcher.LaunchUriAsync(mailto);
}));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
}
catch (KeyNotFoundException)
{
// KeyNotFoundException will fire if we've not ever had crash data. No worries!
}
}
private static void SafeDeleteLog()
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = string.Empty;
exceptionValues[message] = string.Empty;
exceptionValues[stacktrace] = string.Empty;
}
}
}
To implement it, you need to do the same as the link above says, but to ensure the data's here in case the url ever goes down:
App.xaml.cs Constructor (BEFORE the call to this.InitializeComponent()):
this.UnhandledException += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Obviously if you already have an UnhandledException method you can throw the call to LittleWatson in there.
If you're on Windows 8.1, you can add a NavigationFailed call too. This needs to be in an actual page (typically MainPage.xaml.cs or whatever page is first opened):
xx.xaml.cs Constructor (any given page):
rootFrame.NavigationFailed += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Lastly, you need to ask the user if they want to send the e-mail when the app re-opens. In your app's default Page's constructor (default: the page App.xaml.cs initializes):
this.Loaded += async (s, e) => await LittleWatson.CheckForPreviousException();
Or add the call to your OnLoad method if you already use it.
In this situation, await could be loosely translated to "do this job on another thread, and continue what you were doing while you wait for it to finish". Given that what your app was doing was crashing, you probably don't want it to continue doing that until you're done logging the problem. I'd suggest running your file IO synchronously in this case.
This may come a bit too late for the original question but...
as #Hans Passant suggested, avoiding await (i.e., running the FileIO.AppendTextAsync() synchronously), also seconded by #Jon, I would opt for this rather than the relatively too heavy code for LittleWatson. As the app is in some error handing state anyway (this should be a rare occurrence) I wouldn't put any blocking arising from synchronous (due to removing await) as a major downside.
Leaving the synchronous option to one side, the following await implementation worked for me:
Change await FileIO.AppendTextAsync(file, e.Message); to:
Task task = LogErrorMessage(file, e.Message)
task.Wait(2000); // adjust the ms value as appropriate
...
private async Task LogErrorMessage(StorageFile file, string errorMessage)
{
await FileIO.AppendTextAsync(file, errorMessage); // this shouldn't crash in App_UnhandledException as it did before
}