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

[Bug] iOS: On-Demand Resource Tag not available to be set #27213

Open
Sebastian1989101 opened this issue Jan 17, 2025 · 21 comments
Open

[Bug] iOS: On-Demand Resource Tag not available to be set #27213

Sebastian1989101 opened this issue Jan 17, 2025 · 21 comments
Labels
area-single-project Splash Screen, Multi-Targeting, MauiFont, MauiImage, MauiAsset, Resizetizer platform/iOS 🍎 s/needs-attention Issue has more information and needs another look

Comments

@Sebastian1989101
Copy link

As the "Community Question" part this repo links to just ends in a 404 when posting a question and after multiple hours of trying to solve it and find the answer to this, I'm pretty certian it's just another bug with MAUI:

With Asset packs for Android now in MAUI .NET 9, I wonder how to use the iOS equivalent of On Demand Resources in MAUI. I thought I could port over a few of my older Apps (which still use Xamarin.Forms) to MAUI but those app are using On Demand Resources for iOS to download ressources only when needed. However, neither the Build Action BundleResource nor the On Demand Resource Tags can be set in a MAUI project. So how is it supposed to be set to work for iOS and Android?

There is not option on files at Platforms/iOS/Resources/* and also not on Resources/Raw/* where I can set the tags for iOS. In Xamarin.Forms, I had the option to set those tags for BundleResource files in my iOS project.

@TommiGustafsson-HMP
Copy link
Contributor

TommiGustafsson-HMP commented Jan 18, 2025

You can probably try editing csproj directly and use build actions like this in an ItemGroup with a condition for iOS:

<BundleResource Include="PathToFile" />

And see if it works. I have no real knowledge if it works, but at least you can try it out.

@jfversluis
Copy link
Member

All Build Actions that were available in Xamarin.iOS should still work, make sure to add a condition on them to only include them for the iOS platform when using single-project.

If the issue then is that being not able to set these properties through the UI in Visual Studio, then this is something in the tooling.

Otherwise, you state that you think this is a bug in .NET MAUI, but without any information about what you have tried, version information, a reproduction, logs or anything similar it will be pretty hard to verify that. Please provide any additional details for us to look at and we will gladly try to help if we can.

@jfversluis jfversluis added area-single-project Splash Screen, Multi-Targeting, MauiFont, MauiImage, MauiAsset, Resizetizer platform/iOS 🍎 s/needs-info Issue needs more info from the author labels Jan 18, 2025
@Sebastian1989101
Copy link
Author

@jfversluis the issue is the following: Based on the description here: https://learn.microsoft.com/en-us/dotnet/maui/android/asset-packs?view=net-maui-9.0#asset-packs-in-net-maui-apps MAUI Apps with .NET 9 now have the option for MauiAssets to be used as Android asset packs (the equivalent of iOS On-Demand Resources). However this does not reflect over to the iOS build even tho the AssetPack="myassets" could easily be the <ResourceTags>myassets</ResourceTags> tag used for the iOS On-Demand Resources.

If I now have both in my app, the "asset" is twice in the iOS *.ipa file (if the On-Demand Resource even works as the UI in VisualStudio has no option to set the Tag as it does in Xamarin.iOS / Xamarin.Forms applications). Also there is 0 documentation about the iOS On-Demand Resource except the official one from Apple and a few guides on the Internet how this has to be done on Xamarin.iOS.

And there is even less for the new AssetPack part of MAUI .NET 9 even tho it is very likely that if I use this feature, I also want to use the very same on the other platform. So logically it already makes no sense at all, once again, that the added part to make this finally work on Android does not also include iOS even tho it has worked before. That's just inconsistant and bad.

And I do not have the BundleResource option for my MauiAssets or even the files added to Platforms/iOS/Resources. And if I try to set it manually in the dropdown of VisualStudio all I get is an error message. https://i.imgur.com/otfKjzc.png

This is how it is on my Xamarin.Forms / iOS projects: https://i.imgur.com/ZsE3d1t.png

And what did I try? I tried to add it manually in the *.csproj file. I tried every possible option I could imagine in VisualStudio.

So basically I have now to write complex ItemGroup Conditions in the hope that it will build properly and work after manually editing the *.csproj file. It's baffling that MAUI and the associated tooling is still so unfinished that you can't even upgrade Xamarin.Forms projects without hourlong research and try&error.

@dotnet-policy-service dotnet-policy-service bot added s/needs-attention Issue has more information and needs another look and removed s/needs-info Issue needs more info from the author labels Jan 18, 2025
@TommiGustafsson-HMP
Copy link
Contributor

TommiGustafsson-HMP commented Jan 18, 2025

The thing is that Android apps have a maximum size of 200 MB (without asset packs) and iOS apps have a maximum size of 4 GB. It was much needed that at least install-time asset packs were implemented for Android (which can be 1.5 GB in size) that would increase the total app size limit to 4 GB on Android. 200 MB is so small that publishing even a medium-sized game in the Google Play Store requires the use of asset packs. The implementation of Play Asset Delivery asset packs in .NET 9 was great (thanks to @dellis1972) and it allowed us to stop using manual workarounds that were required for Xamarin.Forms apps (essentially building the asset packs manually using aapt2).

I understand that implementing On-Demand resources for iOS has not been on very high priority for the .NET iOS / MAUI team, since the 4 GB app size limit is pretty high. Also, the implementation is platform-dependent, so whatever work the .NET for Android team did for Play Asset Delivery asset packs, can't be used by the .NET for iOS team. If you want to know if the old code from Xamarin.Forms still works, I'd suggest contacting the .NET for iOS team over here: https://github.com/xamarin/xamarin-macios

It's true that things could be in a better shape, but I suggest that you try to contact the right people and perhaps find some solution to your problem that you can share with others that have the same problem. You can probably find emails of people responsible for .NET for iOS on GitHub and contact them directly, if need be, or you can leave an issue or start a discussion.

@Sebastian1989101
Copy link
Author

@TommiGustafsson-HMP it already worked for iOS with Xamarin.iOS and Xamarin.Forms. It just makes no sense, once again, how they solved it here. No documentation at all on this topic. Weird *.csproj edits that may work or may not work. While there is a implementation available with .NET 9 that could just be used on both platforms.

I agree, it was necessary and long overdue for Android. But there is no reason at all why the already working implementation of Xamarin.iOS and Xamarin.Forms seems to be not available and is not done in the same way as it is the same thing.

And for your games reference: Who the hack would make games with MAUI? There are far better options available for that.

@TommiGustafsson-HMP
Copy link
Contributor

We have a Xamarin.Forms / MAUI game here:

https://gnollhack.com

We used Xamarin.Forms originally with SkiaSharp, because we needed to port the NetHack codebase to modern operating systems and it turned out after long research that Xamarin.Forms and .NET had the best interoperability with C code.

You can read more about it in this article: https://github.com/hyvanmielenpelit/GnollHack/wiki/Mobile-Version-Development

@Sebastian1989101
Copy link
Author

And just because you had a, probably giga niche, use case for it, thats the reason already working and existing stuff should be broken on the newer replacement framework? Yes makes sense. Espacially if similar functionality is added on another platform, it makes much sense that exclude the other platforms on a cross platform framework.

That asset packs where not available before was due to Xamarin.Android. But now Xamarin.Android and Xamarin.iOS have this feature. So the reasoning excluding a platform should not exist. Espacially because this should be a very small thing.

@dellis1972
Copy link
Contributor

dellis1972 commented Jan 19, 2025

So the MauiAsset ItemGroup will be transformed to BundledResource as part of the build when you target iOS.

One option is to use the following in your csproj. There should be no reason to duplicate the Items as the AssetPack attribute will be ignored on iOS. Note it uses Update not Include this will update existing items. So you can just drop this little ItemGroup at the bottom of your .csproj.

	<ItemGroup>
		<MauiAsset Update="/Resources/Raw/*" AssetPack="installtime" ResourceTags="installtime" />
                <!-- its iOS resource so we don't need assetpack on it -->
                <MauiAsset Update="Platforms/iOS/Resources/*" ResourceTags="installtime" />
	</ItemGroup>

You can also limit the update to certain files via wildcards or specifying the actual specific path.

	<ItemGroup>
		<MauiAsset Update="/Resources/Raw/*.mp4" AssetPack="installtime" ResourceTags="installtime" />
                <MauiAsset Update="Platforms/iOS/Resources/*.mp4" ResourceTags="installtime" />
               <MauiAsset Update="Platforms/iOS/Resources/mylargefile.txt" ResourceTags="foo" />
	</ItemGroup>

Granted that is not obvious how to do that from the IDE. We'd need to talk to the IDE teams to figure out how to expose the metadata on MauiAsset.

Here is a sample of what this code produces

Copying file from '/Users/xxx/Documents/Sandbox/Repos/mauiassetpacks/Resources/Raw/Data0.bank' to '/Users/xxx/Documents/Sandbox/Repos/mauiassetpacks/bin/Release/net9.0-ios/iossimulator-arm64/OnDemandResources/com.companyname.mauiassetpacks.foo.assetpack/Data0.bank'

Note it will pickup the ResourceTags attribute and do the right thing.

@Sebastian1989101
Copy link
Author

@dellis1972 if it really works like this (will test this tomorrow), where is this documented? As I was not able to find anything about a ResourceTags attribute in the hours I thought I wasted on this topic. But if this is how it works, and if it really works, then it's exactly what I was looking for.

@dellis1972
Copy link
Contributor

@Sebastian1989101 its not documented for iOS or Android, as its how MSBuild works [1]. The "Metadata" (Attributes or Child Elements) are carried through to over ItemGroups when they are copied (for the most part).

There is no code in the iOS or Android .Net (or Maui) Sdks which specifically looks at/for the MauiAsset ItemGroup AssetPack or ResourceTag metadata. This section of my post https://learn.microsoft.com/en-us/dotnet/maui/android/asset-packs?view=net-maui-9.0#asset-packs-in-net-maui-apps, is just highlighting the build in MSBuild functionality.
What is happening in maui is the MauiAsset items are tranformed to AndroidAsset for android and Content for iOS (and then BundleResource.

To be honest, I know nothing about iOS, I didn't even know it supported OnDemand assets as my principal focus is Android.
But I do know this will work in the manner I have described as I tested this locally.

I changed the following code which is almost right from the blog post.

<ItemGroup>
	<MauiAsset Update="**\*.bank" AssetPack="installtime" />
</ItemGroup>

to

<ItemGroup>
	<MauiAsset Update="**\*.bank" AssetPack="installtime" ResourceTags="installtime" />
</ItemGroup>

and it worked as expected and placed all the ".bank" files in an OnDemand pack.😃

[1] https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-items?view=vs-2022#item-metadata

@Sebastian1989101
Copy link
Author

Sebastian1989101 commented Jan 19, 2025

@Sebastian1989101 its not documented for iOS or Android, as its how MSBuild works [1]. The "Metadata" (Attributes or Child Elements) are carried through to over ItemGroups when they are copied (for the most part).

There is no code in the iOS or Android .Net (or Maui) Sdks which specifically looks at/for the MauiAsset ItemGroup AssetPack or ResourceTag metadata. This section of my post https://learn.microsoft.com/en-us/dotnet/maui/android/asset-packs?view=net-maui-9.0#asset-packs-in-net-maui-apps, is just highlighting the build in MSBuild functionality. What is happening in maui is the MauiAsset items are tranformed to AndroidAsset for android and Content for iOS (and then BundleResource.

@dellis1972 this part alone in the documentation would have been really helpful. However you said you tested it, but did it work? Because on my machine it's only placing empty files in the assetpack target location and trying to access them results in a error:

Error Domain=NSCocoaErrorDomain Code=4994 "The requested application data doesn’t exist." UserInfo={NSUnderlyingError=0x600000c8ac10 {Error Domain=_NSBundleResourceRequestErrorDomain Code=100 "No manifest found for bundle ID com.mycompany.samplemauiapp" UserInfo={NSLocalizedFailureReason=No manifest found for bundle ID com.softwarenotion.samplecompanion}}}

https://i.imgur.com/T8ddkBX.png

For my test I just did the following:

Added this to the *.csproj file:

<MauiAsset Update="Resources\Raw\AboutAssets.txt" AssetPack="test123" ResourceTags="test123" />

And then added this to the Platforms/iOS/AppDelegate.cs file:

        public override async void OnActivated(UIApplication application)
        {
            base.OnActivated(application);

            var request = new NSBundleResourceRequest(new string[] { "test123" }) { LoadingPriority = NSBundleResourceRequest.LoadingPriorityUrgent };

            try
            {
                var result = await request.ConditionallyBeginAccessingResourcesAsync();
                if (!result)
                    await request.BeginAccessingResourcesAsync();

                var pathForResource = NSBundle.MainBundle.PathForResource("AboutAssets", "txt");
                var content = File.ReadAllText(pathForResource);

                Console.WriteLine(content);
            }
            catch (NSErrorException ex)
            {

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

This should have resulted in a On-Demand Resource loaded on request. However this might be a issue with the debug build because as far as I remember, this issue was also there on Xamarin.Forms / Xamarin.iOS and got ignored in the classic Microsoft/Xamarin/MAUI way of handling issues.

Edit: Yes, the same issue existed back in the days in Xamarin.Forms... well then I just hope it will work on a release build but thats something I can only test in a few days when I'm done converting my Forms app... xamarin/xamarin-macios#8026

@dellis1972
Copy link
Contributor

@Sebastian1989101

as I said, I'm not an iOS person. All I know is the data ended up in this path bin/Release/net9.0-ios/iossimulator-arm64/OnDemandResources/com.companyname.mauiassetpacks.foo.assetpack/Data0.bank when I tested the build. That looks to me like the right place.

@Sebastian1989101
Copy link
Author

@Sebastian1989101

as I said, I'm not an iOS person. All I know is the data ended up in this path bin/Release/net9.0-ios/iossimulator-arm64/OnDemandResources/com.companyname.mauiassetpacks.foo.assetpack/Data0.bank when I tested the build. That looks to me like the right place.

Should be correct, I agree. But in my case the files are just empty that got created there through VisualStudio. And I assume empty files is not fully correct. ;)

@dellis1972
Copy link
Contributor

@rolfbjarne might be able to shed some light on that issue.

@Sebastian1989101
Copy link
Author

Sebastian1989101 commented Jan 24, 2025

@dellis1972 Is there actually a reason why the MAUI documentation differs from the official Android documentation regarding the size etc. of the AssetPacks?

For example the MAUI documentation says there can only be one InstallTime AssetPack while this limit seems to not exist on the Android documentation. Also the Android documentations says up to 100 AssetPacks each 1.5GB max while MAUI says 50 with 2GB total.

https://learn.microsoft.com/en-us/dotnet/maui/android/asset-packs?view=net-maui-9.0#asset-delivery-options

https://developer.android.com/guide/playcore/asset-delivery?hl=en

https://support.google.com/googleplay/android-developer/answer/9859372#size_limits

I assume that MAUI just tunnels it through Xamarin.Android which just wraps the native stuff? So I assume the Android documentation limits are the correct ones? Same for iOS with the iOS documentation.

@dellis1972
Copy link
Contributor

@Sebastian1989101 I have no idea. I didn't write those docs, only the blog post.
The info in the blog was accurate at the time of posting. My tests showed that multiple install-time packs would produce an error when trying to either build or publish on google play, I can't remember.
Its possible the info has been changed, the google/android documentation should be considered the source of truth in these cases.

@Sebastian1989101
Copy link
Author

@dellis1972 is there any sample for InstallTime AssetPacks? I can see them if I build a publish package but when debugging on the simulator, the asset pack is nowhere to be found. And yes, I have included the part from "Test asset packs locally".

The pack in question is included this way:

<MauiAsset Update="Resources\Raw\*.db3" AssetPack="COREDATA" ResourceTags="COREDATA" DeliveryType="InstallTime" />

            var assetPackManager = AssetPackManagerFactory.GetInstance(this);
            AssetPackLocation assetPackPath = assetPackManager.GetPackLocation("COREDATA");
            string assetsFolderPath = assetPackPath?.AssetsPath() ?? null;

Results in null.

            //var stream = await FileSystem.Current.OpenAppPackageFileAsync("images.db3");
            var path = Path.Combine(FileSystem.Current.AppDataDirectory, "images.db3");
            var exists = File.Exists(path);

Both of these result in nothing. As said, I added this to the *.csproj as well, but no change at all:

  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <AndroidPackageFormat>aab</AndroidPackageFormat>
    <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
    <AndroidBundleToolExtraArgs>--local-testing</AndroidBundleToolExtraArgs>
  </PropertyGroup>

Searching for the asset pack on via adb shell (with root access) does not yield any results. https://i.imgur.com/jLTEPz5.png

When I build a release package, I can see that the AssetPack is included as expected: https://i.imgur.com/yHym4g5.png

So either it's not working. Or at least not in debugging. Or the documentation is not complete.

@TommiGustafsson-HMP
Copy link
Contributor

TommiGustafsson-HMP commented Jan 25, 2025

You should use a different syntax for Debug:

<AndroidAsset Include="Platforms\Android\banks\Master.bank" LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" Condition=" '$(AndroidPackageFormat)' == 'apk' " />

This assumes that the Debug build is using the APK package format (see Condition). LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" places your file into the banks subdirectory under the main directory of your app (%(RecursiveDir)).

Then for Release, you use:

<AndroidAsset Include="Platforms\Android\banks\Master.bank" LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" AssetPack="installtimeassetpack" DeliveryType="InstallTime" Condition=" '$(AndroidPackageFormat)' != 'apk'" />

This assumes that the Release build is using the AAB package format (see Condition).

I don't know how MauiAsset behaves, so maybe @dellis1972 can help you out there.

@Sebastian1989101
Copy link
Author

You should use a different syntax for Debug:

<AndroidAsset Include="Platforms\Android\banks\Master.bank" LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" Condition=" '$(AndroidPackageFormat)' == 'apk' " />

This assumes that the Debug build is using the APK package format (see Condition). LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" places your file into the banks subdirectory under the main directory of your app (%(RecursiveDir)).

Then for Release, you use:

<AndroidAsset Include="Platforms\Android\banks\Master.bank" LogicalName="%(RecursiveDir)banks\%(Filename)%(Extension)" AssetPack="installtimeassetpack" DeliveryType="InstallTime" Condition=" '$(AndroidPackageFormat)' != 'apk'" />

This assumes that the Release build is using the AAB package format (see Condition).

I don't know how MauiAsset behaves, so maybe @dellis1972 can help you out there.

There is no "banks" subdirectory in my app and I also do not see any .bank files at all in bin/ or obj/* from my VisualStudio output. Also why would I need to specify the MauiAssets as AndroidAsset and move them around while Maui should already do this and if this is really necessary, why is it not part of the documentation? As said, I can see the AssetPack in the *.abb output when I use the publish function of VisualStudio but it seems to not work when debugging so it's untestable?

I haven't even converted 10% of my Xamarin.Forms app to Maui and it's already super frustrating due to documentation and things not working as expected. But this case is probably a Xamarin.Android thing and not even Maui's fault. So the question remains, why the InstallTime asset is missing. Or is it that, "InstallTime" is not even the proper tag and it needs to be "install-time" like it is on the Android documentation?

@TommiGustafsson-HMP
Copy link
Contributor

TommiGustafsson-HMP commented Jan 25, 2025

It was just a short example, so please let me explain more. You are expected to replace the assets and their paths with your own assets and their paths. In our project, we have an asset called Master.bank (it's a sound bank file), located in Platforms\Android\banks\Master.bank. For you, it can be something else. Then, we copy the asset to %(RecursiveDir)banks\%(Filename)%(Extension) in the APK/AAB, which results it being put into the banks subdirectory. If you don't want to put your assets into a subdirectory, you will use %(RecursiveDir)%(Filename)%(Extension) as your LogicalName.

The two asset paths work so that:

  • In Debug mode, we use the APK format and we want to copy our asset to the APK. We don't want to put it into an asset pack, since APK does not support asset packs.
  • In Release mode, we put the asset into the AAB bundle using the AssetPack and DeliveryType properties, since AAB supports asset packs.

This allows us to debug things in the debug mode using APKs, while putting them into an asset pack in the release mode. Naturally, you can configure things how you like, but this is the setup we are using currently.

@dellis1972
Copy link
Contributor

@Sebastian1989101

From the Android docs (and ours)

The --local-testing flag is required for testing on your local device. It tells the bundletool app that ALL the asset packs should be installed in a cached location on the device. It also sets up the IAssetPackManager to use a mock downloader which will use the cache. **_This allows you to test installing OnDemand and FastFollow packs in a Debug environment._**

'--local-testing' only works for OnDemand and FastFollow. For InstallTime you just don't use the AssetPack metadata for those assets. This is because installtime files get installed in the same place as assets which are packed into the apk/aab via AndroidAsset or MauiAsset. You don't need to use the AssetPackManager to load them, just the usual FileSystem API like for assets in the apk/aab.

This is probably because google can't really emulator how the installtime works for debug since those package are downloaded immediately while the app is downloading, and the app will not be reported as installed until the apk and the installtime packages are installed.

The conditional suggested by @TommiGustafsson-HMP is a good way to handle installtime packs. So you end up with
an extra Condition attribute on the MauiAsset. This means the AssetPack metadata will only be added if you are using an aab file.

<ItemGroup>
	<MauiAsset Update="Resources\Raw\*" AssetPack="installtime" Condition=" '$(AndroidPackageFormat)' != 'apk'"/>
</ItemGroup>

or you can key off the $(Configuration)

<ItemGroup>
	<MauiAsset Update="Resources\Raw\*" AssetPack="installtime" Condition=" '$(Configuration)' != 'Debug'"/>
</ItemGroup>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-single-project Splash Screen, Multi-Targeting, MauiFont, MauiImage, MauiAsset, Resizetizer platform/iOS 🍎 s/needs-attention Issue has more information and needs another look
Projects
None yet
Development

No branches or pull requests

4 participants