Decreasing an Android App’s Min SDK

There comes a point in any project where, for some reason, you have to do something you really don’t want to do. If you haven’t experienced this, you’re either very lucky or just bloody stubborn. The dreaded day came for me.

The initial app was supporting Lollipop (API 21) and onwards. We had to now support Jelly Bean (API 16).

My immediate response was that it will undoubtedly increase development time on future features and most definitely increase the complexity of bug fixes and maintenance. Alas, we suck it up and move forward.

At the time of starting and writing this, here is the Android version distribution according to Google.

A table and chart showing the distribution of Android version. See [here](https://developer.android.com/about/dashboards) for latest details

By these numbers, how accurate they are I’m not sure, we would be gaining just over 10% of the Android user base. This number was a small consolation to the daunting task ahead. Where to begin?!

At The Beginning

I don’t personally own any old Android devices anymore so it was time to create one using Android Studio’s emulators. I was going to use Genymotion as I have a license but their in-built Google Play Services installation mechanism does not work on these older versions and I couldn’t be bothered to manually install it, hence Android Studio.

via GIPHY

The approach I took was to change the minimum SDK version in the module build.gradle files to 16 and see what broke. It’s very hard to plan ahead for these kinds of changes. I had some guesses what would need fixing, for example the heavy use of vector drawables in the app, tinting these drawables and the use of only marginStart and marginEnd. 

Broken Build

Straight away the project wouldn’t build.

Caused by: com.android.tools.r8.errors.CompilationError: Program type already present: com.app.BuildConfig

After a quick Google it became pretty clear it was due to a conflict in my modules. After reviewing my modules I could see that the library module I have to share code between my other modules had the same package name as the main app module. This was not an issue before but it also didn’t fit right anyway so I was all too happy to oblige and change the package name of the library name. I won’t go into details but if you’re interested, see this handy SO post.

Margins/Padding

RTL support was added in version 17. This meant the old style margins needed to be supported otherwise there would be no margins at all. I started resolving this and documenting my process but it soon became apparent it should be a blog post on its own. Also, don’t forget to check your styles.

Vector Drawables

I use vector drawables a lot in my project. Actually, anywhere I can I try and remove any full size PNGs or other such image files. I love the amount of size you save in your APK/App bundle. This was great when supporting 5.0 and above. If only I knew…

via GIPHY

Unfortunately, these weren’t something I could catch at compile time. The way I found them was at runtime, when vector drawables were used in the layout. For example, I was using a vector drawable as the drawableEnd for a button:

<Button
----
android:drawableEnd="@drawable/ic_plus_grey"
----

Two things, drawableEnd is not supported in API < 17 but also ic_plus_grey is a vector.

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">

<path
android:fillColor="@color/border_dashed_grey"
android:pathData="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"/>
</vector>

This led to an android.view.InflateException. More specifically – Binary XML file line #61: Error inflating class Button. Pretty self explanatory but it doesn’t tell you the exact line it fails just its starting tag line. Well, from experience I knew it was the drawableEnd.

Assigning the drawable to my button programmatically in the Activity‘s onCreate resolves this.

btn_apdoc_add_file.setCompoundDrawablesWithIntrinsicBounds(
null,
null,
VectorDrawableCompat.create(resources,R.drawable.ic_plus_grey,null),
null)

For those of you of the Kotlin persuasion, here is a quick little extension for setting the top drawable as a vector. You can of course modify it for left etc.

fun TextView.setVectorTop(@DrawableRes drawableRes: Int) {
    setCompoundDrawablesWithIntrinsicBounds(
            null,VectorDrawableCompat.create(resources,drawableRes,null),null,null)
}

You can then use it like so

textView.setVectorTop(R.drawable.my_vector)

Finding these at runtime was not practical and I was guaranteed to miss some. Therefore I took a proactive approach and just did a “Find All” on XML tags like android:drawableTop and android:src.

VectorMaster

Used in the project was this library. This allows fine grained manipulation of vector drawables. I believe you can do a lot of things this library offers with AnimatedVectorDrawables but I can’t say for sure as I haven’t used them. In any case, I noticed where these views were used, the vectors were not displaying. This led me to this issue on the Github site. As it was quite old with no response, I decided the likelihood of it being resolved was slim. Added to the fact it was only used in two places anyway, I decided to remove it and replace with other methods of achieving the same result.

Firebase

In the app I use Firebase Database for instant messaging/chat. For some reason, this started failing with the following:

04-01 13:49:35.422 E/SynchroniserImpl$reactT: Failed to observe chat events
    com.google.firebase.database.DatabaseException: Firebase Database error: Permission denied
        at com.google.firebase.database.DatabaseError.toException(com.google.firebase:firebase-database@@16.1.0:229)
        at com.padoq.android.data.extensions.FirebaseExtKt$getRxFlowableChildSnapshot$1$childListener$1.onCancelled(FirebaseExt.kt:201)
        at com.google.firebase.database.core.ChildEventRegistration.fireCancelEvent(com.google.firebase:firebase-database@@16.1.0:97)
        at com.google.firebase.database.core.view.CancelEvent.fire(com.google.firebase:firebase-database@@16.1.0:40)
        at com.google.firebase.database.core.view.EventRaiser$1.run(com.google.firebase:firebase-database@@16.1.0:55)
        at android.os.Handler.handleCallback(Handler.java:615)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)

According to the Firebase documentation

Targets API level 16 (Jelly Bean) or later

Strange I hear you say. Well, that’s what I thought. Turned out it was due to the outdated Google Play Services on the emulator. I could not update it for some reason on the emulator so I ran up a Genymotion emulator, figured out how to install Google Play Services manually (again) and tested it there. It worked.

Text Alignment

As of API 17 there is an android:textAlignment attribute. Of course, I had to find all cases where this was present and also add the corresponding gravity attribute.

Lint error message

So again, we leverage Android Studio’s find feature and find where the offending attribute is used and also add the android:gravity tag:

android:textAlignment="textEnd"
android:gravity="end"

Notice the use of end here. The end and start values for gravity were added in 14. Result.

SpannableStringBuilder

As a result of a lint inspection, there was an error with SpannableStringBuilder. I was using the append(text:CharSequence!, what: Any!, flags: Int) method and this is only available from API level 21.

As a result, I leveraged previously written Kotlin extensions to make a compatible method:

fun SpannableStringBuilder.appendCompat(text: CharSequence, what: Any, flags: Int) : SpannableStringBuilder {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        this.append(text,what,flags)
    else {
        append(text)
        italiciseLast(text.toString())
    }
}

fun SpannableStringBuilder.italiciseLast(value: String) : SpannableStringBuilder =
        spanLastValue(StyleSpan(Typeface.ITALIC),value)

private fun SpannableStringBuilder.spanLastValue(what: Any,value:String) : SpannableStringBuilder {
    val start = toString().lastIndexOf(value)
    setSpan(what,start, start+value.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    return this
}

Activity Transitions

I started an activity so that it would animate in. However, this is only available from API 21 and above. Lint reports this as an error so it needed to be fixed. Here is the current code. The method that shows the error is makeSceneTransitionAnimation.

val options = ActivityOptions.makeSceneTransitionAnimation(activity)
                activity.startActivityForResult(MediaPickerActivity.launch(context,palette,
                        uet_lpci_comment_input.text.toString()),
                        REQUEST_CODE_EXTRA,options.toBundle())

So, I placed a conditional in there.

val intent = MediaPickerActivity.launch(context,palette,
                        uet_lpci_comment_input.text.toString())
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    val options = ActivityOptions.makeSceneTransitionAnimation(activity)
                    activity.startActivityForResult(intent, REQUEST_CODE_EXTRA, options.toBundle())
                } else activity.startActivityForResult(intent, REQUEST_CODE_EXTRA)

Lint

When you think you’re all done and ready, run lint. Just a quick ./gradlew lintAppDebug (or whatever your variant is) ad it will highlight any errors it may find. I did it and found a few I’d missed.

Conclusion

After what seemed initially like a daunting task, the reality was it wasn’t that difficult, just monotonous and time consuming.

Would I have catered for these older Android versions if I was to start again. No.

Up to this point, the project was aimed at 5.0 and above. The only reason I had to move backwards was because the business direction changed and resulted in a client pressure for us to support their devices.

I would much rather have a clean codebase than clutter it up with unnecessary compatibility code.

Join the Conversation

No comments

  1. Viagra Cialis Generika Forum Viagra Online Rezept [url=http://viaacost.com]generic viagra[/url] Opinion Propecia Finasteride

  2. Viagra Alle Erbe Naturale Cialis Generic [url=http://elc4sa.com]buy viagra online[/url] Dental Prophylaxis Amoxicillin Can I Take Sudafed With Amoxicillin Lasix Buy

Leave a comment

Your email address will not be published. Required fields are marked *