Migrating to the AIR Package Manager

A case study of moving an existing project to APM

Michael
Michael
Oct 21 · 9 min read

As with most AIR developers we support clients who have been using AIR in their applications for many years. They have an existing application code base structure and build systems in place that sometimes can make integrating new systems, like the AIR Package Manager (APM), a time consuming process.

In this guide we are going to run through a case study of converting one of these existing applications across to use the AIR Package Manager. Undoubtably this won’t match exactly your setup, but we wanted to show how easy it can be to migrate even a large application across to APM.

  • Goal
  • Approach
  • Process
  • Summary

Goal

Our goal with this initial integration is to utilise APM to manage the native extensions in use with this application. We will use APM to generate the required manifest additions and info additions in the application descriptor.

The application is a mobile application targeting iOS, Android and Amazon.

While this is a long document the process is very simple and there are only really 5 major steps.

Approach

The application has the following basic structure at the top level:

|____ AppLibs
|____ AppServerAPI
|____ amazon
|____ android
|____ common
|____ ios

Each of the platforms has a separate directory with a common source tree and several shared libraries (AppLibs / AppServerAPI).

The application descriptors are stored in each of the platform directories alongside the main application class for that platform.

|____ amazon
| |____ provisioning
| |____ src
| | |____ assets
| | | |____ // amazon specific assets
| | |____ App.as
| | |____ App-app.xml
|____ android
| |____ provisioning
| |____ src
| | |____ assets
| | | |____ // android specific assets
| | |____ App.as
| | |____ App-app.xml
|____ ios
| |____ provisioning
| |____ src
| | |____ assets
| | | |____ // ios specific assets
| | |____ App.as
| | |____ App-app.xml

The common directory contains the majority of the application source code along with all the ANEs for the application:

|____ common
| |____ ane
| | |____ // ANE files are here
| |____ src

All the ANEs are stored in one location, and each application may exclude some of them from the build as there are some slight differences between the platform implementations, particularly around supported advertising mediation networks. The ANEs being stored in a single location was just to make updating the extensions simpler however with apm we don’t need to worry about this so much and instead we will have an ane directory inside each of the platform directories. We simply make a small change in our IDE to include the [platform]/ane directory in each project instead of the common/ane directory.

Due to these slight differences between the platform we have decided to create a separate apm project for each platform. While this adds a slight complexity to updating (i.e. having to update multiple projects), it gives us the flexibility to control specifics of the packages used in each platform.

Depending on your setup you may choose to do this as well or if you don’t have any differences between the extensions packaged on the platforms it is simpler to use one project.

Process

Now for some magic! We can utilise the existing application descriptor and get apm to attempt to create a project for it. To do this we pass the path to the application descriptor to the init command. We run this in the platform directory, so lets move into the android directory and do this platform first:

$ apm init src/App-app.xml

Note we run this command in the android directory and have passed src/App-app.xml. This is so the project file is created at android/project.apm and not in the src directory.

This process will read the app xml and extract the application identifier, version and other information for the project definition and importantly, it will scan the extensions, attempt to identify matching packages and add the latest version to your project.

Creating new project definition file from application descriptor: App-app.xml
✓ FOUND : Package com.distriqt.Core@6.4.3
✓ FOUND : Package com.distriqt.GameServices@7.2.7
✓ FOUND : Package com.distriqt.Notifications@6.2.0
✓ FOUND : Package com.distriqt.ZipUtils@3.0.31
✓ FOUND : Package com.distriqt.InAppBilling@13.1.0
✓ FOUND : Package com.distriqt.Share@7.0.12
✓ FOUND : Package com.distriqt.Adverts@13.3.32
✓ FOUND : Package com.distriqt.Application@6.8.1
✗ WARNING : Could not locate package for extensionID com.distriqt.Debug
✓ FOUND : Package com.distriqt.SystemGestures@2.1.46
✓ FOUND : Package com.distriqt.ApplicationRater@6.1.0
✓ FOUND : Package com.distriqt.Image@5.1.79
✓ FOUND : Package com.distriqt.MediaPlayer@4.4.8
✓ FOUND : Package androidx.appcompat@1.2.0
✓ FOUND : Package androidx.browser@1.3.0
✓ FOUND : Package androidx.cardview@1.0.0
✓ FOUND : Package androidx.core@1.3.2
✓ FOUND : Package androidx.room@2.2.6
✓ FOUND : Package androidx.work@2.5.0
✓ FOUND : Package androidx.recyclerview@1.1.0
✓ FOUND : Package androidx.multidex@2.0.1
✓ FOUND : Package androidx.transition@1.4.0
✓ FOUND : Package androidx.vectordrawable@1.1.0
✓ FOUND : Package androidx.constraintlayout@1.1.3
✓ FOUND : Package com.android.installreferrer@1.0.0
✓ FOUND : Package com.google.android.material@1.0.0
✓ FOUND : Package com.google.code.gson@2.8.6
✓ FOUND : Package com.distriqt.playservices.Base@17.5.1
✓ FOUND : Package com.distriqt.playservices.Auth@19.0.0
✓ FOUND : Package com.distriqt.playservices.Identity@17.0.0
✓ FOUND : Package com.distriqt.playservices.Drive@17.0.0
✓ FOUND : Package com.distriqt.playservices.Games@21.0.0
✓ FOUND : Package com.distriqt.playservices.Ads@20.2.0
✓ FOUND : Package com.distriqt.playservices.AdsIdentifier@17.0.0
✓ FOUND : Package com.distriqt.playservices.Analytics@17.0.0
✗ WARNING : Could not locate package for extensionID com.distriqt.CustomResources
✓ FOUND : Package com.distriqt.IronSource@3.2.3
✓ FOUND : Package com.distriqt.ironsource.AdColony@1.0.122
✓ FOUND : Package com.distriqt.ironsource.AppLovin@1.0.117
✗ WARNING : Could not locate package for extensionID com.amazon.extensions.GameCircle
Processing identified packages
✗ SKIPPING : Dependency: com.distriqt.Core@6.4.3
✓ ADDING : Package: com.distriqt.GameServices@7.2.7
✓ ADDING : Package: com.distriqt.Notifications@6.2.0
✓ ADDING : Package: com.distriqt.ZipUtils@3.0.31
✓ ADDING : Package: com.distriqt.InAppBilling@13.1.0
✓ ADDING : Package: com.distriqt.Share@7.0.12
✓ ADDING : Package: com.distriqt.Adverts@13.3.32
✓ ADDING : Package: com.distriqt.Application@6.8.1
✓ ADDING : Package: com.distriqt.SystemGestures@2.1.46
✓ ADDING : Package: com.distriqt.ApplicationRater@6.1.0
✓ ADDING : Package: com.distriqt.Image@5.1.79
✓ ADDING : Package: com.distriqt.MediaPlayer@4.4.8
✗ SKIPPING : Dependency: androidx.appcompat@1.2.0
✗ SKIPPING : Dependency: androidx.browser@1.3.0
✗ SKIPPING : Dependency: androidx.cardview@1.0.0
✗ SKIPPING : Dependency: androidx.core@1.3.2
✗ SKIPPING : Dependency: androidx.room@2.2.6
✗ SKIPPING : Dependency: androidx.work@2.5.0
✗ SKIPPING : Dependency: androidx.recyclerview@1.1.0
✗ SKIPPING : Dependency: androidx.multidex@2.0.1
✗ SKIPPING : Dependency: androidx.transition@1.4.0
✗ SKIPPING : Dependency: androidx.vectordrawable@1.1.0
✗ SKIPPING : Dependency: androidx.constraintlayout@1.1.3
✓ ADDING : Package: com.android.installreferrer@1.0.0
✗ SKIPPING : Dependency: com.google.android.material@1.0.0
✗ SKIPPING : Dependency: com.google.code.gson@2.8.6
✗ SKIPPING : Dependency: com.distriqt.playservices.Base@17.5.1
✗ SKIPPING : Dependency: com.distriqt.playservices.Auth@19.0.0
✗ SKIPPING : Dependency: com.distriqt.playservices.Identity@17.0.0
✗ SKIPPING : Dependency: com.distriqt.playservices.Drive@17.0.0
✗ SKIPPING : Dependency: com.distriqt.playservices.Games@21.0.0
✗ SKIPPING : Dependency: com.distriqt.playservices.Ads@20.2.0
✗ SKIPPING : Dependency: com.distriqt.playservices.AdsIdentifier@17.0.0
✗ SKIPPING : Dependency: com.distriqt.playservices.Analytics@17.0.0
✗ SKIPPING : Dependency: com.distriqt.IronSource@3.2.3
✓ ADDING : Package: com.distriqt.ironsource.AdColony@1.0.122
✓ ADDING : Package: com.distriqt.ironsource.AppLovin@1.0.117

This application is a decent size so contains a number of extensions and dependencies.

You will note there are a couple of WARNING flags in there for extensions that it couldn’t find: com.distriqt.CustomResources , com.distriqt.Debug and com.amazon.extensions.GameCircle.

We will address these later in the section “Manually Add Extensions”.

The process has gone through and identified all the extensions and we can check with apm list:

$ apm list 
App@3.3.7 ~/work/application/android
├──com.distriqt.GameServices@7.2.7
├──com.distriqt.Notifications@6.2.0
├──com.distriqt.ZipUtils@3.0.31
├──com.distriqt.InAppBilling@13.1.0
├──com.distriqt.Share@7.0.12
├──com.distriqt.Adverts@13.3.32
├──com.distriqt.Application@6.8.1
├──com.distriqt.SystemGestures@2.1.46
├──com.distriqt.ApplicationRater@6.1.0
├──com.distriqt.Image@5.1.79
├──com.distriqt.MediaPlayer@4.4.8
├──com.android.installreferrer@1.0.0
├──com.distriqt.ironsource.AdColony@1.0.122
└──com.distriqt.ironsource.AppLovin@1.0.117

The process skips all packages that are dependencies of another package. So this doesn’t add things like the playservices and androidx libs, but unfortunately it has incorrectly skipped com.distriqt.IronSource . This won’t affect anything as we have the mediators there for IronSource which will trigger the main extension installation but it is convenient to list it separately incase we remove the mediators later. So we will manually install this package later. We do hope to improve this, but currently it’s just something to watch out for.

Next we will trigger an install.

$ apm install

This will download all the latest versions of the extensions and deploy them to the ane directory in our platform directory.

Once complete we will have

|____ android
| |____ ane
| |____ apm_packages
| |____ project.apm
| |____ provisioning
| |____ src
| | |____ assets
| | | |____ // android specific assets
| | |____ App.as
| | |____ App-app.xml

The ane directory contains all the downloaded extensions and the apm_packages directory is a cache directory used by apm . You can add this directory to your .gitignore (or equivalent), but do not delete it. apm will use this cache for certain operations and would need to download the package again if you delete it.

At this point lets add that incorrectly skipped ironSource package:

$ apm install com.distriqt.IronSource

Next we check the configuration to make sure we have correctly set any configuration items for the packages. We list the configuration parameters required by running apm project config :

apm project configphotoLibraryAddUsageDescription=Access to photo library is required to save images.
motionUsageDescription=
calendarsUsageDescription=
photoLibraryUsageDescription=Access to photo library is required to save images.
adMobIOSApplicationId=
adMobAndroidApplicationId=
playGamesAppId=XXXXXXXX
cameraUsageDescription=

There are several important pieces of information here, such as the AdMob application identifiers and the Play Games App Id. All these values would have been in your application descriptor previously but we need to inform apm of these values. To set a value:

apm project config set [NAME] [VALUE]

eg:

apm project config set adMobAndroidApplicationId ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY

Make sure you set values for all of the configuration:

adMobAndroidApplicationId=ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY
adMobIOSApplicationId=ca-app-pub-XXXXXXXXXXXXXXXX~ZZZZZZZZZZ
calendarsUsageDescription=Calendar usage description
cameraUsageDescription=Camera usage description
motionUsageDescription=Motion usage description
photoLibraryAddUsageDescription=Access to photo library is required to save images.
photoLibraryUsageDescription=Access to photo library is required to save images.
playGamesAppId=TTTTTTTTTTTT

Now we can address those extensions that aren’t packages and need to be handled manually. Firstly just copy these extensions the the android/ane directory.

apm will remove extensions from this location but only ones that are unused dependencies or directly uninstalled, so these extensions will never be affected by apm.

Two of the extensions, com.distriqt.CustomResources and com.distriqt.Debug are simple extensions that don’t require any manifest additions so once we have copied them across we are done with them.

However com.amazon.extensions.GameCircle has some manifest additions. In order to add manifest entries with apm we create a new xml file at config/android/AndroidManifest.xml . This file is used to specify any additional manifest entries you require for your application. This file should have the following contents:

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="30" />

</manifest>

I immediately place the uses-sdk tag in here to make sure we are specifying the correct minimum and target SDK for android. This is a good file to create even if you don’t have any other manifest additions, just to make sure you specify these SDK versions.

Now add the additions for the GameCircle ANE:

These will now be merged with the other extensions manifest additions automatically.

One thing to note here is the usage of the application package name in the manifest additions (air.application.id). You can leave that as is without any issues, however if you replace it with ${applicationId} then the merge process will insert it automatically for you and will make it easier for you to copy these additions into another project.

So now we have:

|____ android
| |____ ane
| |____ apm_packages
| |____ config
| | |____ android
| | | |____ AndroidManifest.xml
| |____ project.apm
| |____ provisioning
| |____ src
| | |____ assets
| | | |____ // android specific assets
| | |____ App.as
| | |____ App-app.xml

If this was an iOS application with info additions we would have added config/ios/InfoAdditions.xml with the following content (the usage description string is just an example):

<plist version="1.0">
<dict>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>Some description string.</string>

</dict>
</plist>

Now we have actually completed the conversion to apm for the android project! We can use apm to generate the application descriptor. This is important to do now as during the above process you will likely have updated the extensions to their latest versions which may have had manifest changes.

Before we do this, if you aren’t using version control, make a backup of your existing application descriptor. This process will modify it.

To generate the app descriptor we call apm generate app-descriptor and pass the location of the current application descriptor:

$ apm generate app-descriptor src/App-app.xml
Merging with supplied main manifest: config/android/AndroidManifest.xml
Android package name: air.application.id
✓ Android manifest merge
✓ iOS additions merge complete
✓ iOS entitlements merge complete
✓ App descriptor generated: /XXX/android/src/App-app.xml

At this point we open up our IDE and check that the application launches and runs as expected. The only change we have made to the build configuration is the relocation of the ane directory.

We then repeat this process with the other platforms.

Summary

In this case study we have shown how to migrate an existing project to apm.

  • Initialise using your existing application descriptor:
$ apm init src/App-app.xml
  • Install the packages:
$ apm install
  • Set configuration parameters
  • Handle any extensions that couldn’t be located as manually add extensions
  • Generate your application descriptor
$ apm generate app-descriptor src/App-app.xml

You now have an active apm managed project that you can use to simply add and update packages and most importantly, never again struggle with manifest additions!

Thanks for reading and if you have any questions you can reach out at the apm discussion forum.

Native Extensions Articles and News