An Example of using Dynamic Asset Delivery with Xamarin Android.
The solution is made up of 4 projects.
- OnDemand/OnDemandExample. This is the main Xamarin.Android application for the OnDemand Example.
- OnDemand/AssetsFeature. This is the project for the Asset only dynamic feature.
- InstallTime/InstallTimeExample. This is the main Xamarin.Android application for the InstallTime Example.
- InstallTime/AssetsFeature. This is the project for the Asset only dynamic feature.
There is a global.json file which pulls in the Microsoft.Build.NoTargets SDK
which we use for the "Feature" projects. This is so they do not produce an assembly.
The way the build works is we have a custom set of targets in the Targets\DynamicFeature.targets
file. The two targets are BuildAssetFeature and PackageAssets. The first target
is build as part of the main Xamarin.Android application. It is responsible for
finding "Feature" projects and then calling the PackageAssets target on each of
them.
It does this by looking for ProjectReferences which have the DynamicFeature metadata
set to true.
<ProjectReference Include="..\AssetsFeature\AssetsFeature.csproj">
<Project>{EABACE4D-E999-48FA-B417-ECD29C8AB6E5}</Project>
<Name>AssetsFeature</Name>
<!-- These next two items are REALLY IMPORTANT!!!! -->
<DynamicFeature>true</DynamicFeature>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
NOTE: Feature references should ALSO have the ReferenceOutputAssembly set to false. This
stops the Xamarin.Android packaging system from including it in the base package.
The PackageAssets target will run for each "feature" project. It is responsible for
using aapt2 to package up the files in the Assets folder (including subdirectories)
and generating a "Feature" package/zip file.
The outputs of PackageAssets are then passed back to BuildAssetFeature which then includes
those zip files in the @(AndroidAppBundleModules) ItemGroup. These will then be included
in the final aab file as dynamic features.
To create a feature you need a few things. The first is a Microsoft.Build.NoTargets project
which imports the Targets\DynamicFeatures.targets file. See OnDemand\AssetFeature\AssetFeature.csproj or InstallTime\AssetFeature\AssetFeature.csproj for an example.
Next you need an AndroidManifest.xml file. This is where you define how they "Feature" will be
installed via the dist:module and dist:delivery elements.
IMPORTANT: The dist:type MUST be set to asset-pack!
The package attriute on the manifest element MUST match the value of the main application.
And finally the split value is the name which the "Feature" will be called in the final package
and when you install via the AssetPackManager API. This is not user facing.
Install time asset packs as installed along side your app during the installation process. There is no additional work needed to download them.
Accessing these assets can be done via the normal Assets property on your main
Activity.
var stream = Assets.Open ("Foo.txt");
You need to use the AssetPackManager to install on-demand asset packs, this is available in the
Xamarin.Google.Android.Play.Core" NuGet Package. However due to the API using Java Generics you cannot
use all the AssetPackManager directly. The Xamarin.Google.Android.Play.Core version 1.10.2.3 contains
a non generic Java Wrapper around the AssetPackManager which allows us to
capture events from the OnStatusUpdate method. This lets us get feedback while we are installing
"Features".
As a result the code is slightly different from the Java code we see in the Google examples.
The first thing we need is a custom Application class which derives from SplitCompatApplication.
This is so Google.Play.Core can hook into the application lifecycle.
Next we need to define the following in the Activity where we want to surface installing features.
IAssetPackManager manager;
AssetPackStateUpdateListenerWrapper listener;The IAssetPackManager is the C# version of the AssetPackManager Java interface. The
AssetPackStateUpdateListenerWrapper class is the C# version of our AssetPackStateUpdateListenerWrapper
which comes from the Xamarin.Google.Android.Play.Core. This is the class which wraps the Java AssetPackStateUpdateListener from the generic based google API and exposes a non generic API.
We now need to create both of these classes in the OnCreate method. This can probably be done elsewhere,
but for the example we do this in OnCreate.
manager = AssetPackManagerFactory.GetInstance (this);
// Create our Wrapper and set up the event handler.
listener = new AssetPackStateUpdateListenerWrapper();
listener.StateUpdate += Listener_StateUpdate;The code that creates the AssetPackStateUpdateListenerWrapper is straight forward. It creates
the class and then hooks up the StateUpdate event.
For the StateUpdate event to work we need to hook the AssetPackStateUpdateListenerWrapper to
the AssetPackManager. This is done in the OnResume override method
protected override void OnResume()
{
// regsiter our Listener Wrapper with the AssetPackManager so we get feedback.
manager.RegisterListener(listener.Listener);
base.OnResume();
}and we need to UnregisterListener on OnPause.
protected override void OnPause()
{
manager.UnregisterListener(listener.Listener);
base.OnPause();
}These two snippets are where things differ from the Java examples you will see. Those will
create an AssetPackStateUpdateListener class directly and pass that into the RegisterListener
and UnregisterListener. But because we had to use our wrapper we need to pass in the
listener.Listener value. This is the only real different between C# and Java code.
Finally you need to hook into then OnActivityResult and handle the case where user
confirmation is required. See the MainActivty.cs for an example.
With all that in place you can use the following code to install an asset pack.
var location = assetPackManager.GetPackLocation ("assetsfeature");
if (location == null)
{
assetPackManager.Fetch(new string[] { "assetsfeature" });
}