diff --git a/.nuspec/Microsoft.Maui.Controls.MultiTargeting.targets b/.nuspec/Microsoft.Maui.Controls.MultiTargeting.targets index e111d6aebaf1..4645d6f2d739 100644 --- a/.nuspec/Microsoft.Maui.Controls.MultiTargeting.targets +++ b/.nuspec/Microsoft.Maui.Controls.MultiTargeting.targets @@ -32,10 +32,10 @@ - - - - + + + + diff --git a/src/Controls/samples/Controls.Sample/Controls.Sample.csproj b/src/Controls/samples/Controls.Sample/Controls.Sample.csproj index 583d58757b58..a64a797d9017 100644 --- a/src/Controls/samples/Controls.Sample/Controls.Sample.csproj +++ b/src/Controls/samples/Controls.Sample/Controls.Sample.csproj @@ -30,4 +30,6 @@ + + \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Services/Android/CustomAndroidLifecycleHandler.cs b/src/Controls/samples/Controls.Sample/Services/Android/CustomAndroidLifecycleHandler.cs new file mode 100644 index 000000000000..e0b1621489ab --- /dev/null +++ b/src/Controls/samples/Controls.Sample/Services/Android/CustomAndroidLifecycleHandler.cs @@ -0,0 +1,41 @@ +using System.Runtime.CompilerServices; +using Android.App; +using Android.Content; +using Android.Content.Res; +using Android.OS; +using Microsoft.Maui; + +namespace Maui.Controls.Sample.Services +{ + public class CustomAndroidLifecycleHandler : AndroidApplicationLifetime + { + public override void OnCreate(Activity activity, Bundle savedInstanceState) => LogMember(); + + public override void OnPostCreate(Activity activity, Bundle savedInstanceState) => LogMember(); + + public override void OnDestroy(Activity activity) => LogMember(); + + public override void OnPause(Activity activity) => LogMember(); + + public override void OnResume(Activity activity) => LogMember(); + + public override void OnPostResume(Activity activity) => LogMember(); + + public override void OnStart(Activity activity) => LogMember(); + + public override void OnStop(Activity activity) => LogMember(); + + public override void OnRestart(Activity activity) => LogMember(); + + public override void OnSaveInstanceState(Activity activity, Bundle outState) => LogMember(); + + public override void OnRestoreInstanceState(Activity activity, Bundle savedInstanceState) => LogMember(); + + public override void OnConfigurationChanged(Activity activity, Configuration newConfig) => LogMember(); + + public override void OnActivityResult(Activity activity, int requestCode, Result resultCode, Intent data) => LogMember(); + + static void LogMember([CallerMemberName] string name = "") => + System.Diagnostics.Debug.WriteLine("LIFECYCLE: " + name); + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Services/iOS/CustomIosLifecycleHandler.cs b/src/Controls/samples/Controls.Sample/Services/iOS/CustomIosLifecycleHandler.cs new file mode 100644 index 000000000000..ce7bd09d65d8 --- /dev/null +++ b/src/Controls/samples/Controls.Sample/Services/iOS/CustomIosLifecycleHandler.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Foundation; +using Microsoft.Maui; +using UIKit; + +namespace Maui.Controls.Sample +{ + public class CustomIosLifecycleHandler : IosApplicationLifetime + { + public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + { + LogMember(); + + return true; + } + + public override void OnActivated(UIApplication application) => LogMember(); + + public override void OnResignActivation(UIApplication application) => LogMember(); + + public override void WillTerminate(UIApplication application) => LogMember(); + + public override void DidEnterBackground(UIApplication application) => LogMember(); + + public override void WillEnterForeground(UIApplication application) => LogMember(); + + static void LogMember([CallerMemberName] string name = "") => + Debug.WriteLine("LIFECYCLE: " + name); + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Startup.cs b/src/Controls/samples/Controls.Sample/Startup.cs index 86789ef23bac..104df7e7da7e 100644 --- a/src/Controls/samples/Controls.Sample/Startup.cs +++ b/src/Controls/samples/Controls.Sample/Startup.cs @@ -43,8 +43,8 @@ public void Configure(IAppHostBuilder appBuilder) {"Logging:LogLevel:Default", "Warning"} }); }) - .UseMauiServiceProviderFactory(true) - //.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + //.UseMauiServiceProviderFactory(true) + .UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) .ConfigureServices((hostingContext, services) => { services.AddSingleton(); @@ -56,6 +56,12 @@ public void Configure(IAppHostBuilder appBuilder) services.AddTransient(); services.AddTransient(); + +#if __ANDROID__ + services.AddTransient(); +#elif __IOS__ + services.AddTransient(); +#endif }) .ConfigureFonts((hostingContext, fonts) => { @@ -63,7 +69,7 @@ public void Configure(IAppHostBuilder appBuilder) }); } - // To use DI ServiceCollection and not the MAUI one + // To use the Microsoft.Extensions.DependencyInjection ServiceCollection and not the MAUI one public class DIExtensionsServiceProviderFactory : IServiceProviderFactory { public ServiceCollection CreateBuilder(IServiceCollection services) diff --git a/src/Core/src/Platform/Android/AndroidApplicationLifetime.cs b/src/Core/src/Platform/Android/AndroidApplicationLifetime.cs new file mode 100644 index 000000000000..4061e64809db --- /dev/null +++ b/src/Core/src/Platform/Android/AndroidApplicationLifetime.cs @@ -0,0 +1,81 @@ +using Android.App; +using Android.Content; +using Android.Content.Res; +using Android.OS; +using Android.Runtime; + +namespace Microsoft.Maui +{ + public class AndroidApplicationLifetime : IAndroidApplicationLifetime + { + public virtual void OnCreate(Activity activity, Bundle? savedInstanceState) + { + + } + + public virtual void OnPostCreate(Activity activity, Bundle? savedInstanceState) + { + + } + + public virtual void OnDestroy(Activity activity) + { + + } + + public virtual void OnPause(Activity activity) + { + + } + + public virtual void OnResume(Activity activity) + { + + } + + public virtual void OnPostResume(Activity activity) + { + + } + + public virtual void OnStart(Activity activity) + { + + } + + public virtual void OnStop(Activity activity) + { + + } + + public virtual void OnRestart(Activity activity) + { + + } + + public virtual void OnSaveInstanceState(Activity activity, Bundle outState) + { + + } + + public virtual void OnRestoreInstanceState(Activity activity, Bundle savedInstanceState) + { + + } + + public virtual void OnConfigurationChanged(Activity activity, Configuration newConfig) + { + + } + + public virtual void OnActivityResult(Activity activity, int requestCode, [GeneratedEnum] Result resultCode, Intent? data) + { + + } + + public virtual void OnBackPressed(Activity activity) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/IAndroidApplicationLifetime.cs b/src/Core/src/Platform/Android/IAndroidApplicationLifetime.cs new file mode 100644 index 000000000000..56f282c00ad0 --- /dev/null +++ b/src/Core/src/Platform/Android/IAndroidApplicationLifetime.cs @@ -0,0 +1,111 @@ +using Android.App; +using Android.Content; +using Android.Content.Res; +using Android.OS; +using Android.Runtime; + +namespace Microsoft.Maui +{ + /// + /// Allow to get Android Activity lifecycle callbacks. + /// + public interface IAndroidApplicationLifetime : IPlatformApplicationLifetime + { + /// + /// Called when the activity is starting. + /// + /// The activity on which we receive lifecycle events callbacks. + /// Previous state saved. + void OnCreate(Activity activity, Bundle? savedInstanceState); + + /// + /// Called when activity start-up is complete (after OnStart() and OnRestoreInstanceState(Bundle) have been called). + /// + /// The activity on which we receive lifecycle events callbacks. + /// Previous state saved. + void OnPostCreate(Activity activity, Bundle? savedInstanceState); + + /// + /// Called when the activity had been stopped, but is now again being displayed to the user. + /// + /// The activity on which we receive lifecycle events callbacks. + void OnStart(Activity activity); + + /// + /// Called for your activity to start interacting with the user. + /// This is an indicator that the activity became active and ready to receive input. + /// + /// The activity on which we receive lifecycle events callbacks. + void OnResume(Activity activity); + + /// + /// Called when activity resume is complete (after OnResume() has been called). + /// + /// The activity on which we receive lifecycle events callbacks. + void OnPostResume(Activity activity); + + /// + /// Called as part of the activity lifecycle when the user no longer actively interacts with the activity, + /// but it is still visible on screen. + /// + /// The activity on which we receive lifecycle events callbacks. + void OnPause(Activity activity); + + /// + /// Called when you are no longer visible to the user. + /// + /// The activity on which we receive lifecycle events callbacks + void OnStop(Activity activity); + + /// + /// Called after OnStop when the current activity is being re-displayed to the user (the user has navigated back to it). + /// + /// The activity on which we receive lifecycle events callbacks + void OnRestart(Activity activity); + + /// + /// This can happen either because the activity is finishing, or because the system is + /// temporarily destroying this instance of the activity to save space. + /// + /// The activity on which we receive lifecycle events callbacks. + void OnDestroy(Activity activity); + + /// + /// Called to retrieve per-instance state from an activity before being killed so that the state can be + /// restored in OnCreate or OnRestoreInstanceState. + /// + /// The activity on which we receive lifecycle events callbacks. + /// Bundle in which to place your saved state. + void OnSaveInstanceState(Activity activity, Bundle outState); + + /// + /// Restore the state of the dialog from a previously saved bundle. + /// + /// The activity on which we receive lifecycle events callbacks. + /// The state of the dialog previously saved. + void OnRestoreInstanceState(Activity activity, Bundle savedInstanceState); + + /// + /// Called when the current configuration of the resources being used by the application have changed. + /// + /// The activity on which we receive lifecycle events callbacks. + /// The new resource configuration. + void OnConfigurationChanged(Activity activity, Configuration newConfig); + + /// + /// Called when an activity you launched exits, giving you the requestCode you started it with, + /// the resultCode it returned, and any additional data from it. + /// + /// The activity on which we receive lifecycle events callbacks. + /// The integer request code originally supplied to startActivityForResult(), allowing you to identify who this result came from. + /// The integer result code returned by the child activity. + /// An Intent, which can return result data to the caller. + void OnActivityResult(Activity activity, int requestCode, [GeneratedEnum] Result resultCode, Intent? data); + + /// + /// Called when the activity has detected the user's press of the back key. + /// + /// The activity on which we receive lifecycle events callbacks. + void OnBackPressed(Activity activity); + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs index 93fde4f05d5d..74859ccf1b0d 100644 --- a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs +++ b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs @@ -1,10 +1,16 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Android.Content; +using Android.Content.Res; using Android.OS; +using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; using AndroidX.AppCompat.Widget; using AndroidX.CoordinatorLayout.Widget; using Google.Android.Material.AppBar; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Maui { @@ -32,11 +38,102 @@ protected override void OnCreate(Bundle? savedInstanceState) CoordinatorLayout parent = new CoordinatorLayout(this); - SetContentView(parent, new ViewGroup.LayoutParams(CoordinatorLayout.LayoutParams.MatchParent, CoordinatorLayout.LayoutParams.MatchParent)); + SetContentView(parent, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)); //AddToolbar(parent); - parent.AddView(content.ToNative(window.MauiContext), new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MatchParent, CoordinatorLayout.LayoutParams.MatchParent)); + parent.AddView(content.ToNative(window.MauiContext), new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnCreate(this, savedInstanceState); + } + + protected override void OnPostCreate(Bundle? savedInstanceState) + { + base.OnPostCreate(savedInstanceState); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnPostCreate(this, savedInstanceState); + } + + protected override void OnStart() + { + base.OnStart(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnStart(this); + } + + protected override void OnPause() + { + base.OnPause(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnPause(this); + } + + protected override void OnResume() + { + base.OnResume(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnResume(this); + } + + protected override void OnPostResume() + { + base.OnPostResume(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnPostResume(this); + } + + protected override void OnRestart() + { + base.OnRestart(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnRestart(this); + } + + protected override void OnDestroy() + { + base.OnDestroy(); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnDestroy(this); + } + + protected override void OnSaveInstanceState(Bundle outState) + { + base.OnSaveInstanceState(outState); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnSaveInstanceState(this, outState); + } + + protected override void OnRestoreInstanceState(Bundle savedInstanceState) + { + base.OnRestoreInstanceState(savedInstanceState); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnRestoreInstanceState(this, savedInstanceState); + } + + public override void OnConfigurationChanged(Configuration newConfig) + { + base.OnConfigurationChanged(newConfig); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnConfigurationChanged(this, newConfig); + } + + protected override void OnActivityResult(int requestCode, [GeneratedEnum] Android.App.Result resultCode, Intent? data) + { + base.OnActivityResult(requestCode, resultCode, data); + + foreach (var androidApplicationLifetime in GetAndroidApplicationLifetime()) + androidApplicationLifetime.OnActivityResult(this, requestCode, resultCode, data); } void AddToolbar(ViewGroup parent) @@ -44,9 +141,12 @@ void AddToolbar(ViewGroup parent) Toolbar toolbar = new Toolbar(this); var appbarLayout = new AppBarLayout(this); - appbarLayout.AddView(toolbar, new ViewGroup.LayoutParams(AppBarLayout.LayoutParams.MatchParent, global::Android.Resource.Attribute.ActionBarSize)); + appbarLayout.AddView(toolbar, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, Android.Resource.Attribute.ActionBarSize)); SetSupportActionBar(toolbar); parent.AddView(appbarLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent)); } + + IEnumerable GetAndroidApplicationLifetime() => + MauiApplication.Current?.Services?.GetServices() ?? Enumerable.Empty(); } } diff --git a/src/Core/src/Platform/Android/MauiApplication.cs b/src/Core/src/Platform/Android/MauiApplication.cs index 2283a529f5f4..7f2a0ea540f2 100644 --- a/src/Core/src/Platform/Android/MauiApplication.cs +++ b/src/Core/src/Platform/Android/MauiApplication.cs @@ -32,6 +32,7 @@ public override void OnCreate() // Configure native services like HandlersContext, ImageSourceHandlers etc.. void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) { + services.AddTransient(); } } diff --git a/src/Core/src/Platform/IPlatformApplicationLifetime.cs b/src/Core/src/Platform/IPlatformApplicationLifetime.cs new file mode 100644 index 000000000000..e9b0ea58c748 --- /dev/null +++ b/src/Core/src/Platform/IPlatformApplicationLifetime.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Maui +{ + /// + /// Allow to get specific lifecycle events for each platform. + /// + public interface IPlatformApplicationLifetime + { + + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/IIosApplicationLifetime.cs b/src/Core/src/Platform/iOS/IIosApplicationLifetime.cs new file mode 100644 index 000000000000..2e04da555a25 --- /dev/null +++ b/src/Core/src/Platform/iOS/IIosApplicationLifetime.cs @@ -0,0 +1,49 @@ +using Foundation; +using UIKit; + +namespace Microsoft.Maui +{ + /// + /// Allow to get iOS UIApplication lifecycle callbacks. + /// + public interface IIosApplicationLifetime : IPlatformApplicationLifetime + { + /// + /// Method invoked after the application has launched to configure the main window and view controller. + /// + /// Reference to the UIApplication that invoked this delegate method. + /// An NSDictionary with the launch options, can be null. + /// Boolean + bool FinishedLaunching(UIApplication application, NSDictionary launchOptions); + + /// + /// Called when the application is launched and every time the app returns to the foreground. + /// + /// Reference to the UIApplication that invoked this delegate method. + void OnActivated(UIApplication application); + + /// + /// The app is about to move from the active state to the inactive state. + /// + /// Reference to the UIApplication that invoked this delegate method. + void OnResignActivation(UIApplication application); + + /// + /// Called if the application is being terminated due to memory constraints or directly by the user. + /// + /// Reference to the UIApplication that invoked this delegate method. + void WillTerminate(UIApplication application); + + /// + /// Indicates that the application has entered the background. + /// + /// Reference to the UIApplication that invoked this delegate method. + void DidEnterBackground(UIApplication application); + + /// + /// Called prior to the application returning from a backgrounded state. + /// + /// Reference to the UIApplication that invoked this delegate method. + void WillEnterForeground(UIApplication application); + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/IosApplicationLifetime.cs b/src/Core/src/Platform/iOS/IosApplicationLifetime.cs new file mode 100644 index 000000000000..5c3d3f669d61 --- /dev/null +++ b/src/Core/src/Platform/iOS/IosApplicationLifetime.cs @@ -0,0 +1,38 @@ +using Foundation; +using UIKit; + +namespace Microsoft.Maui +{ + public class IosApplicationLifetime : IIosApplicationLifetime + { + public virtual bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + { + return true; + } + + public virtual void OnActivated(UIApplication application) + { + + } + + public virtual void OnResignActivation(UIApplication application) + { + + } + + public virtual void WillTerminate(UIApplication application) + { + + } + + public virtual void DidEnterBackground(UIApplication application) + { + + } + + public virtual void WillEnterForeground(UIApplication application) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs b/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs index 1ed2e8308227..defedae7be15 100644 --- a/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs +++ b/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Foundation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -41,12 +43,49 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l Window.MakeKeyAndVisible(); + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.FinishedLaunching(application, launchOptions); + return true; } + public override void OnActivated(UIApplication application) + { + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.OnActivated(application); + } + + public override void OnResignActivation(UIApplication application) + { + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.OnResignActivation(application); + } + + public override void WillTerminate(UIApplication application) + { + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.WillTerminate(application); + } + + public override void DidEnterBackground(UIApplication application) + { + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.DidEnterBackground(application); + } + + public override void WillEnterForeground(UIApplication application) + { + foreach (var iOSApplicationDelegateHandler in GetIosApplicationLifetime()) + iOSApplicationDelegateHandler.WillEnterForeground(application); + } + void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) { + services.AddTransient(); } + + IEnumerable GetIosApplicationLifetime() => + Services?.GetServices() ?? Enumerable.Empty(); } public abstract class MauiUIApplicationDelegate : UIApplicationDelegate, IUIApplicationDelegate