Suggestions on how to support Multiple API Levels from a Single Application (APK)

If you want to support multiple API levels and provide newer features with your app is running on a higher level API and reduce the features when your app is running on lower level API's what is a common design pattern that is used to solve this with Mono for Android?

Example would being wanting your application to not only Run on 2.3.x, 3.x, 4.0.x, 4.1.x but also take full advantage of the full API available on the device we are installed on. I know we can obviously use reflection to do this, but we really don't want to use that, and are looking for a cleaner design that would be easier to maintain and support.

Posts

  • JonathanPryorJonathanPryor USXamarin Team Xamurai

    System.Reflection won't work, because members tend to disappear because of the linker. JNI would work, but is ugly.

    I would suggest runtime version checks, such as those used in SanityTests:

    if (((int)Android.OS.Build.VERSION.SdkInt) >= 11) {
    
        // Android 11+ _really_ doesn't want you to do networking from the main thread:
        //   http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html
        // I want to do so, so...
        var policy = new Android.OS.StrictMode.ThreadPolicy.Builder ()
            .PermitAll()
            .Build ();
        Android.OS.StrictMode.SetThreadPolicy (policy);
    }
    
  • RodneyFoleyRodneyFoley USBeta ✭✭

    Well for what we are doing Reflection has been working in a POC. Runtime versions checks don't really solve the problem however.

    Great I know what version I am on now what? I compiled the application at Gingerbread, but I need to use ICS and JB features as well. So the methods are not their to call.

    The main problem I can see is that you have to COMPILE your code to a specific version, which is usually the lowest version you want to support, so lets say Gingerbread. However you want to be able to take advantage of new features if you are running on Honeycomb, ICS, or Jelly Bean.

    I am new to Android development so maybe I am missing something obvious. As for mobile development I have mostly focused on iOS and there we don't have the same problems as users upgrade fairly quick and so we can just worry about the latest or latest-1. However because of the Android device vendors not providing upgrades or limiting the upgrades to a specific level you end up with are much larger segmentation of devices out there and especially in the enterprise that are latest-3 to latest.

  • RodneyFoleyRodneyFoley USBeta ✭✭
    edited November 2012

    @JonP

    As I said I am a newb for Android. I was aware of Minimum Android to Target testing on a projects application properties, and figured this controlled what API you are compiling against. However I just noticed that the Android Manifests tab (if you create a manifest) has a Target API level which is seems to be independent of the other.

    SO are you saying that I can set the Application minimum Android to target to 2.3 and then set the Android Manifests Target API level to 16 and then I will be able to compile against Jelly Bean but still install and run on a Gingerbread device? If so then that would explain about testing for API level before making certain calls.

    If what I stated is true please let me know if now please help me understand more about why checking API level in code would work then.

    Thanks!

  • JonathanPryorJonathanPryor USXamarin Team Xamurai

    @CreepyGnome: What IDE are you using? :-)

    SO are you saying that I can set the Application minimum Android to target to 2.3 and then set the Android Manifests Target API level to 16 and then I will be able to compile against Jelly Bean but still install and run on a Gingerbread device? If so then that would explain about testing for API level before making certain calls.

    Yes. Unfortunately, Visual Studio isn't currently capable of expressing those constraints; MonoDevelop can.

    Specifically, you'd want to set $(TargetFrameworkVersion) to Android 4.1 (allowing you to use API 16 members), while setting AndroidManifest.xml's //uses-sdk/@android:minSdkVersion attribute to 10 (Android 2.3).

    In Visual Studio, Project Properties > Application > Minimum Android to target sets $(TargetFrameworkVersion). The Android Manifest tab doesn't have a setting for the minimum Android version to support. Instead, you would need to hand-edit the AndroidManifest.xml file to add this entry.

    Alternatively, you can use MonoDevelop, which provides the Project Options > Build / Mono for Android Application > Minimum Android version entry, which sets the //uses-sdk/@android:minSdkVersion attribute.

    Well for what we are doing Reflection has been working in a POC

    I don't know what a "POC" is.

    The main problem I can see is that you have to COMPILE your code to a specific version, which is usually the lowest version you want to support, so lets say Gingerbread. However you want to be able to take advantage of new features if you are running on Honeycomb, ICS, or Jelly Bean.

    This is mostly correct, though the version you compile against can be higher than the minimum version you run on. Therein lies the problem: how do you ensure that you don't accidentally call a member that doesn't exist? Obviously you need runtime checks, but runtime checks are manual, and thus can accidentally miss things.

    This is where another feature comes in; note the surrounding #ifs in the SanityTests sample:

    #if __ANDROID_11__
    if (((int)Android.OS.Build.VERSION.SdkInt) >= 11) {
        // Android 11+ _really_ doesn't want you to do networking from the main thread:
        //   http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html
        // I want to do so, so...
        var policy = new Android.OS.StrictMode.ThreadPolicy.Builder ()
            .PermitAll()
            .Build ();
        Android.OS.StrictMode.SetThreadPolicy (policy);
    }
    #endif  // __ANDROID_11__
    

    When you specify a $(TargetFrameworkVersion), a set of C# defines are automatically provided, including __ANDROID__ and __ANDROID_<i>X</i>__ where X is from 1 to your target API level. For example, if you target Android 1.4, the C# defines __ANDROID_1__, __ANDROID_2__, __ANDROID_3__, and __ANDROID_4__ will be provided.

    This allows you to wrap your conditional code in the API level the members were introduced in, then set your $(TargetFrameworkVersion) to e.g. Android 2.3 (API-10). This allows the compiler to see if you're accidentally using members that don't exist in Android 2.3, and then you can reset your $(TargetFrameworkVersion) to Android 4.1 for release.

  • RodneyFoleyRodneyFoley USBeta ✭✭

    @JonP I am using Visual Studio 2012.

    POC = Proof of Concept

    Now that I think I understand the TargetFrameworkVersion and minSdkVersion versions I think I can work out something more elegant that a bunch of if checks for versions or using compiler directives. I think we could use a facade pattern and a strategy pattern. This way everything should be able to work via a facade that will be mapped at run time via a strategy pattern to a valid implementation. There still some work to do but I think it points us in the right direction.

    Thanks for you help!

  • JonathanPryorJonathanPryor USXamarin Team Xamurai

    Well for what we are doing Reflection has been working in a POC

    I don't know what a "POC" is.

    POC = Proof of Concept

    The reason Reflection was working in your POC is because you were only working with Debug builds. The linker isn't used in Debug builds, so all members are present. Once you build a Release build, the linker will be used, and the linker will remove all members that your code doesn't statically reference.

    Reflection doesn't work too well when the members you're expecting don't exist. :-)

  • RodneyFoleyRodneyFoley USBeta ✭✭

    Sorry wasn't more clear we were using JNI Reflection which does work in release builds, but doesn't matter it was something I didn't want to do as it seemed there should be a better way and you helped get us their thanks @JonP !

Sign In or Register to comment.