DeCODE logo

How to deal with the pace of changing the Android SDK?

In the modern day of mobile development, where everything is modular, and the updates is shipped rapidly, you can easily be lost in the process of manual updates of your dependencies. While there exists solutions like [Renovate] and [Dependabot], the best application of these solutions are to maintain the external dependencies and libraries. But how can we make sure, that if Android compileSDK and targetSDK are up-to-date, if the application logic is not shifting that often, as the SDK'-s? Well, today we would like to share our journey with this, welcome aboard!

Determining the source of truth for Android SDK versions

Upon deep investigation and the Android source code, our team have found, that SDKManager is fetching data from https://dl.google.com/android/repository/repository2-1.xml. This XML contains a lot of information, but the most important to us is "Android SDK Platform XX", where XX - is the Android SDK version. Exactly this information we will need to determine if our application is behind the official SDK's provided by Android development team. For ease of use, there's a docker image already with necessary steps done: ghcr.io/fallenangel97/android-fetcher:master

Process of bumping the SDK'-s in the application code

Now, when we know, where to look at the most up-to-date Android SDK version, we need to find a way how to specify the logic to update necessary API's. In order to do that, we will create scripts folder inside the root folder of the necessary Android application. Now, whenever the docker image will detect versions of Android SDK, it will send them to that folder as an arguments, so that we can read them and construct the logic for updating necessary files.

Example of such script can look like this:

#!/bin/sh
# filename: <project-root>/scripts/bump-android-version.sh

base_path=$(dirname -- "$0")
node $base_path/js/bump-version.js $1
const { execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
const path = require('path');

const [,,remoteVersions] = process.argv;

const parsedVersions = JSON.parse(remoteVersions);
const latestAndroidVersion = Math.max(...parsedVersions);

const fileContents = readFileSync(path.join(__dirname,'../../app/build.gradle')).toString();

const targetSdkRegex = /targetSdkVersion ([0-9]+)/;
const compileSdkRegex = /compileSdkVersion ([0-9]+)/;

const match = targetSdkRegex.exec(fileContents);

if (match.length > 0) {
	const [,version] = match;

	if (parseInt(latestAndroidVersion) > parseInt(version)) {
		console.log('Replacing the android version...');
		const newText = fileContents
			.replace(targetSdkRegex, "targetSdkVersion " + latestAndroidVersion)
			.replace(compileSdkRegex, "compileSdkVersion " + latestAndroidVersion);
		writeFileSync(path.join(__dirname, '../../app/build.gradle'), newText);
		execSync('git add .');
		execSync(`git checkout -b bump-android-${latestAndroidVersion}`);
		execSync(`git commit -m 'Bumped targetSdkVersion and compileSdkVersion to ${latestAndroidVersion}'`);
		execSync(`git push -o merge_request.create -o merge_request.merge_when_pipeline_succeeds origin HEAD`);
	} else {
		console.log('The version is up to date, exiting');
	}
}

Of course, you should adjust the scripts up to your requirements.

Putting everything together as a periodic task

We'll use a wonderful docker-cron project in order to connect our pieces together. Let's run our process for bumping Android version every 8 hours, so we can define the logic for swarm-cronjob like this:

version: "3.8"

services:
  android_sdk_bumper:
    image: "ghcr.io/fallenangel97/android-fetcher:master"
    healthcheck:
      disable: true
    environment:
      - REPOSITORIES="<path-to-your-git-repo>"
    volumes:
      - "/root/.ssh:/root/.ssh"
    deploy:
      mode: replicated
      replicas: 0
      labels:
        - "swarm.cronjob.enable=true"
        - "swarm.cronjob.schedule=0 */8 * * *"
      restart_policy:
        condition: none

Make sure, that you have generated SSH keys and added them in your Git service in order for the "git clone" to work properly, otherwise it might fail

You are welcome to create a PR for the project here: https://github.com/FallenAngel97/android-list-fetcher Photo by Jan Kopřiva on Unsplash