MvvmCross / MvvmCross-Forms

Support for Xamarin.Forms on MvvmCross: The .NET MVVM framework for cross-platform solutions, including Xamarin.iOS, Xamarin.Android, Windows and Mac.
http://mvvmcross.com
6 stars 2 forks source link

iOS Start/Sleep/Resume not being called #41

Open NightOwlCoder opened 8 years ago

NightOwlCoder commented 8 years ago

Hey there,

I've code like this on MvxIosSetup:

        protected override IMvxIosViewPresenter CreatePresenter()
        {
            Forms.Init();

            var mvxFormsApp = new MvxFormsApp();

            mvxFormsApp.Start += (s, e) => { StartMessage.Publish(); };
            mvxFormsApp.Sleep += (s, e) => { SleepMessage.Publish(); };
            mvxFormsApp.Resume += (s, e) => { ResumeMessage.Publish(); };

            return new MvxFormsIosPagePresenter(Window, mvxFormsApp);
        }

But those methods are never called.

Is this a known issue or am I doing something wrong?

NightOwlCoder commented 8 years ago

I get the same situation when running UWP on a WP emulator.

Droid is the only one that is actually working fine.

I've also asked this question on SO: http://stackoverflow.com/questions/35142319/xamarin-forms-start-sleep-resume-not-being-called-when-using-mvvmcross-forms

Cheesebaron commented 8 years ago

https://github.com/MvvmCross/MvvmCross-Forms/blob/master/MvvmCross.Forms.Presenter.Core/MvxFormsApp.cs#L14 do they get called in a normal Forms app?

codeknox commented 8 years ago

Brand new solution on VS does the expected:

using Xamarin.Forms;
using System.Diagnostics;
namespace XS.Forms
{
    public class App : Application
    {
        public App()
        {
            MainPage = new ContentPage
            {
                Content = new StackLayout
                {
                    VerticalOptions = LayoutOptions.Center,
                    Children = { new Label { XAlign = TextAlignment.Center, Text = "Welcome to Xamarin Forms!" } }
                }
            };
        }

        protected override void OnStart()
        {
            Debug.WriteLine("start");
        }

        protected override void OnSleep()
        {
            Debug.WriteLine("sleep");
        }

        protected override void OnResume()
        {
            Debug.WriteLine("resume");
        }
    }
}

Console output for iOS run:

Launching 'XSFormsiOS' on 'iPhone 6s Plus iOS 9.2'...
Thread started:  #2
Thread started:  #3
2016-02-02 09:45:17.895 XSFormsiOS[82830:5257324] start
2016-02-02 09:45:32.227 XSFormsiOS[82830:5257324] sleep[0:] sleep
2016-02-02 09:45:37.095 XSFormsiOS[82830:5257324] resume[0:] resume
2016-02-02 09:45:39.410 XSFormsiOS[82830:5257324] sleep[0:] sleep
2016-02-02 09:45:40.799 XSFormsiOS[82830:5257324] resume
The app has been terminated.

and Console output for Droid run:

Android application is debugging.
InspectorDebugSession(0): HandleTargetEvent: TargetReady
Thread started:  #3
02-02 09:39:51.988 I/mono-stdout( 2015): start
02-02 09:39:52.183 I/mono-stdout( 2015): sleep
02-02 09:40:16.844 I/mono-stdout( 2015): resume
02-02 09:40:24.163 I/mono-stdout( 2015): sleep
02-02 09:40:35.610 I/mono-stdout( 2015): resume
InspectorDebugSession(0): HandleTargetEvent: ThreadStopped
Thread started: <Thread Pool> #7
InspectorDebugSession(0): HandleTargetEvent: ThreadStarted
InspectorDebugSession(0): Disposed

I switched the app a few times to BG and back.

Cheesebaron commented 8 years ago

Are you sure that your mvxFormsApp isn't GC'ed? Try creating it as a member variable.

codeknox commented 8 years ago

As it goes as a parameter to the presenter and it saves it, I did not think it was necessary.

new MvxFormsIosPagePresenter(Window, mvxFormsApp);

Anyway, I created a local fiend but unfortunately, got the same end result.

codeknox commented 8 years ago

So I got the source code for the presenter, and added the Example002XAML PCL/iOS into it, removed packages from it and pointed to src code on the solution. Created a default ctor on MvxFormsApp and put a BP there and on the 3 overrides.

Unfortunately, only the ctor one is hit, the overrides never came.

But as I already tested a pure Form app and they are calling this, looks like we are doing something out of order?

Any other idea that can help me move further in the investigation?

codeknox commented 8 years ago

Something else I just found, there is no LoadApplication in iOS code. Isn't it necessary like on the other platforms?

codeknox commented 8 years ago

Probably exactly what is missing?

        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();

            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }

Above code is from a pure Xamarin Forms iOS project. But unfortunately LoadApplication is only available if we inherit from Xamarin.Forms.Platform.iOS.FormsApplicationDelegate.

Looks like we need some refator on the base class here?

helmerm commented 8 years ago

Is there any update on this one? Is there a workaround?

This probably also causes the Start/ReloadState/SaveState to be not called on iOS.

xabre commented 8 years ago

I solved this in my current project by doing the following:

public class MyApplicationDelegate : FormsApplicationDelegate, IMvxApplicationDelegate
      {
        public override void WillEnterForeground(UIApplication application)
        {
            FireLifetimeChanged(MvxLifetimeEvent.ActivatedFromMemory);
        }

        public override void DidEnterBackground(UIApplication application)
        {
            FireLifetimeChanged(MvxLifetimeEvent.Deactivated);
        }

        public override void WillTerminate(UIApplication application)
        {
            FireLifetimeChanged(MvxLifetimeEvent.Closing);
        }

        public override void FinishedLaunching(UIApplication application)
        {
            FireLifetimeChanged(MvxLifetimeEvent.Launching);
        }

        private void FireLifetimeChanged(MvxLifetimeEvent which)
        {
            var handler = LifetimeChanged;
            if (handler != null)
                handler(this, new MvxLifetimeEventArgs(which));
        }

        #region IMvxLifetime implementation

        public event EventHandler<MvxLifetimeEventArgs> LifetimeChanged;

        #endregion
    }
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            Forms.Init();
            Forms.ViewInitialized += (sender, e) =>
            {
                if (!string.IsNullOrWhiteSpace(e.View.StyleId))
                {
                    e.NativeView.AccessibilityIdentifier = e.View.StyleId;
                }
            };
            var formsApp = new MyFormsApp();

            var setup = new Setup(this, formsApp);
            setup.Initialize();

            var startup = Mvx.Resolve<IMvxAppStart>();
            startup.Start();

            LoadApplication(formsApp);

            return base.FinishedLaunching(app, options);
        }

Basicly the solution consists in using LoadApplication and removing the explicit window.

helmerm commented 8 years ago

Thanks! How does your Setup and FormsApp class look like? Do I need to initialize the MainPage in the FormsApp class? What did you pass to the MvxIosSetup base class constructor?

xabre commented 8 years ago

My setup looks like this:

  public class Setup : MvxIosSetup
    {
        private readonly Xamarin.Forms.Application _app;

        public Setup(IMvxApplicationDelegate applicationDelegate, Xamarin.Forms.Application app)
            : base(applicationDelegate, window: null)
        {
            _app = app;
        }

        protected override IMvxApplication CreateApp()
        {
            return new MyMvxApp();
        }

        protected override IMvxTrace CreateDebugTrace()
        {
            return new DebugTrace();
        }

        protected override IMvxIosViewPresenter CreatePresenter()
        {
            var presenter = new MyIosFormsViewPresenter(_app);
            return presenter;
        }
}

MyFormsApp (inside the PCL) is just:

 public class MyFormsApp : MvxFormsApp
    {

        protected override void OnStart()
        {
            // Handle when your app starts
            Mvx.Trace("App start");
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
            Mvx.Trace("App sleep");
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
            Mvx.Trace("App resume");
       }
}

My custom presenter is something like (the window reference is not needed):

public class MyIosFormsViewPresenter : MvxFormsPagePresenter, IMvxIosViewPresenter
    {
        public MyIosFormsViewPresenter(Xamarin.Forms.Application mvxFormsApp)
            : base(mvxFormsApp)
        {

        }

        public bool PresentModalViewController(UIViewController controller, bool animated)
        {
            return false;
        }

        public void NativeModalViewControllerDisappearedOnItsOwn()
        {

        }
    }
helmerm commented 8 years ago

Thanks!

Do the application lifecycle events work correctly for you?

On android, the OnResume method on the FormsApplication does not get called, when bringing the app to foreground, but the OnStart method is called instead.

On iOS the OnResume method on the FormsApplication works, but the OnStart method is not invoked on the ViewModel on resume.

The SaveStateToBundle and ReloadFromBundle methods on my view model are never executed, neither on android nor iOS.

xabre commented 8 years ago

These application lifecyle methos have nothing to do with the view models direclty (or at least out of the box). They work fine for my app.

The Start method of the MvxViewModel is called after the view model is instantiated by the ViewPresenter.

I had to forward these events to my view model manually. For example, for 'App -> OnResume' I get the topmost view (Page), then I get the binding context and if the binding context is my BaseViewModel then I call OnResume on it.

I can't tell you more about SaveStateToBundle and ReloadFromBundle because I'm not using them in my forms app but I guess they also need to be triggered.

Moreover, I defined methods like OnSuspended, OnResumed, OnLoaded etc in my BaseViewModel and trigger them from a BasePage by overriding Page methods like OnAppearing, OnDissapearing, OnBindingContextChanged, OnBackButtonPressed. This way my ViewModels are aware of the state of their views.