diff --git a/MvvmCross.Android.Support/V7.AppCompat/MvxSplashScreenAppCompatActivity.cs b/MvvmCross.Android.Support/V7.AppCompat/MvxSplashScreenAppCompatActivity.cs index c6d6ac44ec..51be9638c5 100644 --- a/MvvmCross.Android.Support/V7.AppCompat/MvxSplashScreenAppCompatActivity.cs +++ b/MvvmCross.Android.Support/V7.AppCompat/MvxSplashScreenAppCompatActivity.cs @@ -7,7 +7,6 @@ using Android.Views; using MvvmCross.Core; using MvvmCross.Platforms.Android.Core; -using MvvmCross.Platforms.Android.Views; using MvvmCross.ViewModels; namespace MvvmCross.Droid.Support.V7.AppCompat @@ -86,5 +85,10 @@ protected virtual void TriggerFirstNavigate() if (!startup.IsStarted) startup.Start(); } + + // do nothing on this override, as initial navigation is managed by TriggerFirstNavigate + protected override void RunAppStart(Bundle bundle) + { + } } } diff --git a/MvvmCross/Core/MvxSetupSingleton.cs b/MvvmCross/Core/MvxSetupSingleton.cs index 64b1aa7d0b..539dae6e28 100644 --- a/MvvmCross/Core/MvxSetupSingleton.cs +++ b/MvvmCross/Core/MvxSetupSingleton.cs @@ -14,20 +14,15 @@ public abstract class MvxSetupSingleton : MvxSingleton { private static readonly object LockObject = new object(); + private static readonly object CompletionLockObject = new object(); private static TaskCompletionSource IsInitialisedTaskCompletionSource; private IMvxSetup _setup; - private bool _initialized; private IMvxSetupMonitor _currentMonitor; - protected virtual IMvxSetup Setup - { - get - { - return _setup; - } - } + protected virtual IMvxSetup Setup => _setup; - public virtual TMvxSetup PlatformSetup() where TMvxSetup : IMvxSetup + public virtual TMvxSetup PlatformSetup() + where TMvxSetup : IMvxSetup { try { @@ -59,59 +54,66 @@ protected static TMvxSetupSingleton EnsureSingletonAvailable public virtual void EnsureInitialized() { - lock (LockObject) - { - if (_initialized) - return; + // This method is safe to call multiple times - will only run init once + StartSetupInitialization(); - if (IsInitialisedTaskCompletionSource == null) - { - IsInitialisedTaskCompletionSource = StartSetupInitialization(); - } - else - { - MvxLog.Instance.Trace("EnsureInitialized has already been called so now waiting for completion"); - } - } + // Return immediately if the task is completed + if (IsInitialisedTaskCompletionSource.Task.IsCompleted) + return; + // Block waiting on Initialize to be completed IsInitialisedTaskCompletionSource.Task.GetAwaiter().GetResult(); } public virtual void InitializeAndMonitor(IMvxSetupMonitor setupMonitor) { - lock (LockObject) + // Grab the lock for the setupMonitor to make sure + // InitializationComplete isn't attempted whilst we're + // checking to see if Initialize has completed + lock (CompletionLockObject) { - _currentMonitor = setupMonitor; - if (_initialized) + var hasCompleted = false; + // Grab the lock for setting the completion state after Initialize is complete + lock (LockObject) { - _currentMonitor?.InitializationComplete(); - return; + hasCompleted = IsInitialisedTaskCompletionSource?.Task.IsCompleted ?? false; + // At this point if the completion state isn't completed, we know + // that we can set the _currentMonitor, and that InitializationComplete will get invoked + if (!hasCompleted) + { + _currentMonitor = setupMonitor; + } } - if (IsInitialisedTaskCompletionSource != null) + // At this point, if completion state is completed, we should + // NOT set the _currentMonitor (as there is a risk InitializationComplete + // has already been invoked). Instead we should just call InitializationComplete + // directly, and return. + if (hasCompleted) { + setupMonitor.InitializationComplete(); return; } - - IsInitialisedTaskCompletionSource = StartSetupInitialization(); } + + // If we get here, we should attempt to start Initialize because all we + // know is that Initialize hasn't completed. StartSetupInitialization is + // clever enough to know if it needs to run Initialize, or if it's already running + StartSetupInitialization(); } public virtual void CancelMonitor(IMvxSetupMonitor setupMonitor) { - lock (LockObject) + lock (CompletionLockObject) { - if (setupMonitor == _currentMonitor) + if (setupMonitor != _currentMonitor) { - _currentMonitor = null; + throw new MvxException("The specified IMvxSetupMonitor is not the one registered in MvxSetupSingleton"); } + _currentMonitor = null; } } - protected MvxSetupSingleton() - { - } - protected virtual void CreateSetup() { try @@ -128,7 +130,7 @@ protected override void Dispose(bool isDisposing) { if (isDisposing) { - lock (LockObject) + lock (CompletionLockObject) { _currentMonitor = null; } @@ -136,29 +138,38 @@ protected override void Dispose(bool isDisposing) base.Dispose(isDisposing); } - private TaskCompletionSource StartSetupInitialization() + private void StartSetupInitialization() { - var completionSource = new TaskCompletionSource(); + // Do double-test to detect if Initialize has started + if (IsInitialisedTaskCompletionSource != null) return; + lock (LockObject) + { + if (IsInitialisedTaskCompletionSource != null) return; + + // At this point we know Initialize hasn't started, so create + // the completion source and kick of Initialize + IsInitialisedTaskCompletionSource = new TaskCompletionSource(); + } + + // InitializePrimary should be only init methods that needs to be done on the UI thread _setup.InitializePrimary(); Task.Run(() => { + // InitializeSecondary should be the bulk of init methods (and done on non-UI thread) _setup.InitializeSecondary(); lock (LockObject) { - completionSource.SetResult(true); - _initialized = true; - var dispatcher = Mvx.GetSingleton(); - dispatcher.RequestMainThreadAction(() => - { - if (_currentMonitor != null) - { - _currentMonitor?.InitializationComplete(); - } - }); + IsInitialisedTaskCompletionSource.SetResult(true); } + var dispatcher = Mvx.GetSingleton(); + dispatcher.RequestMainThreadAction(() => + { + lock (CompletionLockObject) + { + _currentMonitor?.InitializationComplete(); + } + }); }); - - return completionSource; } } } diff --git a/MvvmCross/Platforms/Android/Views/MvxSplashScreenActivity.cs b/MvvmCross/Platforms/Android/Views/MvxSplashScreenActivity.cs index 95ed4a891e..65c1f18319 100644 --- a/MvvmCross/Platforms/Android/Views/MvxSplashScreenActivity.cs +++ b/MvvmCross/Platforms/Android/Views/MvxSplashScreenActivity.cs @@ -85,5 +85,10 @@ protected virtual void TriggerFirstNavigate() if (!startup.IsStarted) startup.Start(); } + + // do nothing on this override, as initial navigation is managed by TriggerFirstNavigate + protected override void RunAppStart(Bundle bundle) + { + } } } diff --git a/MvvmCross/Platforms/Ios/Core/MvxApplicationDelegate.cs b/MvvmCross/Platforms/Ios/Core/MvxApplicationDelegate.cs index e5c3d52ce1..bad49534f8 100644 --- a/MvvmCross/Platforms/Ios/Core/MvxApplicationDelegate.cs +++ b/MvvmCross/Platforms/Ios/Core/MvxApplicationDelegate.cs @@ -15,7 +15,7 @@ public abstract class MvxApplicationDelegate : UIApplicationDelegate, IMvxApplic /// /// UIApplicationDelegate.Window doesn't really exist / work. It was added by Xamarin.iOS templates /// - public new UIWindow Window { get; set; } + public new virtual UIWindow Window { get; set; } public MvxApplicationDelegate() : base() { diff --git a/MvvmCross/Platforms/Tvos/Core/MvxApplicationDelegate.cs b/MvvmCross/Platforms/Tvos/Core/MvxApplicationDelegate.cs index ea45eb87c5..6bac51a92c 100644 --- a/MvvmCross/Platforms/Tvos/Core/MvxApplicationDelegate.cs +++ b/MvvmCross/Platforms/Tvos/Core/MvxApplicationDelegate.cs @@ -15,7 +15,7 @@ public abstract class MvxApplicationDelegate : UIApplicationDelegate, IMvxApplic /// /// UIApplicationDelegate.Window doesn't really exist / work. It was added by Xamarin.iOS templates /// - public new UIWindow Window { get; set; } + public new virtual UIWindow Window { get; set; } public MvxApplicationDelegate() : base() { diff --git a/Projects/Playground/Playground.Droid/MainApplication.cs b/Projects/Playground/Playground.Droid/MainApplication.cs index b115369641..03cf7fab34 100644 --- a/Projects/Playground/Playground.Droid/MainApplication.cs +++ b/Projects/Playground/Playground.Droid/MainApplication.cs @@ -6,7 +6,6 @@ using Android.App; using Android.Runtime; using MvvmCross.Droid.Support.V7.AppCompat; -using MvvmCross.Platforms.Android.Views; using Playground.Core; namespace Playground.Droid diff --git a/Projects/Playground/Playground.Droid/Playground.Droid.csproj b/Projects/Playground/Playground.Droid/Playground.Droid.csproj index a9ff6bfe8b..7132503331 100644 --- a/Projects/Playground/Playground.Droid/Playground.Droid.csproj +++ b/Projects/Playground/Playground.Droid/Playground.Droid.csproj @@ -114,11 +114,11 @@ Designer - - - - - + + + + + diff --git a/Projects/Playground/Playground.Droid/Resources/layout/SplashScreen.axml b/Projects/Playground/Playground.Droid/Resources/layout/SplashScreen.axml index 60edacd234..23a17cc0f3 100644 --- a/Projects/Playground/Playground.Droid/Resources/layout/SplashScreen.axml +++ b/Projects/Playground/Playground.Droid/Resources/layout/SplashScreen.axml @@ -2,7 +2,8 @@ + android:layout_height="match_parent" + android:background="@color/colorAccent">