From 56d94d64b9aa6f16200be39b3bcb17f03325b7f9 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 25 Jun 2025 08:38:20 +0000 Subject: [PATCH] Add IActivityApplicationLifetime for android (#18893) * add lifetime with view factory on android * rename android lifetime interface * add warning when using ISingleViewApplicationLifetime for android, set current main activity reference on OnResume * fix nit --- samples/ControlCatalog/App.xaml.cs | 8 ++++ samples/SafeAreaDemo/App.xaml.cs | 4 ++ samples/SingleProjectSandbox/App.axaml.cs | 4 ++ .../Avalonia.Android/ApplicationLifetime.cs | 28 +++++++++++ .../AvaloniaAndroidApplication.cs | 6 +-- .../Avalonia.Android/AvaloniaMainActivity.cs | 23 ++++++++-- .../Avalonia.Android/SingleViewLifetime.cs | 46 ------------------- .../IActivityApplicationLifetime.cs | 11 +++++ 8 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 src/Android/Avalonia.Android/ApplicationLifetime.cs delete mode 100644 src/Android/Avalonia.Android/SingleViewLifetime.cs create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/IActivityApplicationLifetime.cs diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index a29a23ff61..b08df1223d 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -44,6 +44,10 @@ namespace ControlCatalog { desktopLifetime.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() }; } + else if(ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) + { + singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView { DataContext = new MainWindowViewModel() }; + } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) { singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() }; @@ -97,6 +101,10 @@ namespace ControlCatalog newWindow.Show(); oldWindow?.Close(); } + else if (app.ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) + { + singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView { DataContext = new MainWindowViewModel() }; + } else if (app.ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) { singleViewLifetime.MainView = new MainView(); diff --git a/samples/SafeAreaDemo/App.xaml.cs b/samples/SafeAreaDemo/App.xaml.cs index 536c850026..bae74ebd8b 100644 --- a/samples/SafeAreaDemo/App.xaml.cs +++ b/samples/SafeAreaDemo/App.xaml.cs @@ -19,6 +19,10 @@ namespace SafeAreaDemo { desktop.MainWindow = new MainWindow(); } + else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) + { + singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView(); + } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { singleViewPlatform.MainView = new MainView(); diff --git a/samples/SingleProjectSandbox/App.axaml.cs b/samples/SingleProjectSandbox/App.axaml.cs index 0c26e3ab0b..17538e83cd 100644 --- a/samples/SingleProjectSandbox/App.axaml.cs +++ b/samples/SingleProjectSandbox/App.axaml.cs @@ -21,6 +21,10 @@ public class App : Application { desktopLifetime.MainWindow = new MainWindow(); } + else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) + { + singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView(); + } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) { singleViewLifetime.MainView = new MainView(); diff --git a/src/Android/Avalonia.Android/ApplicationLifetime.cs b/src/Android/Avalonia.Android/ApplicationLifetime.cs new file mode 100644 index 0000000000..b2afea06f0 --- /dev/null +++ b/src/Android/Avalonia.Android/ApplicationLifetime.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Logging; + +namespace Avalonia.Android; + +internal class ApplicationLifetime : IActivityApplicationLifetime, ISingleViewApplicationLifetime +{ + private Control? _mainView; + + public Func? MainViewFactory { get; set; } + + public Control? MainView + { + get => _mainView; set + { + _mainView = value; + + Logger.TryGet(LogEventLevel.Warning, LogArea.AndroidPlatform)?.Log(this, "ISingleViewApplicationLifetime.MainView is not fully supported on Android." + + " Consider setting IActivityApplicationLifetime.MainViewFactory."); + if (_mainView != null) + MainViewFactory = () => _mainView; + else + MainViewFactory = null; + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs b/src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs index 5f59e63dcd..2b36e328f0 100644 --- a/src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs +++ b/src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs @@ -9,13 +9,13 @@ namespace Avalonia.Android { internal interface IAndroidApplication { - SingleViewLifetime? Lifetime { get; set; } + ApplicationLifetime? Lifetime { get; set; } } public class AvaloniaAndroidApplication : global::Android.App.Application, IAndroidApplication where TApp : Application, new() { - SingleViewLifetime? IAndroidApplication.Lifetime { get; set; } + ApplicationLifetime? IAndroidApplication.Lifetime { get; set; } protected AvaloniaAndroidApplication(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { @@ -32,7 +32,7 @@ namespace Avalonia.Android var builder = CreateAppBuilder(); builder = CustomizeAppBuilder(builder); - var lifetime = new SingleViewLifetime(); + var lifetime = new ApplicationLifetime(); ((IAndroidApplication)this).Lifetime = lifetime; diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index 975b4b7883..46514cd05f 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -12,14 +12,20 @@ public class AvaloniaMainActivity : AvaloniaActivity { if (Application is IAndroidApplication application && application.Lifetime is { } lifetime) { - initialContent ??= lifetime.MainView; + initialContent ??= lifetime.MainViewFactory?.Invoke(); - _view = new AvaloniaView(this) { Content = initialContent }; - lifetime.Activity = this; + _view = new AvaloniaView(this); + + Content = initialContent; } if (_view is null) throw new InvalidOperationException("Unknown error: AvaloniaView initialization has failed."); + } + + protected override void OnResume() + { + base.OnResume(); if (Avalonia.Application.Current?.TryGetFeature() is AndroidActivatableLifetime activatableLifetime) @@ -28,6 +34,17 @@ public class AvaloniaMainActivity : AvaloniaActivity } } + protected override void OnDestroy() + { + base.OnDestroy(); + + if (Avalonia.Application.Current?.TryGetFeature() + is AndroidActivatableLifetime activatableLifetime && activatableLifetime.CurrentMainActivity == this) + { + activatableLifetime.CurrentMainActivity = null; + } + } + protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure().UseAndroid(); protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder; } diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs deleted file mode 100644 index b5dbf38c64..0000000000 --- a/src/Android/Avalonia.Android/SingleViewLifetime.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; - -namespace Avalonia.Android; - -internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime -{ - private Control? _mainView; - private AvaloniaMainActivity? _activity; - - /// - /// Since Main Activity can be swapped, we should adjust lifetime as well. - /// - public AvaloniaMainActivity Activity - { - [return: MaybeNull] get => _activity!; - internal set - { - if (_activity != null) - { - _activity.Content = null; - } - _activity = value; - _activity.Content = _mainView; - } - } - - public Control? MainView - { - get => _mainView; - set - { - if (_mainView != value) - { - _mainView = value; - if (_activity != null) - { - _activity.Content = _mainView; - } - } - } - } - - public TopLevel? TopLevel => _activity?._view?.TopLevel; -} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IActivityApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IActivityApplicationLifetime.cs new file mode 100644 index 0000000000..a4b976207d --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IActivityApplicationLifetime.cs @@ -0,0 +1,11 @@ +using System; +using Avalonia.Metadata; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + [NotClientImplementable] + public interface IActivityApplicationLifetime : IApplicationLifetime + { + Func? MainViewFactory { get; set; } + } +}