Telerik blogs

.NET for Android 9 allows you to avoid the 200 MB file size restrictions for your app on Google Play by using separate asset packs with 2 GB storage.

When publishing an app on Google Play, all the resources from your project, such as graphics, videos and audio, are packaged within the Android App Bundle (AAB), the file required to complete the app’s publication.

These resources are included in the main AAB package without issues, as long as they do not exceed 200 MB. If any file exceeds this limit, it cannot be included in the main package due to the size restrictions imposed by Google Play.

The good news is that, starting with .NET for Android 9, it is now possible to include resources in separate packages, known as asset packs. This allows you to upload additional asset packs of up to 2 GB, instead of being restricted to the 200 MB limit for the main package.

What key points should you keep in mind about the 2 GB limit?

  • The combined size of all your asset packs cannot exceed 2 GB.
  • You can create up to 50 individual asset packs.

This makes it easier to publish the app and offers more flexibility, so you don’t overload the main package while still complying with Google Play’s restrictions.

Basically, an asset pack is a collection of files containing your app’s resources.

⚠️ It is crucial to note that in .NET for Android, you must always set the Build Action property of each resource to “AndroidAsset” In contrast, when working with .NET MAUI, you should set it to “MauiAsset.” This sets the resources to be properly recognized as assets and included correctly in the asset packs.

How to Create an Asset Pack

To enable the use of asset packages in .NET for Android apps, the AndroidAsset item group now includes two new metadata attributes:

AssetPack: This attribute allows us to specify which asset package the asset should be included in. The default value is the main package, so if no other value is specified, the resource will be included there.

The name of the asset package must be constructed as follows: $(AndroidPackage).%(AssetPack).

For instance, if your package name is com.mycompany.myproject and the AssetPack name is myassets, the resulting asset package name will be com.mycompany.myproject.myassets.

DeliveryType: Determines when your assets are installed on the device. This property supports the following values:

  • InstallTime: Packs will be installed alongside the application. This type of pack can be up to 1 GB in size, but only one pack of this kind is allowed. (Default value.)
  • FastFollow: These packs are installed shortly after the application completes its installation. Keep in mind that the app must be fully installed before the assets can be used. This type of package can be up to 512 MB in size.
  • OnDemand: As the name suggests, this type manages downloads as needed, meaning the assets will only be downloaded to the device when the application explicitly requests them.

Let’s put this into context. Imagine you have a file named Demo.mp4 that is over 200 MB. How can you move it to its own resource package? Here’s an example of how to do it in code:

<ItemGroup> 
    <AndroidAsset Update="Assets/Demo.mp4"  AssetPack="myassets" /> 
</ItemGroup>

Let’s go over the details:

Inside the <ItemGroup> </ItemGroup> tags, add the AndroidAsset and specify the name of the AssetPack. ItemGroup tells the .NET Android build system the following:

  • Create a new asset pack, in this example named myassets.
  • Include the Demo.mp4 file inside “myassets” asset pack.
  • This asset pack will automatically be included in the AAB (Android App Bundle) when you publish the app.

Update: The Update attribute is used instead of Include. If a resource is already present in the project and you only want to update its configuration (for example, assign it to an asset pack), use Update. If you’re adding a new resource, use Include.

Can I Choose Which Assets Are Included in the Main App Bundle? 🤔

Yes! 🌟 You can easily specify which resources stay in the main app bundle and which are moved to an asset pack. This is particularly useful when you have a large number of resources but only a few are essential to be available immediately upon installing the app.

Resources included in the main app bundle are delivered during the initial installation. In contrast, files in asset packs are downloaded based on the configured DeliveryType (for example, FastFollow, OnDemand, etc.).

How to Do It

Here’s a practical example:

<ItemGroup> 
    <AndroidAsset Update="Assets/*" AssetPack="myassets" /> 
    <AndroidAsset Update="Assets/myfile.json" AssetPack="base" /> 
</ItemGroup>

Code Explanation:

  1. <AndroidAsset Update="Assets/*" AssetPack="myassets" />
    This specifies that all resources in the Assets folder will be placed in an asset pack named “myassets”.

  2. <AndroidAsset Update="Assets/myfile.json" AssetPack="base" />
    This indicates that the file myfile.json will be excluded from the myassets pack and placed in the main app bundle. This happens because the “base” value refers to the main package.

This approach gives you precise control over which resources are immediately available upon installation and which are delivered based on user needs.


Something You Need to Know About FastFollow ✍️

If your app utilizes a FastFollow asset pack, you need to verify its status and that the pack is fully installed before attempting to access its content.

You can achieve this easily:

➖ Add the Xamarin.Google.Android.Play.Asset.Delivery package to your project via NuGet Package Manager.

Xamarin.Google.Android.Play.Asset.Delivery - NuGet package

This package provides access to the AssetPackManager type, enabling you to query the location of asset packs in your application.

Let’s look at a code example:

using Xamarin.Google.Android.Play.Core.AssetPacks; 
var assetPackManager = AssetPackManagerFactory.GetInstance(this);

AssetPackLocation assetPackPath = assetPackManager.GetPackLocation("myfastfollowpack"); 
string assetsFolderPath = assetPackPath?.AssetsPath() ?? null; 
if (assetsFolderPath is null) 
{ 
    // FastFollow asset pack isn't installed. 
}

Here’s a review of what the code does:

  • The GetPackLocation method is used to query the location of the FastFollow asset pack.
  • This method returns an AssetPackLocation object.
  • If the AssetsPath method of the AssetPackLocation object returns null, it means the package has not been installed yet.
  • However, if it returns a value, it indicates the installation location of the package.

Downloading OnDemand Asset Packs

If your app utilizes an OnDemand asset pack, you need to download it manually. To do this, simply add the same Xamarin.Google.Android.Play.Asset.Delivery NuGet package mentioned in the previous explanation.

Declare the necessary variables:

using Xamarin.Google.Android.Play.Core.AssetPacks; 

IAssetPackManager assetPackManager; 
AssetPackStateUpdateListenerWrapper listener;

Create the event handler to monitor the download progress:

void Listener_StateUpdate(object? sender, AssetPackStateUpdateListenerWrapper.AssetPackStateEventArgs e) 
{ 
    var status = e.State.Status(); 
    switch (status) 
    { 
	    case AssetPackStatus.Downloading: 
		    long downloaded = e.State.BytesDownloaded(); 
		    long totalSize = e.State.TotalBytesToDownload(); 
		    double percent = 100.0 * downloaded / totalSize; 
		    Android.Util.Log.Info("Download Progress", $"Downloading {percent}%"); 
		    break; 
	    case AssetPackStatus.Completed: 
		    Android.Util.Log.Info("Download Complete", "Asset pack download finished."); 
		    break; 
	    case AssetPackStatus.WaitingForWifi: 
		    assetPackManager.ShowConfirmationDialog(this); 
		    break; 
    } 
}

Register the listener when starting and unregister it when pausing the application:

protected override void OnResume() 
{ 
    assetPackManager.RegisterListener(listener.Listener); 
    base.OnResume(); 
}
 
protected override void OnPause() 
{ 
    assetPackManager.UnregisterListener(listener.Listener); 
    base.OnPause(); 
}

Before attempting to download the asset pack, check its location with GetPackLocation to determine if it is already installed.

var assetPackPath = assetPackManager.GetPackLocation("myondemandpack");

string assetsFolderPath = assetPackPath?.AssetsPath() ?? null;

if (assetsFolderPath is null) 
{ 
    // If it is not installed, start the download 
    await assetPackManager.Fetch(new string[] { "myondemandpack" }).AsAsync<AssetPackStates>(); 
}

How to Test Asset Packs Locally

Testing asset packs locally is straightforward; just follow these steps and keep these points in mind:

1️. Use the Android App Bundle (AAB) format. This is important because, by default, .NET for Android uses the Android Package (APK) format for debugging.

2️. Open your .csproj file and add the following debugging properties.

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

This code:

  • Enables testing directly on the device with –local-testing.
  • Informs the BundleTool that all asset packs should be installed in a cached location on the device.
  • Configures IAssetPackManager to use a mock downloader, which utilizes the cache. (This allows you to test the installation of OnDemand and FastFollow asset packs in a debugging environment.)

References

This article was based on the official documentation and includes code examples from the main page to explain each implementation step in detail.


LeomarisReyes
About the Author

Leomaris Reyes

Leomaris Reyes is a Software Engineer from the Dominican Republic, with more than 5 years of experience. A Xamarin Certified Mobile Developer, she is also the founder of  Stemelle, an entity that works with software developers, training and mentoring with a main goal of including women in Tech. Leomaris really loves learning new things! 💚💕 You can follow her: Twitter, LinkedIn , AskXammy and Medium.

Related Posts

Comments

Comments are disabled in preview mode.