diff --git a/Documentation/guides/DynamicFeatures.md b/Documentation/guides/DynamicFeatures.md new file mode 100644 index 00000000000..161279e3b9d --- /dev/null +++ b/Documentation/guides/DynamicFeatures.md @@ -0,0 +1,461 @@ +# Android Dynamic Feature Delivery + +_NOTE: This document is very likely to change, as the the implementation +of Dynamic Features matures._ + +Android Dynamic Features are a way of splitting up your application +into smaller parts which users can download on-demand. This can be +useful in scenarios such as Games or apps with low usage parts. +An example would be a Support module which does not get used very +often, rather than including this code in the main app it could be +a dynamic feature which is downloaded when the user requests support. +Alternatively think of Game levels in a game. On first install only +the first few levels are installed to save download size. But later +the additional levels can be downloaded while the user is not playing +the game. + +Dynamic Features can ONLY be used when targeting the Google Play +Store and using `PackageFormat` set to `aab`. Using `apk` will +NOT work. + +## Xamarin Dynamic Asset/Feature Delivery + +The Xamarin implementation of this will start by focusing on asset +delivery. This is where the `AndroidAsset` items can be split out +into a separate module for downloading later. + +_NOTE: In the future we plan to support feature or code delivery +however there are significant challenges to supporting that in our +runtime. So this will be done after the initial rollout of asset +delivery._ + +The new Dynamic Feature Module will be based around a normal +Xamarin Android class library. The kind you get when calling + +``` +dotnet new androidlib +``` + +Although we plan to provide a template. The new Module will generally +be nothing different from a normal class library project. The +current restrictions are that it only contains `AndroidAsset` items. +No `AndroidResource` items can be included and any C# code in the +library will NOT end up in the application. + +The one additional file is a special `AndroidManifest.xml` file which +will be generated as part of the module build process. This manifest +file needs a few special elements which need to be provided by +the following MSBuild properties. + +* FeatureSplitName: (string) The name of the feature. You will use this in code to + install the feature. Defaults to `ProjectName`. +* IsFeatureSplit: (bool) Defines if this feature is a "feature split". Defaults to `true`. +* FeatureDeliveryType: (Enum) The type of delivery mechanism to use. Valid values are + OnDemand or InstallTime. Defaults to InstallTime. +* FeatureType: (Enum) The type of feature this is. Valid values are + Feature or AssetPack. Defaults to Feature. +* FeatureTitleResource: (string) This MUST be a valid @string which is present in your + application strings.xml file. For example `@strings/assetsfeature`. + Note the actual value of the resource can be 50 characters long and can be localized. + It is this resource which is used to let the users know which feature is being + downloaded. So it should be descriptive. + This Item does NOT have default and will need to be provided. + +These properties will have default values based on things like the Project name. + +Here is a example of an Asset Feature Module for .net 6. + +```xml + + + net6.0-android + + + assetsfeature + Asset + true + OnDemand + + +``` + +This is defining an OnDemand Asset Delivery package. +The follow code is defining a Dynamic Feature Module. + +```xml + + + net6.0-android + + + examplefeature + Feature + true + OnDemand + @strings/examplefeature + + +``` + +In order to reference a feature you can use a normal `ProjectReference` MSBuild +Item. We will detect the use of `FeatureSplitName` in each project and will +remove that project from the normal build process. + +Features will then build later in the build process, after the main app +has built its base resource package but before the linker runs. + +As part of the build system all `Features` will include a `Reference` to +the main application `Assembly`. So there is no need to add any `PackageReference` +items to any NuGet which is already being used by main app as they will be +automatically referenced when we build the feature. + +## Downloading a Dynamic Feature at runtime + +In order to download features you need to reference the +`Xamarin.Google.Android.Play.Core` Nuget Package from your main application. +You can do this by adding the following Package References. + +```xml + + +``` + +Note that `Xamarin.GooglePlayServices.Base` is also required. The `Version` numbers +here will change as new versions are being released all the time. The minimum +required `Xamarin.Google.Android.Play.Core` is `1.10.2`. + +This is because some of the classes we need to use in the `Play.Core` API use Java +Generics. The Xamarin Android Binding system has a problem with these types of +classes. So some of the API will either be missing or will produce build errors. +In `Xamarin.Google.Android.Play.Core` `1.10.2` we added a set of `Wrapper` classes +which expose a non Generic API in C# which will allow users to use the required +`Listener` classes. + +### Installing Asset Packs + +If you have created an `InstallTime` asset pack, there is no additional work to do. +The pack will be installed at the same time the app is installed. As a result the +`Assets` will be available via the normal `AssetManager` API. + +If you create an `OnDemand` asset pack, you will need to manually start the installation +and monitor its progress. To do this you need to use the `IAssetPackManager`. This can +be created using + +```csharp +IAssetPackManager manager = AssetPackManagerFactory.GetInstance (this); +``` + +You can then use the following code to check if the Asset Pack was installed. If it was +not, then you can start the installation with the `IAssetPackManager.Fetch` method. + +```csharp +var location = assetPackManager.GetPackLocation ("assetsfeature"); +if (location == null) +{ + assetPackManager.Fetch(new string[] { "assetsfeature" }); +} +``` + +Note the argument passed in there is the same as the `FeatureSplitName` MSbuild property. +In order to monitor the progress of the download google provided a `AssetPackStateUpdateListener` +class. However this class uses Generics and if you use it directly it will result in +java build errors. So the `Xamarin.Google.Android.Play.Core` version `1.10.2` Nuget provides +a `AssetPackStateUpdateListenerWrapper` class which exposes a C# event based API to make +things easier. + +```csharp +listener = new AssetPackStateUpdateListenerWrapper(); +listener.StateUpdate += (s, e) => { + var status = e.State.Status(); + switch (status) + { + case AssetPackStatus.Pending: + break; + // more case statements here + } +}; +``` + +The `StateUpdate` event will allow you to get the download status if any Asset Packs which +are being downloaded. +In order for this listener to work we need to register it with the `IAssetPackManager` we +created earlier. We do this in the `OnResume` and `OnPause` methods in the activity. + +To get the `manager` object to use this listener we need to call the `RegisterListener` method. +We also need to call the `UnregisterListener` as well when we are finish or if the app is paused. +Because we are using a wrapper class we cannot just pass our `listener` to the `RegisterListener` +method directly. This is because it is expecting a `AssetPackStateUpdateListener` type. +But we do provide a property on the `AssetPackStateUpdateListenerWrapper` which will give +you access to the underlying `AssetPackStateUpdateListener` class. So to register you can +use the following code. + +```csharp +protected override void OnResume() +{ + // regsiter our Listener Wrapper with the IAssetPackManager so we get feedback. + manager.RegisterListener(listener.Listener); + base.OnResume(); +} + +protected override void OnPause() +{ + manager.UnregisterListener(listener.Listener); + base.OnPause(); +} +``` + +With the `listener` registered you will now get status updates during a download. +Here is a sample activity. + +```csharp +using Android.App; +using Android.OS; +using Android.Runtime; +using Android.Widget; +using Java.Lang; +using Xamarin.Google.Android.Play.Core.AssetPacks; +using Xamarin.Google.Android.Play.Core.AssetPacks.Model; +using Android.Content; + +namespace DynamicAssetsExample +{ + [Activity(Label = "@string/app_name", MainLauncher = true)] + public class MainActivity : Activity + { + IAssetPackManager manager; + AssetPackStateUpdateListenerWrapper listener; + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + // Set our view from the "main" layout resource + SetContentView(Resource.Layout.activity_main); + var button = FindViewById