Antoni Maniscalco
Antoni Maniscalco

Reputation: 453

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

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.".

enter image description here

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;
            }
        }

Upvotes: 3

Views: 5693

Answers (2)

Augustin Bocken
Augustin Bocken

Reputation: 379

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 !

Upvotes: 2

Ahmad ElMadi
Ahmad ElMadi

Reputation: 2617

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.

Upvotes: 2

Related Questions