Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F# support is broken in Xamarin.Android #12640

Closed
Dolfik1 opened this issue Jan 25, 2022 · 34 comments
Closed

F# support is broken in Xamarin.Android #12640

Dolfik1 opened this issue Jan 25, 2022 · 34 comments
Labels
Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.

Comments

@Dolfik1
Copy link

Dolfik1 commented Jan 25, 2022

Repro steps

Provide the steps required to reproduce the problem:

  1. Install Visual Studio or Visual Studio for Mac and enable both Xamarin and .NET Core support, these are listed as ‘Mobile development with .NET’ and ‘.NET Core Cross-platform development’ respectively.

  2. Open a command prompt window and install the template pack by entering:
    dotnet new -i Fabulous.XamarinForms.Templates

  3. Navigate to a folder in the command prompt window where your new app can be created and enter:
    dotnet new fabulous-xf-app -n SqueakyApp

  4. Build and run SqueakyApp.Android on device.

Expected behavior

The app build and run successfully

Actual behavior

/Users/nikolay/Dev/fsharp/SqueakyApp2/SqueakyApp2.Android/AssemblyInfo.fs(9,30): error FS0039: The type 'Android' is not defined.
/Users/nikolay/Dev/fsharp/SqueakyApp2/SqueakyApp2.Android/AssemblyInfo.fs(9,30): error FS0039: The type 'Android' is not defined.
/Users/nikolay/Dev/fsharp/SqueakyApp2/SqueakyApp2.Android/MainActivity.fs(20,63): error FS0039: The value, constructor, namespace or type 'Layout' is not defined.
/Users/nikolay/Dev/fsharp/SqueakyApp2/SqueakyApp2.Android/MainActivity.fs(21,61): error FS0039: The value, constructor, namespace or type 'Layout' is not defined.

These errors are due to Xamarin.Android.FSharp.ResourceProvider does not compile and does not load Resources library:
type Resources = SqueakyApp2.Android.Resource

Known workarounds

Use Paket instead of NuGet. Looks like Paket in a magical way works with this.
But Paket does not support MonoAndroid11 and MonoAndroid12 (link1, link2)

I also found out that there is no way to add back Xamarin.Android.FSharp.ResourceProvider 1.0.0.28 after remove:

Could not install package 'Xamarin.Android.FSharp.ResourceProvider 1.0.0.28'. You are trying to install this package into a project that targets 'MonoAndroid,Version=v10.0', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

I tried to install Xamarin.Android.FSharp.ResourceProvider 1.0.1 and it is installed successfully but this version does not work (link1, link2) and I getting the same error as above with warning:

 Referenced assembly '/Users/nikolay/.nuget/packages/xamarin.android.fsharp.resourceprovider/1.0.1/lib/monoandroid81/Xamarin.Android.FSharp.ResourceProvider.Runtime.dll' has assembly level attribute 'Microsoft.FSharp.Core.CompilerServices.TypeProviderAssemblyAttribute' but no public type provider classes were found

Related information

Provide any related information (optional):

  • macOS 12.1
  • Xamarin.Android Version: 12.0.0.3 (Visual Studio Community)
  • Visual Studio Community 2019 for Mac Version 8.10.16 (build 2)

cc @dsyme @vzarytovskii @nosami

@Dolfik1 Dolfik1 added the Bug label Jan 25, 2022
@Happypig375
Copy link
Member

Duplicate of #10837, xamarin/Xamarin.Android.FSharp.ResourceProvider#9, and dotnet/android#6404

@TimLariviere
Copy link

In general, the state of mobile development with F# is a real mess right now.

Just to name a few:

  • All the issues with Xamarin.Android.FSharp.ResourceProvider as mentioned here (1.0.1 never worked)
  • Existing F# Xamarin templates removed from Visual Studio 2022
  • Mono not supporting F# 6.0
  • No official way to use F# with net6.0-ios and net6.0-android (I don't even know if we can create Android project with F# on .NET 6.0)

Sorry for hijacking this issue and venting my frustration, but we could really use some help from Microsoft on the F# + Mobile front. :)

@Dolfik1
Copy link
Author

Dolfik1 commented Jan 25, 2022

* No official way to use F# with `net6.0-ios` and `net6.0-android` (I don't even know if we can create Android project with F# on .NET 6.0)

I have successfully ported our F# mobile app to net6.0-ios. It seems most of code works without any issues. I also successfully ran sample app on net6.0-android (but without using android resources).

@7sharp9
Copy link
Contributor

7sharp9 commented Jan 25, 2022

@TimLariviere These things tend to happen people move to different companies and departments and were never replaced, things enter maintenance mode and are forgotten etc. A lot of the F# integration used to be open source, but Im not sure if parts were made internal after the acquisition. I think the F# droid stuff was internal to be honest, now I think back to when I was there.

@7sharp9
Copy link
Contributor

7sharp9 commented Jan 25, 2022

The type provider could be replaces by Myriad too to make it easier to maintain in the future and open source.

@nosami
Copy link
Contributor

nosami commented Jan 25, 2022

Xamarin.Android.FSharp.ResourceProvider was originally written by myself because of issues with the F# and CodeDOM provider that came before it that couldn't easily be fixed (F# doesn't support public static fields and Xamarin.Android required these). The problem was that I was never supposed to work on that as I was always part of the VSMac team not Xamarin.Android.

I later moved to the Codespaces team which meant that I was even further removed from Xamarin.Android work. At the time that I moved, I handed over Xamarin.Android.FSharp.ResourceProvider for maintenance to the XA team but it seems like bitrot set in pretty quickly.

I've since returned to the VSMac team, but now that VSMac shares much of the same codebase as Visual Studio, there is no longer any need for dedicated F# staff on the Mac side of things so I mostly work on other areas such as the debugger but have spent a lot of time recently porting parts of the UI to Cocoa.

I could work on the type provider in my spare time, but I have very little of it. It is open source though so anyone could work on it if they wanted to.

I enquired about the state of play with MAUI and net6.0-android. The plan is to ditch the C# code generation completely (which the F# type provider uses in the background) and use IL generation instead which at least in theory should work for all dotnet languages. See comment by @dellis1972 This should make the type provider completely redundant.

@TimLariviere
Copy link

TimLariviere commented Jan 26, 2022

Thanks for the replies

I could work on the type provider in my spare time, but I have very little of it

I was not asking for you or anyone to work on their spare time, especially if it's not even your day job.

The message I wanted to convey was more like it would be really appreciated if Microsoft as a whole would consider .NET to not be only C#, and put on the roadmap some F# to get some basic support on everything labeled .NET.

Such an example is PackageReference in Visual Studio (Win) in old style fsproj.
VS Mac and Jetbrains Rider support it since a long time ago. VS Win doesn't which is a deal-breaker because most developers use this one.

More than 3 years ago, I tried to submit a PR to fix that but most of what was required was not open-source and the response I got was "Won't do it. Wait for Xamarin to move to sdk-style".
Now we're 2022, C# seems to have got it. F#, still very bumpy.

Sure, we could stay with packages.config and wait.
But since C# has moved away from that years ago, all IDE tooling and Xamarin teams are slowly removing support for it.
"Just use PackageReference." Well, we can't :(

It's that kind of experience that makes it so frustrating.

It is open source though so anyone could work on it if they wanted to.

I'll try to fix my main issues. Just hoping getting reviewed, merged and released won't take months since ownership is from teams at Microsoft...

@nosami Could I ping you if I happen to be stuck on non open-source stuff, so you could try to relay the message internally to the right people?

I have successfully ported our F# mobile app to net6.0-ios. It seems most of code works without any issues. I also successfully ran sample app on net6.0-android (but without using android resources).

@Dolfik1 Would you happen to have an empty net6.0-android with F#?
I know there is this one for iOS: https://github.com/edgarfgp/Net6iOSTemplate
It would be great to release those templates on NuGet for both iOS and Android (and Xamarin.Forms too).

I tried on my own to convert from C# to F#, but got inflate errors...

@Dolfik1
Copy link
Author

Dolfik1 commented Jan 26, 2022

F# doesn't support public static fields and Xamarin.Android required these

The generated resource file also contains partial class which is also not supported in F#

I was not asking for you or anyone to work on their spare time, especially if it's not even your day job.

We can try to fix type provider on our own. I think the main problem is the wrong type provider's project configuration.

The message I wanted to convey was more like it would be really appreciated if Microsoft as a whole would consider .NET to not be only C#, and put on the roadmap some F# to get some basic support on everything labeled .NET.

I am agree with @TimLariviere. F# reduces development time and provides the ability to write more compact and reusable code. But at the moment we have to spend a lot of time solving tooling/mono/AOT problems instead of solving business problems. At the moment we want to migrate to .Net 6 because some of our problems seem to be solved.

@Dolfik1 Would you happen to have an empty net6.0-android with F#?

I will attach sample app but it is incomplete without type provider.

I tried on my own to convert from C# to F#, but got inflate errors...

I also wrote a migration script from an old-style project. This script is not perfect but mostly works with few manual changes. (this script does not support packages.config, only Paket is supported)

migrator.fsx.zip

Net6Sample.zip

@TimLariviere
Copy link

TimLariviere commented Jan 26, 2022

Thanks!
I have created a repository to centralize all F# 6.0 templates for mobile development:
https://github.com/fabulousfx/net6.0-mobile-fsharp

This will be helpful to package them for dotnet new and put them on NuGet.
Later, I'll include Xamarin.Forms and maybe MAUI if I get it to work.

I'll take a look at the issue with the Android Type Provider as well.

@nosami
Copy link
Contributor

nosami commented Jan 26, 2022

@nosami Could I ping you if I happen to be stuck on non open-source stuff, so you could try to relay the message internally to the right people?

Sure! You can email me at jasonimison [AT] gmail [DOT] com or jaimison [AT] MS or I have an email rule to notify me if I am mentioned on GitHub.

I think personally I would stay with F# 5 and Mono until MAUI comes out. This issue should be resolved automatically.

@gsomix
Copy link
Contributor

gsomix commented Jan 26, 2022

This will be helpful to package them for dotnet new and put them on NuGet.

@TimLariviere Hey, could you create issue in your repository? I'll take a look.

@charlesroddie
Copy link
Contributor

charlesroddie commented Jan 26, 2022

"F# support is broken in Xamarin.Android" suggests that creating Xamarin.Android apps using F# has problems. The issue is much smaller, that creating a Xamarin.Android platform project in F# has problems.

Simplest solution is for dotnet new -i Fabulous.XamarinForms.Templates to generate C# Xamarin.Android projects which depend on an F# Fabulous Xamarin.Forms netstandard project.

Platform head projects are very tooling-dependent and F# projects are always going to be in a cycle of breaking and being fixed and lagging or non-existent tooling. (Unless sdk-style projects fix all problems?)

@Dolfik1
Copy link
Author

Dolfik1 commented Jan 26, 2022

I think personally I would stay with F# 5 and Mono until MAUI comes out. This issue should be resolved automatically.

We used FSharp.Core 4.7.2 for a long time due to AOT issues on iOS. Last month we updated FSharp.Core to version 6.0.2 and it seems that most of the issues have been fixed. But we got new issues with VTable setup on Android in System.Text.Json library (I still don not fully understand what happened, but it looks like the problem is that 6.0.2 supports netstandard2.1 while 4.7.2 does not). It looks like it has been fixed somehow in .Net 6.

Moreover, I do not really believe that all problems will be fixed on MAUI release.

@KevinRansom KevinRansom added the Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. label Jan 26, 2022
@dsyme
Copy link
Contributor

dsyme commented Jan 31, 2022

The issue is much smaller, that creating a Xamarin.Android platform project in F# has problems..... Platform head projects are very tooling-dependent and F# projects are always going to be in a cycle of breaking and being fixed and lagging or non-existent tooling. (Unless sdk-style projects fix all problems?)

I actually agree with this. It has always been a losing fight to attempt to have the more exotic platform headed by F# projects. For example, the iOS and Android platform heads took forever to move to .NET SDK, for example (have they moved now?) which made it impossible to maintain them.

It's up to the F# community, but there is wisdom in following @charlesroddie's advice here and consider the F# platform head projects for Android and iOS deprecated in favour of focusing on the incorporation of F# libraries and component code into C#-project-headed Android and iOS apps (that is, solutions headed by relatively minimal C# projects for packaging and delivery). There are almost certainly more important things to advocate, and it's a realistic, stable position.

@TimLariviere
Copy link

Even though I understand the reasoning behind this, I feel it's unfair to abandon F# heads because Microsoft will always only focus on C#.

We're actually extremely close to have something fully working.

iOS head already work,
Android head's only issue is Resource.designer.cs only generated for C# - once compiled for F# all is good.

Other issues are not related to the F# heads, but to F# as a whole.

And I fear this "give-up" will only lead in the future to an abandonment of F# support à la .NET Native; blocking all apps with F# libs from being published to the Windows Store because of tail-recursion. ("Too hard to implement and nobody uses F#, so we don't care")

For example, the iOS and Android platform heads took forever to move to .NET SDK, for example (have they moved now?)

Yes, with .NET 6.0.
Even though Microsoft only provided C# heads with .NET SDK, F# heads also work out of the box.

@dellis1972
Copy link

iOS head already work, Android head's only issue is Resource.designer.cs only generated for C# - once compiled for F# all is good.

The Android head Resource.desinger.cs issue will hopefully be solved when this PR dotnet/android#6427 is completed. It will remove the need for the Type Provider in favour of generating a Resource Assembly via IL directy. I have tested this locally with an F# Head project as it works as expected.
Note we plan to enable this new system for .net 6 onwards.

@TimLariviere
Copy link

@dellis1972 That's a wonderful news. Would you have an estimation when this would be available?
Same as MAUI release (mid 2022) ?

@Dolfik1
Copy link
Author

Dolfik1 commented Jan 31, 2022

The issue is much smaller, that creating a Xamarin.Android platform project in F# has problems..... Platform head projects are very tooling-dependent and F# projects are always going to be in a cycle of breaking and being fixed and lagging or non-existent tooling. (Unless sdk-style projects fix all problems?)

I actually agree with this. It has always been a losing fight to attempt to have the more exotic platform headed by F# projects. For example, the iOS and Android platform heads took forever to move to .NET SDK, for example (have they moved now?) which made it impossible to maintain them.

It's up to the F# community, but there is wisdom in following @charlesroddie's advice here and consider the F# platform head projects for Android and iOS deprecated in favour of focusing on the incorporation of F# libraries and component code into C#-project-headed Android and iOS apps (that is, solutions headed by relatively minimal C# projects for packaging and delivery). There are almost certainly more important things to advocate, and it's a realistic, stable position.

I am really sorry to hear that. We are developing a mobile application on F# for a few years. During this time we have contributed to F# on our own (Fabulous, Paket, Fantomas, FSharp.Core, LiteDb.FSharp, XF, Xamarin.Android, Xamarin.iOS etc). We are actively involved in the Russian F# community and hiring engage people. We made a Fabulous fork and develop it separateley but sharing experience with @TimLariviere to include this in the main repository. We also developed Fabulous.Android/Fabulous.iOS. This allows you to build Android/iOS apps with native UI on F# and it works pretty fine. We also have plans to make them open source. During this time we have included many people in the development on F# (we have retrained several people from Kotlin/Swift/Obj-C/C# to F#).

Our application, in addition to business logic, also includes a very complex UI. It's the reason why we do not want to C#-project-headed Android and iOS apps. For the same reason, we refused Xamarin.Forms. It is very painful to write a complex UI with Xamarin.Forms.

If you are interested, I can give you more details about our app and our architecture in PM.

@dellis1972
Copy link

@dellis1972 That's a wonderful news. Would you have an estimation when this would be available? Same as MAUI release (mid 2022) ?

Not at this time, there are still some issues around design time build we need to figure out. I'm trying to get this in as soon as I can because there are some app size and app speed benifits around this new method which should help both C# and F# users.

@dsyme
Copy link
Contributor

dsyme commented Jan 31, 2022

@dellis1972 That's great news

@Dolfik1 This is very valuable feedback - it's really important to understand the breadth of usage and all sceanrios being impacted

Our application, in addition to business logic, also includes a very complex UI. It's the reason why we do not want to C#-project-headed Android and iOS apps. For the same reason, we refused Xamarin.Forms. It is very painful to write a complex UI with Xamarin.Forms.

Just to check, you're saying it's not possible to write this as a set of F# Android/iOS libraries implementing the complex UI, with a very thin C# head for resources, packaging etc?

I'm actually not taking any specific opinion here, and will leave the quetion of head-support to various teams who have that responsibility - just agreeing with @charlesroddie's general point that historically they've been difficult.

@Dolfik1
Copy link
Author

Dolfik1 commented Jan 31, 2022

Just to check, you're saying it's not possible to write this as a set of F# Android/iOS libraries implementing the complex UI, with a very thin C# head for resources, packaging etc?

In short, you can't. In Android development resources are used for access to bundeled images/audio/fonts and to specify styles for widgets. For example:

View.TextInputLayout(
  style = Resources.Style.Widget_MaterialComponents_TextInputLayout_OutlinedBox
)

Theoretically you can access to images/audio/fonts with other methods but it is difficult and overheaded. However, you cannot access style resources in the same way.

But in general, I would like to note that the problem is not only in the resource provider specifically. The problem is weak F# support in Xamarin/Mono and weak support by Microsoft generally.

As @TimLariviere noted above:

Existing F# Xamarin templates removed from Visual Studio 2022
Mono not supporting F# 6.0
No official way to use F# with net6.0-ios and net6.0-android (I don't even know if we can create Android project with F# on .NET 6.0)

About a year ago we were forced to have our own compiled version of the F# compiler because Mono didn't update it. (/mono/mono/issues/16763)

I hope it gets better with the migration to Net 6.0, but it looks like .Net/VS team is not very interested in F# support.

@dsyme
Copy link
Contributor

dsyme commented Jan 31, 2022

Well, it would be good to get issues raised in the correct repos - literally none of the above is implemented in this repo, so this is just not the right place to be discussing this. The product owners for Xamarin.Android etc. are ultimately responsible here and you need to be talking with them.

@dsyme
Copy link
Contributor

dsyme commented Jan 31, 2022

Mono not supporting F# 6.0

@nosami Do we have a canonical issue link for this?

No official way to use F# with net6.0-ios and net6.0-android (I don't even know if we can create Android project with F# on .NET 6.0)

Anyone have a link to any specific outstanding issues on this?

Existing F# Xamarin templates removed from Visual Studio 2022

Anyone got any links to this? TBH I'm not too surprised by this given the old templates used the old project format, and were VS-only, and I think actually only VS-for-Windows (or was it VS-for-mac?). They'd need to be replaced by command line dotnet new templates.

@nosami
Copy link
Contributor

nosami commented Jan 31, 2022

Mono not supporting F# 6.0

@nosami Do we have a canonical issue link for this?

Not as far as I know. Mono is being superceded by .Net Core. There are no plans to support F# 6 in Mono.

I'm currently getting F# 6 working in VS Mac, but that's a separate issue.

@dsyme
Copy link
Contributor

dsyme commented Jan 31, 2022

Not as far as I know. Mono is being superceded by .Net Core. There are no plans to support F# 6 in Mono.

Right, I think that's part of the issue here - it's been hard to justify the udpate when Mono is being replaced within Xamarin stack. So much chicken and egg.

I'm currently getting F# 6 working in VS Mac, but that's a separate issue.

❤️ ❤️ ❤️ ❤️

@Dolfik1
Copy link
Author

Dolfik1 commented Feb 1, 2022

Well, it would be good to get issues raised in the correct repos - literally none of the above is implemented in this repo, so this is just not the right place to be discussing this. The product owners for Xamarin.Android etc. are ultimately responsible here and you need to be talking with them.

The main problem at this moment it is the type provider. I have created an issue at November 12 but still no solution. For this reason, I decided to create an issue in this repository to draw more attention to the problem.

Anyone got any links to this? TBH I'm not too surprised by this given the old templates used the old project format, and were VS-only, and I think actually only VS-for-Windows (or was it VS-for-mac?). They'd need to be replaced by command line dotnet new templates.

This is not possible at the moment (at least for android) due to type provider issues. So we first need to solve type provider problem. I really like what @dellis1972 does but I am afraid at the moment this cannot be a solution, since it is not known how soon it will be released. For this reason, I think we still need to solve the problem with the type provider.

However at the moment, nothing prevents you from create templates for iOS.

@dsyme
Copy link
Contributor

dsyme commented Feb 1, 2022

The main problem at this moment it is the type provider. I have created an issue at November 12 but still no solution. For this reason, I decided to create an issue in this repository to draw more attention to the problem.

What if you just don't use the type provider and address the resources by string? i.e. the generated code that the type provider produces?

@Dolfik1
Copy link
Author

Dolfik1 commented Feb 1, 2022

What if you just don't use the type provider and address the resources by string? i.e. the generated code that the type provider produces?
It seems that this is really possible, but it is very expensive. It will look something like this:

context.Resources.GetIdentifier("Widget_MaterialComponents_TextInputLayout_OutlinedBox", "style", context.PackageName)

instead of

Resources.Style.Widget_MaterialComponents_TextInputLayout_OutlinedBox

This way is also unsafe because you can make a typo in resource name. It is also more difficult to refactor.

@dsyme
Copy link
Contributor

dsyme commented Feb 1, 2022

This way is also unsafe because you can make a typo in resource name. It is also more difficult to refactor.

Yes, but it works and unblocks.

TBH I was always skeptical about putting a type provider into the main path of such a corner case of the Xamarin build story. I would have absolutely no problem with ripping it out of any defaults and making it a community-provided optional add-on that the community maintains - we don't bake in type providers like FSharp.Configuration into .NET Core projects even though they are also useful. If we expect the Xamarin team to maintain F# support we need things to be really simple for them.

@Dolfik1
Copy link
Author

Dolfik1 commented Feb 1, 2022

TBH I was always skeptical about putting a type provider into the main path of such a corner case of the Xamarin build story. I would have absolutely no problem with ripping it out of any defaults and making it a community-provided optional add-on that the community maintains - we don't bake in type providers like FSharp.Configuration into .NET Core projects even though they are also useful. If we expect the Xamarin team to maintain F# support we need things to be really simple for them.

You are absolutely right. But for now, we have what we have. There is good news about IL generated assembly for resources but still we need some temporary solution.

At the moment @TimLariviere is trying to revive the type provider but there are some difficulties.

@TimLariviere
Copy link

TimLariviere commented Feb 3, 2022

FYI: I abandoned trying to revive the type provider (too many issues with it) and did a small MSBuild target to build the C# designer file as part of the build.

https://github.com/fabulousfx/FSharp.Android.Resource

A little rough for the moment, but it works with both .NET Framework and .NET 6.0!
It's a drop-in replacement for Xamarin.Android.FSharp.ResourceProvider.

Should be good enough until we get dotnet/android#6427

@nbevans
Copy link

nbevans commented Feb 4, 2022

That's a very simple solution @TimLariviere one wonders why the original impl didn't just do this? Looking forward to the IL generation though.

@dsyme
Copy link
Contributor

dsyme commented Feb 4, 2022

That's a very simple solution @TimLariviere one wonders why the original impl didn't just do this? Looking forward to the IL generation though.

It should have, a type provider wasn't the right solution for this case, especially in retrospect.

@dsyme
Copy link
Contributor

dsyme commented Mar 3, 2022

Closing as the workaround is to use the community alternative here: #12640 (comment)

@dsyme dsyme closed this as completed Mar 3, 2022
jonpryor pushed a commit to dotnet/android that referenced this issue Jan 5, 2023
…6427)

Fixes: #6310

Context: dotnet/runtime@60d9b98
Context: dotnet/fsharp#12640
Context: 103b5a7   Optimize ResourceIdManager.UpdateIdValues() invocations
Context: 9e6ce03   Adds $(AndroidLinkResource)
Context: 522d7fb
Context: 9c04378   (AndroidEnablePreloadAssemblies crash)
Context: d521ac0   (Styleables array values)

Replace the existing `Resource.designer.cs` generation code with a
new system that relies on Reference Assemblies.  This results in
smaller apps and faster startup.


~~ Bind `@(AndroidResource)` values as fields ~~

The original approach to binding `@(AndroidResource)` values was to
Do What Java Does™: there are two "styles" of `Resource.designer.cs`
files, one for Library projects, and one for App projects.

`Resource.designer.cs` for Library projects involves mutable read/write
fields:

	[assembly: Android.Runtime.ResourceDesignerAttribute ("ExampleLib.Resource", IsApplication=false)]
	namespace ExampleLib;
	partial class Resource {
	  partial class String {
	    public static int app_name = 2130771968;
	    static String() {
	      global::Android.Runtime.ResourceIdManager.UpdateIdValues();
	    }
	  }
	  partial class Styleable {
	    public static int[] MyLibraryWidget = new int[]{…};
	    static Styleable() {
	      global::Android.Runtime.ResourceIdManager.UpdateIdValues();
	    }
	  }
	}

`Resource.designer.cs` for App projects involves *`const`* fields:

	[assembly: Android.Runtime.ResourceDesignerAttribute ("App.Resource", IsApplication=true)]
	namespace App;
	partial class Resource {
	  partial class String {
	    public const int app_name = 2130968576;
	    static String() {
	      global::Android.Runtime.ResourceIdManager.UpdateIdValues();
	    }
	  }
	  partial class Styleable {
	    public static int[] MyLibraryWidget = new int[]{…}; // still read+write, not const
	    static Styleable() {
	      global::Android.Runtime.ResourceIdManager.UpdateIdValues();
	    }
	  }
	}

There is a field each Android `resource` in the project *and* any
`resource`s declared in a referenced assembly or `.aar` files.
This can result in 1000's of fields ending up in each `Resource` class.

Because we only know the final `Id` values at app packaging time,
library projects could not know those values at build time.  This meant
that we needed to update those library values at startup with the ones
that were compiled into the final application project.  This is handled
by the `Resource.UpdateIdValues()` method.  This method is called by
reflection on app startup and contains code to set the read/write
fields for *all* `Resource` types from *all referenced assemblies*:

	partial class Resource {
	  public static void UpdateIdValues() {
	    global::ExampleLib.Resource.String.app_name = String.app_name;
	    // plus all other resources
	  }
	}

**Pros**:

  * It's a "known good" construct, as it's what Java does!
    (Or *did*, circa 12 years ago…)

**Cons**:

  * There is a semantic difference between the use of the `Resource`
    types between Library and App projects: in an App project, you
    can use Resource IDs in switch `case`s, e.g.
    `case Resource.String.app_name: …`.
    This is not possible in Library projects.

  * As the App `Resource.UpdateIdValues()` method references *all*
    fields from all referenced libraries, the linker is not able to
    remove any of the fields.  This pattern is linker hostile.
    This results in larger `.apk` sizes, though this can be optimized
    via [`$(AndroidLinkResources)`][0] (9e6ce03, d521ac0).

  * As the App `Resource.UpdateIdValues()` method references *all*
    fields from all referenced libraries, the method can be *huge*;
    it depends on how many resources the App and all dependencies
    pull in.  We have seen cases where the size of
    `Resource.UpdateIdValues()` would cause the interpreter to crash,
    breaking certain Hot Reload scenarios.
    (Fixed in dotnet/runtime@60d9b989).

  * The `Resource.UpdateIdValues()` method needs to be invoked during
    process startup, *before* any assemblies try to use their
    `Resource.…` values, and the method is looked up via *Reflection*.
    This means System.Reflection is part of the app startup path,
    which has overheads.
    (This overhead is also removed via `$(AndroidLinkResources)`.)


~~ Bind `@(AndroidRoesource)` values as properties  ~~

Replace the "bind resources as fields" approach with a new system
with significant differences:

 1. Android resource ids are bound as read-only *properties*, and

 2. The `Resource` class is placed into a *separate assembly*,
    `_Microsoft.Android.Resource.Designer.dll`.

The new `$(AndroidUseDesignerAssembly)` MSBuild property controls
which Android resource approach is used; if True -- the default for
.NET 8 -- then `_Microsoft.Android.Resource.Designer.dll` will be
used.  If False, then the previous "bind resource ids as fields"
approach will be used.  This property is only valid for Library
projects; App projects must use the property-oriented approach.

This new approach takes advantage of [Reference Assemblies][1].
Reference Assemblies are designed to be replaced at runtime, and are
generally used to provide placeholder API's which can be swapped out
later.

Library projects will generate a Reference Assembly for
`_Microsoft.Android.Resource.Designer.dll` which contains read-only
properties for each `@(AndroidResource)` within the project and all
dependencies.  This is otherwise identical to the "fields" approach,
*except* that the namespace is predefined, its a new assembly, and
properties are used instead of fields, *as if* it contained:

	// _Microsoft.Android.Resource.Designer.dll for Library project
	[assembly: System.Runtime.CompilerServices.ReferenceAssemblyAttribute]
	namespace Microsoft.Android.Resource.Designer;
	public partial class Resource {
	  public partial class String {
	    public static int app_name => 0;
	  }
	  public partial class Styleable {
	    public static int[] MyLibraryWidget => nullptr;
	  }
	}

Also note that `_Microsoft.Android.Resource.Designer.dll` is produced
*with Mono.Cecil* as a pre-build action; no C# source is generated.
The Library assembly references the generated
`_Microsoft.Android.Resource.Designer.dll`.

The generated `_Microsoft.Android.Resource.Designer.dll` should
***NOT*** be shipped with NuGet packages.

App projects will generate the "real"
`_Microsoft.Android.Resource.Designer.dll`, also as a pre-build step,
and the "real" assembly will contain actual values for resource ids.
The App-built `_Microsoft.Android.Resource.Designer.dll` will also
have `[assembly:InternalsVisibleToAttribute]` to the App assembly:

	// _Microsoft.Android.Resource.Designer.dll for App project
	[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute ("App…")]
	namespace Microsoft.Android.Resource.Designer;
	public partial class Resource {
	  public partial class String {
	    public static int app_name => 2130968576;
	  }
	  public partial class Styleable {
	    static int[] MyLibraryWidget = new[]{…};
	    public static int[] MyLibraryWidget => MyLibraryWidget;
	  }
	}

This approach has a number of benefits

 1. All the property declarations are in one place and are not
    duplicated (-ish… more on that later).
    As a result the size of the app will be reduced.

 2. Because we no longer need the `Resource.UpdateIdValues()` method,
    start up time will be reduced.

 3. The linker can now do its job and properly link out unused
    properties.  This further reduces application size.

 4. F# is now fully supported.  See also: dotnet/fsharp#12640.


~~ Styleable Arrays ~~

Styleable resources may be arrays; see e.g. d521ac0.  Via the power
of Cecil (and not using C# as an intermediate codegen), the binding
of styleable arrays in the "Bind `@(AndroidRoesource)` values as
properties" world order involves a static field containing the array
data, and a public property which returns the private field, which
has the same name:

	public partial class Resource {
	  public partial class Styleable {
	    static int[] MyLibraryWidget = new[]{…};
	    public static int[] MyLibraryWidget => MyLibraryWidget;
	  }
	}

CIL-wise, *yes*, the field and the property have the same name (?!),
but because properties actually have `get_` method prefix, there will
actually be a `MyLibraryWidget` field and a `get_MyLibraryWidget()`
method, so there are no name collisions.

*Note*: ***The styleable array is not copied***.  This means it is
global mutable data, i.e. one can do this:

	Microsoft.Android.Resource.Designer.Resource.Styleable.MyLibraryWidget[0] = 42;

***DO NOT DO THIS***.	It will introduce runtime errors.

The e.g. `Resource.Styleable.MyLibraryWidget` property must be an
`int[]` in order to maintain compatibility, as these are often
passed to methods which take `int[]` as the parameter type.  We thus
cannot instead use e.g. `IEnumeragble<int>` as the property type.
Additionally, the array isn't copied for performance reasons.

We do not think that this will be a problem in practice, as the
previous "Bind `@(AndroidRoesource)` values as fields" strategy
*also* had mutable `int[]` fields, and suffers from the same
safety concerns, and the world hasn't ended…


~~ Source Compatibility ~~

In the "bind resource ids as fields" approach, the `Resource` class
was in the default namespace for the Library project, set via the
[`$(RootNamespace)`][2] MSBuild property.  In order to maintain
source compatibility, Library projects will have a generated
`__Microsoft.Android.Resource.Designer.cs` file which contains a new
`Resource` declaration which *inherits* from the `Resource` type in
`_Microsoft.Android.Resource.Designer.dll`:

	// Generated __Microsoft.Android.Resource.Designer.cs in Library projects
	namespace ExampleLib;
	public class Resource : Microsoft.Android.Resource.Designer.Resource {
	}

This allows existing code such as `ExampleLib.Resource.String.app_name`
to continue to compile.

App projects also expect a `Resource` class in `$(RootNamespace)`,
*and* expect the values to be `const`.  To support this, the generated
`_Microsoft.Android.Resource.Designer.dll` *actually* has two sets
of `Resource` types, one with properties, and an *`internal`*
`ResourceConstant` type:

	// _Microsoft.Android.Resource.Designer.dll for Library project
	[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute ("App…")]
	namespace Microsoft.Android.Resource.Designer;
	internal partial class ResourceConstant {
	  public partial class String {
	    public const int app_name = 2130968576;
	  }
	}

	public partial class Resource {
	  public partial class String {
	    public static int app_name => ResourceConstant.String.app_name;
	  }
	}

App projects *also* have a generated
`__Microsoft.Android.Resource.Designer.cs`, which has a `Resource` type
which inherits from `ResourceConstant`.  This is why the App-built
`_Microsoft.Android.Resource.Designer.dll` needs
`[assembly: InternalsVisibleToAttribute]`:

	// Generated __Microsoft.Android.Resource.Designer.cs in App projects
	namespace App;
	public class Resource : Microsoft.Android.Resource.Designer.ResourceConstant {
	}

This allows existing App code to use `App.Resource.String.app_name`
in `case` statements.


~~ Binary Compatibility ~~

Binary compatibility is maintained via a new
`MonoDroid.Tuner.FixLegacyResourceDesignerStep` linker step.
`FixLegacyResourceDesignerStep` rewrites Library assemblies to replace
`Resource.…` field access with property access to
`Microsoft.Android.Resource.Designer.Resource.…` in
`_Microsoft.Android.Resource.Designer.dll`.  Much of this code
overlaps with the existing logic of `$(AndroidLinkResources)`, and
allows existing Library assemblies to participate in the property-
oriented system.


~~ Internals ~~

The new build system introduces a number of new Tasks and Targets to
bring this all together.  It also unify's some code between the field-
oriented and property-oriented approaches which would otherwise be
duplicated.  The field-oriented system will be maintained for now for
backward compatibility, however the property-oriented system will be
enabled by default for .net 8.

The property-oriented system is mostly contained in
`Xamarin.Android.Resource.Designer.targets`.  The entry point for this
set of targets is `_BuildResourceDesigner`, which will only be run if
the `$(AndroidUseDesignerAssembly)` MSBuild property is `True`, as it
will be for .NET 8+.

New tasks are as follows.

  - `<GenerateRtxt/>` is responsible for scanning the resource
    directory and generating an `aapt2`-compatible `R.txt` file.
    This will be used by `<GenerateResourceDesignerAssembly/>`.

  - `<GenerateResourceCaseMap/>` is responsible for generating a
    `casemap.txt` file which will map the all lower case android
    resources to the casing required for the C# code.  Android requires
    ALL resources be lower case, but our system allows the user to
    define the case using any system then want.  This task handles
    generating this mapping between what the android system needs and
    what the user is expecting.  Its output is used by the
    `<GenerateResourceDesignerAssembly/>` task when generating the IL
    in `_Microsoft.Android.Resource.Designer.dll`.
    It is also used by the old system to generate the same file.

  - `<GenerateResourceDesignerIntermediateClass/>` is responsible for
    generating the `__Microsoft.Android.Resource.Designer.cs` file in
    `$(IntermediateOutputPath)`.


  - `<GenerateResourceDesignerAssembly/>` is the key to the whole
    property-oriented approach.  This task will read the `R.xt` file
    and generate a `_Microsoft.Android.Resource.Designer.dll` assembly
    in `$(IntermediateOutputPath)`.  This task is called in two places.
    The first is in `_GenerateResourceDesignerAssembly`, this is called
    as part of the build which happens just before `CoreCompile` and
    only for design time builds.
    It is also called in `_UpdateAndroidResgen` which happens as part
    of the build and runs just after `aapt2` is called.  This ensures
    we always use the most up to date version of `R.txt` to generate
    the new assembly.

Because we are using the `R.txt` file to drive the generation of the
new assembly, we needed some way for that to work when `aapt2` was
not being run.  This usually happens on a first time design time build.
The field-oriented approach has a `<GenerateResourceDesigner/>` task
which is responsible for both scanning the resources and generating a
design time `Resource.designer.cs` file.  While we could have
duplicated the code it made more sense to split out the resource
scanner into its own class.  We now have a new `<GenerateRtxt/>` task
which is responsible for scanning the resources and generating an
`R.txt` file.  This is only used when we are not doing a full build
with `aapt2`.  This new task lets us generate the needed `R.txt` which
can then be used by both the old and new system to generate their
respective outputs.

As part of this we have two other classes: `RtxtReader` and
`RtxtWriter`.  The `RtxtReader` unify's the code which was used to read
the values of the `R.txt` into one class which can be used by both
approaches.  The `RtxtWriter` is responsible for writing the `R.txt`
file for design time builds.  Again it will be used by both the old
and new system.

The `_AddResourceDesignerFiles` target is responsible for ensuring that
the new assembly and `__Microsoft.Android.Resource.Designer.cs` get
added to the correct item groups.  These are `@(ReferencePath)` for the
assembly and `@(Compile)` for the source file.  In the case of F# the
`__Microsoft.Android.Resource.Designer.fs` file which gets generated
has to be added to the `@(CompileBefore)` ItemGroup, this is so that
the types are resolved in the correct order.

To ensure that the new assembly is added to the final application we
have to introduce the `_AddResourceDesignerToPublishFiles` target.
This target makes sure that the new assembly is added to the
`@(ResolvedFileToPublish)` ItemGroup.  It also adds the require
MetaData items such as `%(IsTrimmable)` and `%(PostprocessAssembly)`
which are required to get the assembly linked correctly.


~~ Results ~~

Results are most visible when lots of Android Resources are used.
For a [Sample app][3] app which uses lots of resources, we see the
following improvements to the **ActivityTaskManager: Displayed** time:

| Before (ms) |  After (ms) |     Δ (%) | Notes                                |
| ----------: | ----------: | --------: | ------------------------------------ |
|     340.500 |     313.250 |  -8.00% ✓ | defaults; 64-bit build               |
|     341.950 |     316.200 |  -7.53% ✓ | defaults; profiled AOT; 64-bit build |
|     345.950 |     324.600 |  -6.17% ✓ | defaults; 32-bit build               |
|     341.000 |     323.050 |  -5.26% ✓ | defaults; profiled AOT; 32-bit build |

[0]: https://learn.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#androidlinkresources
[1]: https://learn.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies
[2]: https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2022
[3]: https://github.com/dellis1972/DotNetAndroidTest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Projects
None yet
Development

No branches or pull requests