Browse Source

Move android app initialization to custom application class (#18756)

* move android app initialization to custom application class

* fix tests

* update api suppression

* address comments
pull/18869/head
Emmanuel Hansen 9 months ago
committed by GitHub
parent
commit
306f37d518
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      api/Avalonia.Android.nupkg.xml
  2. 30
      samples/ControlCatalog.Android/Application.cs
  3. 15
      samples/ControlCatalog.Android/MainActivity.cs
  4. 2
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  5. 14
      samples/SafeAreaDemo.Android/Application.cs
  6. 2
      samples/SafeAreaDemo.Android/MainActivity.cs
  7. 2
      samples/SafeAreaDemo.Android/Properties/AndroidManifest.xml
  8. 2
      samples/SingleProjectSandbox/Platforms/Android/AndroidManifest.xml
  9. 14
      samples/SingleProjectSandbox/Platforms/Android/Application.cs
  10. 7
      samples/SingleProjectSandbox/Platforms/Android/MainActivity.cs
  11. 45
      src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs
  12. 7
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  13. 33
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  14. 14
      tests/BuildTests/BuildTests.Android/Application.cs
  15. 2
      tests/BuildTests/BuildTests.Android/MainActivity.cs
  16. 3
      tests/BuildTests/BuildTests.Android/Properties/AndroidManifest.xml

6
api/Avalonia.Android.nupkg.xml

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Android.AvaloniaMainActivity`1</Target>
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left>
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Android.AndroidViewControlHandle.get_HandleDescriptor</Target>

30
samples/ControlCatalog.Android/Application.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Runtime;
using Avalonia;
using Avalonia.Android;
namespace ControlCatalog.Android
{
[Application]
public class Application : AvaloniaAndroidApplication<App>
{
protected Application(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
}
}

15
samples/ControlCatalog.Android/MainActivity.cs

@ -1,7 +1,6 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
using Avalonia;
using Avalonia.Android;
using static Android.Content.Intent;
@ -13,17 +12,9 @@ namespace ControlCatalog.Android
{
[Activity(Name = "com.Avalonia.ControlCatalog.MainActivity", Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, Exported = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
// CategoryLeanbackLauncher is required for Android TV.
[IntentFilter(new [] { ActionView }, Categories = new [] { CategoryDefault, CategoryLeanbackLauncher })]
public class MainActivity : AvaloniaMainActivity<App>
[IntentFilter(new[] { ActionView }, Categories = new[] { CategoryDefault, CategoryLeanbackLauncher })]
public class MainActivity : AvaloniaMainActivity
{
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
}
/// <summary>
@ -31,7 +22,7 @@ namespace ControlCatalog.Android
/// `AvaloniaActivity` internally will redirect parameters to the Avalonia Application.
/// </summary>
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true, Theme = "@android:style/Theme.NoDisplay")]
[IntentFilter(new[] {ActionView}, Categories = new[] {CategoryDefault, CategoryBrowsable}, DataScheme = "avln")]
[IntentFilter(new[] { ActionView }, Categories = new[] { CategoryDefault, CategoryBrowsable }, DataScheme = "avln")]
public class DataSchemeActivity : AvaloniaActivity
{
protected override void OnCreate(Bundle? savedInstanceState)

2
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
<application android:name=".Application" android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE " />

14
samples/SafeAreaDemo.Android/Application.cs

@ -0,0 +1,14 @@
using Android.App;
using Android.Runtime;
using Avalonia.Android;
namespace SafeAreaDemo.Android
{
[Application]
public class Application : AvaloniaAndroidApplication<App>
{
protected Application(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
}
}

2
samples/SafeAreaDemo.Android/MainActivity.cs

@ -5,7 +5,7 @@ using Avalonia.Android;
namespace SafeAreaDemo.Android
{
[Activity(Label = "SafeAreaDemo.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>
public class MainActivity : AvaloniaMainActivity
{
}
}

2
samples/SafeAreaDemo.Android/Properties/AndroidManifest.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="SafeAreaDemo" android:icon="@drawable/Icon" />
<application android:name=".Application" android:label="SafeAreaDemo" android:icon="@drawable/Icon" />
</manifest>

2
samples/SingleProjectSandbox/Platforms/Android/AndroidManifest.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<application android:label="SingleProjectSandbox.Android"></application>
<application android:name=".Application" android:label="SingleProjectSandbox.Android"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

14
samples/SingleProjectSandbox/Platforms/Android/Application.cs

@ -0,0 +1,14 @@
using Android.App;
using Android.Runtime;
using Avalonia.Android;
namespace SingleProjectSandbox.Android
{
[Application]
public class Application : AvaloniaAndroidApplication<App>
{
protected Application(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
}
}

7
samples/SingleProjectSandbox/Platforms/Android/MainActivity.cs

@ -1,15 +1,10 @@
using Android.App;
using Android.Content.PM;
using Avalonia;
using Avalonia.Android;
namespace SingleProjectSandbox;
[Activity(Label = "SingleProjectSandbox.Android", Theme = "@style/Theme.AppCompat.NoActionBar", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaMainActivity<App>
{
protected override AppBuilder CreateAppBuilder()
public class MainActivity : AvaloniaMainActivity
{
return App.BuildAvaloniaApp().UseAndroid();
}
}

45
src/Android/Avalonia.Android/AvaloniaAndroidApplication.cs

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.Runtime;
namespace Avalonia.Android
{
internal interface IAndroidApplication
{
SingleViewLifetime? Lifetime { get; set; }
}
public class AvaloniaAndroidApplication<TApp> : global::Android.App.Application, IAndroidApplication
where TApp : Application, new()
{
SingleViewLifetime? IAndroidApplication.Lifetime { get; set; }
protected AvaloniaAndroidApplication(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
InitializeAppLifetime();
}
private void InitializeAppLifetime()
{
var builder = CreateAppBuilder();
builder = CustomizeAppBuilder(builder);
var lifetime = new SingleViewLifetime();
((IAndroidApplication)this).Lifetime = lifetime;
builder.SetupWithLifetime(lifetime);
}
protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure<TApp>().UseAndroid();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
}
}

7
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -1,7 +0,0 @@
namespace Avalonia.Android;
public class AvaloniaMainActivity<TApp> : AvaloniaMainActivity
where TApp : Application, new()
{
protected override AppBuilder CreateAppBuilder() => AppBuilder.Configure<TApp>().UseAndroid();
}

33
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -8,41 +8,18 @@ namespace Avalonia.Android;
public class AvaloniaMainActivity : AvaloniaActivity
{
private protected static SingleViewLifetime? Lifetime;
private protected override void InitializeAvaloniaView(object? initialContent)
{
// Android can run OnCreate + InitializeAvaloniaView multiple times per process lifetime.
// On each call we need to create new AvaloniaView, but we can't recreate Avalonia nor Avalonia controls.
// So, if lifetime was already created previously - recreate AvaloniaView.
// If not, initialize Avalonia, and create AvaloniaView inside of AfterSetup callback.
// We need this AfterSetup callback to match iOS/Browser behavior and ensure that view/toplevel is available in custom AfterSetup calls.
if (Lifetime is not null)
if (Application is IAndroidApplication application && application.Lifetime is { } lifetime)
{
initialContent ??= Lifetime.MainView;
initialContent ??= lifetime.MainView;
_view = new AvaloniaView(this) { Content = initialContent };
Lifetime.Activity = this;
lifetime.Activity = this;
}
else
{
var builder = CreateAppBuilder();
builder = CustomizeAppBuilder(builder);
Lifetime = new SingleViewLifetime();
Lifetime.Activity = this;
builder
.AfterApplicationSetup(_ =>
{
_view = new AvaloniaView(this) { Content = initialContent };
})
.SetupWithLifetime(Lifetime);
// AfterPlatformServicesSetup should always be called. If it wasn't, we have an unusual problem.
if (_view is null)
throw new InvalidOperationException("Unknown error: AvaloniaView initialization has failed.");
}
if (_view is null)
throw new InvalidOperationException("Unknown error: AvaloniaView initialization has failed.");
if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
is AndroidActivatableLifetime activatableLifetime)

14
tests/BuildTests/BuildTests.Android/Application.cs

@ -0,0 +1,14 @@
using Android.App;
using Android.Runtime;
using Avalonia.Android;
namespace BuildTests.Android
{
[Application]
public class Application : AvaloniaAndroidApplication<App>
{
protected Application(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
}
}

2
tests/BuildTests/BuildTests.Android/MainActivity.cs

@ -8,4 +8,4 @@ namespace BuildTests.Android;
Label = "BuildTests.Android",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>;
public class MainActivity : AvaloniaMainActivity;

3
tests/BuildTests/BuildTests.Android/Properties/AndroidManifest.xml

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="BuildTests" />
<application android:name=".Application"
android:label="BuildTests" />
</manifest>

Loading…
Cancel
Save