How to resume the application and open a specific page with a push notification in Xamarin.forms - xamarin

I'm currently working on a Xamarin application working both on iOS and Android, but the problem I'm going to explain only concerns the Android application (this is not yet implemented in the iOS app).
Actually, when I receive a given push notification, I need to open a specific page in my application. It works very well if the application is open when the push notification is received, but the app crashes if my app is closed or run in background.
Well, when I receive the notification, I end up in the method called "OnShouldOpenCommand" :
private void OnShouldOpenCommand(string commandId)
{
NotifyNewCommand(AppResources.AppName, AppResources.CommandNotificationText, commandId);
Device.BeginInvokeOnMainThread(() =>
{
try
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
App.MasterDetailPage.NavigationStack.Push(((NavigationPage)(App.MasterDetailPage.Detail)).CurrentPage);
}
catch(Exception e)
{
Log.Debug("PushAsync", "Unable to push CommandAcceptancePage : "+ex.Message);
}
});
}
private void NotifyNewCommand(string Title,string Description, string commandId)
{
var intent = new Intent(this, typeof(MainActivity));
if (!String.IsNullOrEmpty(commandId))
{
intent.PutExtra("CommandId", commandId);
}
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
var notificationBuilder = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Kluox")
.SetContentText(Description)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
Notification notification = notificationBuilder.Build();
var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify(0, notification);
}
And the code
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
is generating an exception of type :
Java.Lang.IllegalStateException: Can not perform this action after
onSaveInstanceState
So well, I suppose I can't access "App" and redirect to another page if my application is not running in foreground. Well, this is when I receive the push notification an not when I click on it. But well, I do not intend to reopen my app by doing this.
Because afther that, when I click on the push notification called Kluox (and this is supposed to reopen my app), the app crashes and I really don't know why, I don't know where to put breakpoints to be able to debug because Visual Studio just tells me "An unhandled exception occured.".
Could anyone help me ? If you need any piece of code, you can just ask me, I'll edit my message and give you any information you need !
EDIT 1 : Here is the code of my OnCreate method :
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
var info = Intent.Extras?.GetString("CommandId", "");
global::Xamarin.Forms.Forms.Init(this, bundle);
if (String.IsNullOrEmpty(info))
{
LoadApplication(new App());
}
else
{
LoadApplication(new App(info));
}
if (instance == null)
{
instance = this;
RegisterWithGCM();
}
else
{
instance = this;
}
}

After overriding all the methods of MainActivity, I finally found the cause of the crash : the method OnDestroy was called twice, and threw a IllegalStateException because the activity was already destroyed. I found this workaround :
protected override void OnDestroy()
{
try
{
base.OnDestroy();
}
catch (Java.Lang.IllegalStateException ex)
{
Log.Debug("MainActivity.OnDestroy", ex, "The activity was destroyed twice");
}
}
And the exception is simply logged, the application can open and be used without problems.
I'll edit this answer when the redirection works too.
EDIT : how to redirect to a page
First, we needed to register for the MessagingCenter, in the constructor
public static MyPackage.Model.Command CurrentCommand { get; set; }
public App()
{
InitializeComponent();
MainPage = new ContentPage();
MessagingCenter.Subscribe<object, DataLib.Model.Command>(this, "Command", (sender, arg) => {
try
{
CurrentCommand = arg;
}
catch(Exception ex)
{
CurrentCommand = null;
}
});
}
And send the message when we get the push notification :
private void OnMessage(string serializedCommand)
{
//stuff happens
MessagingCenter.Send<object, MyPackage.Model.Command>(this, "Command", command);
}
Finally, when we get the OnStart() of App.Xaml.cs
if (CurrentCommand != null)
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(CurrentCommand, service))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
}
For now, it seems to do the trick ! More debugging will follow, but the code seems to work. Thanks a VERY lot to #BraveHeart for their help !

well luckily for you I was there few days ago and lost a lot of hair till I got it working in Android (and still in the strugle for iOS).
When you kill your app and instantiate it again form the icon or from the notification in both cases you will go to the main activity .
If we want to take some information in the main activity from the notification that instantiated it we do it like this in OnCreate():
var info = Intent.Extras?.GetString("info", "");
Now in your case I would add extra information to the notification showing that which View/Page this notification is about, something like the name of it for example)
This extra piece of information you can pass it to the constructor of the App before you load it.
In the constructor of the app you can check if there are extra info or not , if not that means to start the app's mainPage is the default MainPage, otherwise it is a certain page.

Related

Local Push notification not working in Xamarin iOS

I am using Xamarin.forms and implemented Local push notification for iOS. It is working successfully when I am debugging the app through visual studio even when the app is minimized, the app can able to receive the notification. But while running the app directly without debugging through visual studio, the app is not able to display the notification. Kindly guide me on this.
Then I also tried by releasing the app to the app store but experienced the same, the app is not able to receive the notification it not even in foreground mode.
I already have selected the "Background fetch" property under Background Modes in Info.plist.
I have also added below the line in my FinishedLaunching method
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(UIApplication.BackgroundFetchIntervalMinimum);
Entire Implementation of code is as below
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
try
{
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(UIApplication.BackgroundFetchIntervalMinimum);
try
{
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert |
UNAuthorizationOptions.Sound |
UNAuthorizationOptions.Sound,
(granted, error) =>
{
if (granted)
{
InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications);
}
});
}
else if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
bool IsRegistered = UIApplication.SharedApplication.IsRegisteredForRemoteNotifications;
}
catch (Exception ex)
{
UIAlertView avAlert = new UIAlertView("FinishedLaunching Push Notification Exception", ex.Message, null, "OK", null);
avAlert.Show();
}
UNUserNotificationCenter.Current.Delegate = new UserNotificationCenterDelegate();
LoadApplication(new MessengerClient.App());
}
catch (Exception ex)
{
NativeHelper.SendUnhandledException(ex, NativeHelper.iOS + ": FinishedLaunching");
}
return base.FinishedLaunching(app, options);
}
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
/// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
/// Cancel/clear all Local notifications fronm the tray.
UIApplication.SharedApplication.CancelLocalNotification(notification);
/// Cancel/clear all notifications fronm the tray.
UIApplication.SharedApplication.CancelAllLocalNotifications();
}
code for displaying the notification is as below.
UILocalNotification notification = new UILocalNotification();
notification.FireDate = NSDate.FromTimeIntervalSinceNow(1);
notification.AlertAction = title;
notification.AlertBody = content;
notification.AlertTitle = title;
notification.SoundName = UILocalNotification.DefaultSoundName;
notification.ApplicationIconBadgeNumber = 1;
UIApplication.SharedApplication.ScheduleLocalNotification(notification);
I know this is the repeat question but, I tried all the workaround but didn't work for me.
You can have a check with this Notifications in Xamarin.iOS .
iOS applications handle remote and local notifications in almost exactly the same fashion. When an application is running, the ReceivedLocalNotification method or the ReceivedRemoteNotification method on the AppDelegate class will be called, and the notification information will be passed as a parameter.
An application can handle a notification in different ways. For instance, the application might just display an alert to remind users about some event. Or the notification might be used to display an alert to the user that a process has finished, such as synching files to a server.
The following code shows how to handle a local notification and display an alert and reset the badge number to zero:
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
// show an alert
UIAlertController okayAlertController = UIAlertController.Create(notification.AlertAction, notification.AlertBody, UIAlertControllerStyle.Alert);
okayAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
Window.RootViewController.PresentViewController(okayAlertController, true, null);
// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
In addition , there is a sample you can download to check . It works no matter in Debug or Release Model in my local site (iOS 13.3).
The effect :
Just add this code in your AppDelegate DidFinishLaunching and notification will start working in the background. BackgroundTask- showNotification may get canceled sometime, for me whenever my dashboard is loaded, I guess as it has multiple API Calls. so add it in your DidEnterBackground Delegate as well with different taskID, to start a new background task. It works fine for me.
nint taskID = yourTaskID;
taskID = application.BeginBackgroundTask("showNotification", expirationHandler: ()=> {
UIApplication.SharedApplication.EndBackgroundTask(taskID);
});

OneSignal Xamarin.Forms HandleNotificationOpened redirect to notification specific page

in my Xamarin.Forms project I use OneSignal for notifications. In iOS Xamarin.Forms.Application.Current.MainPage = new NavigationPage(new NotificationPage()); worked but in Android this not work. I tried to use messaging center to communicate with PCL project. It worked when app is background but not working when app is closed. How can I redirect notification specific page when notification received in Android? Thanks
Note : Code edited and issue solved, I used shared preferences to control if app launched from notification or not. Then I Load xamarin.Forms application.
You can use below method before LoadApplication() method is called.
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
if (key != null)
{
var value = Intent.Extras.GetString(key);
Log.Debug(TAG, "Key: {0} Value: {1}", key, value);
}
}
}
LoadApplication(new App());
You have to set intent.putExtra() method in OnMessageReceivedMethod().
intent.PutExtra("Key", "value");
Then you can use redirection in App.xaml.cs file based on this key value. Because in android when notification is open while app is reinitialized.
I think you don't need to put below condition.
if (extrasList[0] == "true")
{
LoadApplication(new App(true));
}
else
{
LoadApplication(new App(false));
}
First Store your value global level so you can use it in App.cs file. Just use below code to handle page navigation in App() class like.
if (Device.RuntimePlatform == Device.Android)
{
if (YourKey == "true")
{
//handle that page navigation
}
else
{
//Default Page of App
}
}
Note : Code edited and issue solved, I used shared preferences to control if app launched from notification or not. Then I Load xamarin.Forms application
public class MainActivity :
global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
InitializeUI();
global::Xamarin.Forms.Forms.Init(this, bundle);
global::Xamarin.FormsMaps.Init(this, bundle);
ImageCircleRenderer.Init();
tV = new TextView(this);
resources = this.Resources;
OneSignal.Current.StartInit("***APP ID***")
.InFocusDisplaying(OSInFocusDisplayOption.Notification)
.HandleNotificationReceived(HandleNotificationReceived)
.HandleNotificationOpened(HandleNotificationOpened)
.EndInit();
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
var LaunchFromNotification = prefs.GetString("is_notification_received", "false");
if (LaunchFromNotification == "true")
{
LoadApplication(new App(true));
}
else
{
LoadApplication(new App(false));
}
OneSignal.Current.IdsAvailable(IdsAvailable); //Lets you retrieve the OneSignal player id and push token.
}
}
private static void HandleNotificationOpened(OSNotificationOpenedResult result)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(Android.App.Application.Context);
ISharedPreferencesEditor editor = prefs.Edit();
editor.PutString("is_notification_received", "true");
editor.Apply();
}
protected override void OnResume()
{
base.OnResume();
ISharedPreferences prefs =
PreferenceManager.GetDefaultSharedPreferences(this);
ISharedPreferencesEditor editor = prefs.Edit();
editor.Remove("is_notification_received");
editor.PutString("is_notification_received", "false");
editor.Apply();
}

Processing notifications in Xamarin Forms Android

I'm using the library https://github.com/aritchie/notifications and I can create and schedule notifications properly.
I wish to process them in Android so that depending on the notification - it will navigate to a particular page when the user taps on it.
I've found that the below event is fired when I tap on a notification (in my Android Project)
protected override void OnNewIntent(Intent intent)
{
}
However, I can't find any info in the intent from my notification in order to build up navigation to a particular page.
Any advice would be appreciated.
Cheers!
Edit #1 (Adding additional code for a related issue):
If I fire off a notification, and close the app before the notification is received - I receive an error saying the app has crashed. If I receive the notification and close the app - I can load the app from the notification OK.
I have a dependency service which hits the following methods.
public void Remind(DateTime dateTime, string msgtype, string usermedid)
{
DateTime now = DateTime.Now;
var diffinseconds = (dateTime - now).TotalSeconds;
Intent alarmIntent = new Intent(Forms.Context, typeof(AlarmBroadcastReceiver));
alarmIntent.PutExtra("notificationtype", msgtype);
alarmIntent.PutExtra("id", id);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Forms.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
AlarmManager alarmManager = (AlarmManager)Forms.Context.GetSystemService(Context.AlarmService);
//TODO: For demo set after 5 seconds.
alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);
}
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[]{"android.intent.action.BOOT_COMPLETED"}, Priority = (int) IntentFilterPriority.LowPriority)]
public class AlarmBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string notificationtype = intent.GetStringExtra("notificationtype");
PowerManager.WakeLock sWakeLock;
var pm = PowerManager.FromContext(context);
sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Reciever Tag");
sWakeLock.Acquire();
intent = new Intent(Forms.Context, typeof(MainActivity));
intent.PutExtra("notificationtype", notificationtype);
intent.AddFlags(ActivityFlags.IncludeStoppedPackages);
// Instantiate the builder and set notification elements, including pending intent:
NotificationCompat.Builder builder = new NotificationCompat.Builder(Forms.Context)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
.SetAutoCancel(true)
.SetContentIntent(PendingIntent.GetActivity(Forms.Context, 0, intent, 0)).SetContentTitle("Sample Notification")
.SetContentText("Hello World! This is my first action notification!")
.SetTicker("New Notification")
.SetSmallIcon(Resource.Drawable.icon);
// Build the notification:
Android.App.Notification notification = builder.Build();
notification.Flags = NotificationFlags.AutoCancel;
// Get the notification manager:
//NotificationManager notificationManager = Forms.Context.GetSystemService(Context.NotificationService) as NotificationManager;
var manager = NotificationManagerCompat.From(context);
// Publish the notification:
const int notificationId = 0;
manager.Notify(notificationId, notification);
sWakeLock.Release();
}
}
How do I keep my Broadcast Receiver alive when the app is closed?
Ok so it took me some time to figure this one out. OnNewIntent is called when the app is in the background and the notification is clicked on. It is also called each time the app is minimized and the brought back up... so to tell the difference between the 2 events, you need to check the passed in Intent for what extra data is inside it. The extra data would have come from the Intent you made when you first initiated the notification.
Also make sure to set your MainActivity's LaunchMode to LaunchMode.SingleTop so that your app does not get restarted each time the notification is clicked on.
[Activity(LaunchMode = LaunchMode.SingleTop, ....)]
public class MainActivity : FormsApplicationActivity {
....
/// <summary>
/// Called when the app is in the background and a notification is clicked on (also called each time the app is minimized and the brought back up), a new <c>Intent</c> is created
/// and sent out, since we use <c>LaunchMode</c> set to <c>SingleTop</c> this method is called instead of the app being restarted.
/// </summary>
/// <param name="intent">The <c>Intent</c> that was set when the call was made. If started from a notification click, extra <c>string</c> values can be extracted.</param>
protected override void OnNewIntent(Intent intent) {
if(intent.HasExtra("Some special key you made up")) { //Here is where you check for special notification intent extras
//Do something brilliant now that you know a notification was clicked on
}
base.OnNewIntent(intent);
}
To see how you can add data to the Intent you can check out the Xamarin Sport App, but do not get too bogged down in all the other stuff they are doing like I always tend to do. Just focus on the PutExtra part.
Edit #1:
If your app is completely closed, you need to pull the data from the Intent passed into OnCreate and pass it into your App class or do something else with it:
protected override async void OnCreate(Android.OS.Bundle bundle) {
base.OnCreate(bundle);
Forms.Init(this, bundle);
string parameterValue = Intent.GetStringExtra("Some special key you made up"); //This would come in from the Push Notification being clicked on
Console.WriteLine("\nIn MainActivity.OnCreate() - Param Intent Extras: {0}\n", parameterValue);
//MessagingCenter.Send("nothing", ConstantKeys.NewNotification); //Do something special with the notification data
LoadApplication(parameterValue != null ? new App(parameterValue) : new App()); //Do something special with the notification data
}
Edit #2:
Some changes I would recommend to your OnReceive method based on my current code (some may not be necessary, but it is just what I am doing):
Label your Broadcast Receiver
Add stupid Xamarin constructors
Used constant property instead of string for IntentFilter
Remove IntentFilter Priority
Check for null Intent (might not be necessary)
Use Application.Context instead of Forms.Context (I use Forms.Context in other parts of my app so not sure about this one, but
can't hurt)
Do not overwrite the passed in Intent
Create startup intent instead of regular
Add IncludeStoppedPackages flag before pulling out extras
Check for boot completed event
Use Notification.Builder instead of NotificationCompat.Builder (though you might need to change this back)
Add following flags to pendingintent: PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot
-- Use NotificationManager (unless you have a specific reason you commented it out)
[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
[assembly: UsesPermission(Android.Manifest.Permission.WakeLock)] //Optional, keeps the processor from sleeping when a message is received
[assembly: UsesPermission(Android.Manifest.Permission.ReceiveBootCompleted)] //Allows our app to be opened and to process notifications even when the app is closed
namespace Your.App.Namespace {
[BroadcastReceiver(Enabled = true, Label = "GCM Alarm Notifications Broadcast Receiver")]
[IntentFilter(new []{ Intent.ActionBootCompleted })]
public class AlarmBroadcastReceiver : BroadcastReceiver {
#region Constructors
// ReSharper disable UnusedMember.Global
public AlarmBroadcastReceiver() { }
public AlarmBroadcastReceiver(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { }
// ReSharper restore UnusedMember.Global
#endregion
public void Remind(DateTime dateTime, string msgtype, string usermedid) {
DateTime now = DateTime.Now;
var diffinseconds = (dateTime - now).TotalSeconds;
Intent alarmIntent = new Intent(Application.Context, typeof(AlarmBroadcastReceiver));
alarmIntent.PutExtra("notificationtype", msgtype);
alarmIntent.PutExtra("id", id);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Application.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
AlarmManager alarmManager = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService);
//TODO: For demo set after 5 seconds.
alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);
}
public override void OnReceive(Context context, Intent intent) {
#region Null Check
if(intent == null) {
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Intent is null\n");
return;
}
#endregion
intent.AddFlags(ActivityFlags.IncludeStoppedPackages);
string action = intent.Action;
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Action: {0}\n", action);
#region Boot Completed Check
if(action.Equals("android.intent.action.BOOT_COMPLETED")) {
PowerManager pm = PowerManager.FromContext(context);
PowerManager.WakeLock sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Receiver Tag");
sWakeLock.Acquire();
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Process Shared Preferences Notifications\n");
#region Process Saved Scheduled Notifications
//Get list of saved scheduled notifications that did not fire off before the device was turned off (I store them in SharedPreferences and delete them after they are fired off)
//Go through the list and reschedule them
#endregion
sWakeLock.Release();
return;
}
#endregion
string notificationtype = intent.GetStringExtra("notificationtype");
Intent startupIntent = Application.Context.PackageManager.GetLaunchIntentForPackage(Application.Context.PackageName);
startupIntent.PutExtra("notificationtype", notificationtype);
// Instantiate the builder and set notification elements, including pending intent:
Notification.Builder builder = new Notification.Builder(Application.Context)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
.SetAutoCancel(true)
.SetContentIntent(PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot))
.SetContentTitle("Sample Notification")
.SetContentText("Hello World! This is my first action notification!")
.SetTicker("New Notification")
.SetSmallIcon(Resource.Drawable.icon);
// Build the notification:
Android.App.Notification notification = builder.Build();
// Get the notification manager:
NotificationManager notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;
// Publish the notification:
int notificationId = ??;//This should be a real unique number, otherwise it can cause problems if there are ever multiple scheduled notifications
notificationManager.Notify(notificationId, notification);
}
}
}

Global Exception Handling in Xamarin Cross platform

Can you please let me know how to handle the global exception(without App crash) in Xamarin Cross platform project.
There isn't a 'Xamarin.Forms' way of doing it that I know of. You need to hook into Android and iOS, what you could do is create one method that handles them both in the same way.
A nice post about this is by Peter Norman. He describes that to implement it in Android you can do this in your MainActivity.cs.
// In MainActivity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
Xamarin.Forms.Forms.Init(this, bundle);
DisplayCrashReport();
var app = new App();
LoadApplication(app);
}
‪#‎region‬ Error handling
private static void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
LogUnhandledException(newExc);
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
LogUnhandledException(newExc);
}
internal static void LogUnhandledException(Exception exception)
{
try
{
const string errorFileName = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // iOS: Environment.SpecialFolder.Resources
var errorFilePath = Path.Combine(libraryPath, errorFileName);
var errorMessage = String.Format("Time: {0}\r\nError: Unhandled Exception\r\n{1}",
DateTime.Now, exception.ToString());
File.WriteAllText(errorFilePath, errorMessage);
// Log to Android Device Logging.
Android.Util.Log.Error("Crash Report", errorMessage);
}
catch
{
// just suppress any error logging exceptions
}
}
/// <summary>
// If there is an unhandled exception, the exception information is diplayed
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private void DisplayCrashReport()
{
const string errorFilename = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var errorFilePath = Path.Combine(libraryPath, errorFilename);
if (!File.Exists(errorFilePath))
{
return;
}
var errorText = File.ReadAllText(errorFilePath);
new AlertDialog.Builder(this)
.SetPositiveButton("Clear", (sender, args) =>
{
File.Delete(errorFilePath);
})
.SetNegativeButton("Close", (sender, args) =>
{
// User pressed Close.
})
.SetMessage(errorText)
.SetTitle("Crash Report")
.Show();
}
‪#‎endregion‬
And for iOS you can add code like this in your AppDelegate.cs.
//iOS: Different than Android. Must be in FinishedLaunching, not in Main.
// In AppDelegate
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary options)
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
// Rest of your code...
}
/// <summary>
// If there is an unhandled exception, the exception information is diplayed
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private static void DisplayCrashReport()
{
const string errorFilename = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Resources);
var errorFilePath = Path.Combine(libraryPath, errorFilename);
if (!File.Exists(errorFilePath))
{
return;
}
var errorText = File.ReadAllText(errorFilePath);
var alertView = new UIAlertView("Crash Report", errorText, null, "Close", "Clear") { UserInteractionEnabled = true };
alertView.Clicked += (sender, args) =>
{
if (args.ButtonIndex != 0)
{
File.Delete(errorFilePath);
}
};
alertView.Show();
}
It also includes the ability to show the log when you're debugging the application. Of course you can implement your own logging or handling methods. One thing you could look at is HockeyApp. This handles unhandled exceptions by default and sends them back to you, amongst other things.
Update, since this is still found on Google: For crash reporting and analytics you now want to start looking at App Center. This is the evolvement of HockeyApp and Xamarin Insights (among others like building, distributing and push notifications) and now acts as the mission dashboard for everything that has to do with apps, and not just Xamarin.
For UWP and WinPhone 8.1 there should be a UnhandledException handler in the Application object. Check out this answer for more information. I quote:
For XAML-based apps, you can use UnhandledException; however, that
only captures exceptions that come up through the XAML (UI) framework
and you don't always get a lot of information about what the root
cause is, even in InnerException.
Update for Windows 8.1: UnhandledException also will capture
exceptions that are created by an async void method. In Windows 8,
such exceptions would just crash the app. LunarFrog has a good
discussion of this on their website.
Basically you should add a event handler in the constructor of your App() in App.xaml.cs: this.UnhandledException += (o, s) => {}.
This is what I did for xamarin forms Android global exception handling and app crashes.
This solution will handle both background and foreground exceptions.
AppDomain.CurrentDomain.UnhandledException += (s,e)=>
{
System.Diagnostics.Debug.WriteLine("AppDomain.CurrentDomain.UnhandledException: {0}. IsTerminating: {1}", e.ExceptionObject, e.IsTerminating);
};
AndroidEnvironment.UnhandledExceptionRaiser += (s, e) =>
{
System.Diagnostics.Debug.WriteLine("AndroidEnvironment.UnhandledExceptionRaiser: {0}. IsTerminating: {1}", e.Exception, e.Handled);
e.Handled = true;
};
As a result, I have different behavior for both types of exceptions:
foreground:
background:

How to detect emergency call only in Xamarin?

Is there a way to detect if the phone, at the call try moment, can only make emergency calls (using Xamarin)?
This is something platform specific. Unfortunaelty I don't know any plugin for this, so you have to use the native API in Xamarin.
On Android it is the TelephonyManager as shown here: https://stackoverflow.com/a/14355706/1489968 It's Java, but can be easily translated to C#:
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
var telMng = (TelephonyManager) GetSystemService(TelephonyService);
var myPhoneStateListener = new MyPhoneStateListener();
myPhoneStateListener.ServiceStateChanged += (s, e) => Console.WriteLine("State: {0}", e);
telMng.Listen(myPhoneStateListener, PhoneStateListenerFlags.ServiceState);
}
}
public class MyPhoneStateListener : PhoneStateListener
{
public event EventHandler<ServiceState> ServiceStateChanged;
public override void OnServiceStateChanged(ServiceState serviceState)
{
base.OnServiceStateChanged(serviceState);
ServiceStateChanged?.Invoke(this, serviceState);
}
}
On iOS: Not sure if that information is available, never have seen it exposed via the SDK... (maybe add iOS tag to this question, or ask an iOS only question, answer might be in ObjC/Swift but you can translate it)
On Android: The info you are looking for is contained within the ServiceState of the phone:
var callState = new ServiceState ();
switch (callState.State) {
case PhoneState.InService:
{
var uri = Android.Net.Uri.Parse ("tel:555-2368"); // Jim Rockford's number ;-)
var intent = new Intent (Intent.ActionDial, uri);
StartActivity (intent);
break;
}
case PhoneState.EmergencyOnly:
{
Toast.MakeText (this, "Emergency Calls Only", ToastLength.Long).Show();
break;
}
case PhoneState.OutOfService:
{
Toast.MakeText (this, "Out of Service", ToastLength.Long).Show();
break;
}
case PhoneState.PowerOff:
{
Toast.MakeText (this, "Cell/Modem Power Off", ToastLength.Long).Show();
break;
}
default:
{
Toast.MakeText (this, "Should never be shown on a real device", ToastLength.Long).Show();
break;
}
}
Ref: http://developer.android.com/reference/android/telephony/ServiceState.html
For testing on the emulator, you can set the state to denied via the adb shell:
voice denied
data denied
Ref: https://developer.android.com/tools/devices/emulator.html

Resources