3
votes

I'm developing a cellphone app for iOS and Android platforms using Xamarin and MvvmCross. I can receive push notifications while the app is in foreground and in background.

When the app is closed, I also receive the push notifications and when I click on them, the app starts but I know no way to distinguish if the app started because of user clicked on the app icon or because the push notification was clicked. I want to navigate to a concrete view model when the app was closed and is started by a push notification.

I've found this article, which proposes a solution I didn't implement yet, that could solve the problem in platform dependant code (iOS):

https://theconfuzedsourcecode.wordpress.com/2015/09/22/handling-push-notifications-in-xamarin-ios-when-app-is-closed/

Also pointing this direction there's this answer in the xamarin forums:

https://forums.xamarin.com/discussion/67698/ios-notifications-while-app-is-closed

If the user taps on the banner displayed, the >AppDelegate.FinishedLaunching method is called. The NSDictionary of >options passed in will contain >UIApplication.LaunchOptionsRemoteNotificationKey to let you know the >app was launched because of a push notification. You can check for this >and handle it accordingly for your app. i.e. Display an alert, navigate >to a page, etc.

My question though is, how can I do that in the Core project shared by the different platforms? I've followed the steps shown at the end of this article in he MvvmCross documentation:

https://www.mvvmcross.com/documentation/advanced/customizing-appstart

So my code looks as follows:

App.cs

public class App : MvxApplication<object>
{
    public override void Initialize()
    {
        CreatableTypes().EndingWith("Service").AsInterfaces().RegisterAsLazySingleton();
        Mvx.IoCProvider.LazyConstructAndRegisterSingleton<IAppStartSynchronizer, AppStartSynchronizer>();
        Mvx.IoCProvider.RegisterSingleton(() => UserDialogs.Instance);

        RegisterCustomAppStart<HaasSohnAppStart<AppSyncViewModel>>();
    }

    public override object Startup(object parameter)
    {
        //never reached
        Console.WriteLine("APP.CS STARTUP");
        return base.Startup(parameter);          
    }
}

Custom AppStart

public class HaasSohnAppStart<AppSyncViewModel> : MvxAppStart<AppSyncViewModel, object> where AppSyncViewModel : IMvxViewModel<object>
{
    public HaasSohnAppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService){}

    /// <summary>
    /// Don't await the navigation to the first view model to be able to do async operations in the first <typeparamref name="AppSyncViewModel"/>
    /// see: https://nicksnettravels.builttoroam.com/post/2018/04/19/MvvmCross-Initialize-method-on-the-first-view-model.aspx
    /// see: https://stackguides.com/questions/49865041/mvvmcross-app-wont-start-after-upgrading-to-version-6-0 
    /// see: https://github.com/MvvmCross/MvvmCross/issues/2829
    /// </summary>
    /// <param name="hint">Hint.</param>
    protected override Task NavigateToFirstViewModel(object hint)
    {
            //First of all configure app synchronously to have db connection, api urls, translations, etc configured
        AppConfig.Initialize().GetAwaiter().GetResult();
        NavigationService.Navigate<AppSyncViewModel, object>(hint);
        return Task.CompletedTask;
    }

    protected override Task<object> ApplicationStartup(object hint = null)
    {
        //hint always null
        Console.WriteLine($"APPSTART APPLICATION STARTUP HINT = {hint}");
        return base.ApplicationStartup(hint);
    }
}

Extract of first view model

public class AppSyncViewModel : BaseViewModel, IMvxViewModel<object>
{
    private IAppStartSynchronizer _appSyncrhronizer;
    private INetworkAvailable _networkAvailable;
    private MainController _mainController = MainController.Instance;
    override public string Title => Strings["AppSyncViewModel_Label_Title"];

    public string SyncInfo { get; set; } = "AppSyncViewModel_Label_SyncInProgress".Translate();

    private bool _comeFromPushNotification;
    public AppSyncViewModel(IMvxNavigationService navigationService, IAppStartSynchronizer appSynchronizer, INetworkAvailable networkAvailable, IUserDialogs userDialogs) : base(navigationService, userDialogs)
    {
        _appSyncrhronizer = appSynchronizer;
        _networkAvailable = networkAvailable;
    }

    public override async Task Initialize()
    {
        if (_appSyncrhronizer != null)
        {
            IsBusy = true;
            _appSyncrhronizer.SyncFinish += Syncnronizer_SyncFinish;
            await _appSyncrhronizer.StartupSync(_networkAvailable);
        }

        await base.Initialize();
    }

    public async void Syncnronizer_SyncFinish(object sender, System.EventArgs e)
    {
        if (_comeFromPushNotification)
        {
            await _userDialogs.AlertAsync("we come from push notification", "parameter");
        }            
    }        

    public void Prepare(object parameter)
    {
        //parameter always null
        _comeFromPushNotification = parameter != null ? true : false;
        Console.WriteLine($"APPSYNCVIEWMODEL: parameter = {parameter}");
    }

I expected this implementation to receive some parameter in the hint parameter of the custom app start class in the NavigateToFirstViewModel method and then in the prepare method of the first view model (AppSyncViewModel) set a flag to know if we opened the app from a push notification, depending on this parameter.

The result, though, is that the parameter is always null, even when the app is closed and it starts from a push notification.

So, to summarise my question, how can I detect, in Core project using MvvmCross that the app started from a push notification?

1

1 Answers

2
votes

So the reason you are getting back null for the hint is because MvvmCross overrides the passed in object to null in MvxApplicationDelegate.

So when you tap a push notification on iOS, the FinishedLaunching method is called with the NSDictionary parameter populated with the LaunchOptionsRemoteNotificationKey. What happens is that this key is overriden to null instead of passed through.

You can see that happening in the MvvmCross source code here

So the way you fix this issue is in your ApplicationDelegate class, override GetAppStartHint and implement it like this:

protected override object GetAppStartHint(object hint = null)
{
    return hint;
}

This will now return the parameter passed to the method as opposed to null. I am not sure why MvvmCross is over-writing the parameter to null here instead of just passing through the hint parameter.