Skip to content

Instantly share code, notes, and snippets.

@stkptr
Last active August 20, 2023 23:58
Show Gist options
  • Save stkptr/709d279212d4ee45133a8924724e2dc5 to your computer and use it in GitHub Desktop.
Save stkptr/709d279212d4ee45133a8924724e2dc5 to your computer and use it in GitHub Desktop.
Guide for getting a command line buildsystem for Android apps set up

Developing Android apps without Android Studio (Debian 12)

If you're looking to develop an Android app you might be led to Android Studio. It's the IDE recommended and developed by Google for the express purpose of Android development. It does what it needs to, but if you look at the system requirements you will see some alarming numbers. If you're using an older system, or simply don't want to use AndroidStudio, this is the guide for you.

Installing packages

We won't be using many apt packages due to permissions issues and incompatible versions. That being said:

$ sudo apt install openjdk-17-jdk sdkmanager kotlin

You will now need to download Gradle. Follow the guide here https://gradle.org/install/ and then edit your .bashrc to add:

export PATH="$PATH:/opt/gradle/gradle-<VERSION>/bin/"

Fill the <VERSION> with the Gradle version you installed, in my case 8.2.1.

Setting up the SDK

You'll need to pick where you want the SDK to go. It should be readable and writable by your user, otherwise sdkmanager won't be able to modify its settings files and can't accept its licenses. Let's assume your username is user and we'll install to ~/android-sdk. Add the following to your .bashrc:

export ANDROID_HOME=~/android-sdk

Then, in a new Bash shell (to refresh .bashrc), install the SDK:

$ mkdir ~/android-sdk
$ sdkmanager --sdk_root=/home/user/android-sdk "platform-tools" "build-tools;33.0.2" "platforms;android-33" "cmdline-tools;latest"

If you see it wanting to install to /opt then open a new Bash shell.

Accept the license agreements:

$ sdkmanager --licenses

Setting up the project

Initialize a Gradle project in an empty directory:

$ mkdir example
$ cd example
$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 4

Generate multiple subprojects for application? (default: no) [yes, no] no
Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 1

Project name (default: example): 
Source package (default: example): 
Enter target version of Java (min. 7) (default: 17): 
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] 


> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.2.1/samples/sample_building_kotlin_applications.html

BUILD SUCCESSFUL in 22s
2 actionable tasks: 2 executed

You can overwrite the .gitignore file with:

# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build

#built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/

# Local configuration file (sdk path, etc)
local.properties

# Windows thumbnail db
Thumbs.db

# OSX files
.DS_Store

# Android Studio
*.iml
.idea
.navigation
captures/
output.json

#NDK
obj/
.externalNativeBuild

Make sure to run gradle build and gradle run to ensure the Hello world! example works. If not, you might have misconfigured packages.

Restructuring for Android

Make sure to check for the newest versions of Android Build Gradle (https://developer.android.com/build/releases/gradle-plugin) and Kotlin Gradle (https://kotlinlang.org/docs/gradle-configure-project.html). The values in this example are up to date as of August 2023. In the root of your project directory add or overwrite build.gradle.kts with:

buildscript {
    repositories {
        mavenCentral()
        google()
    }

    dependencies {
        classpath("com.android.tools.build:gradle:8.1.0")
        classpath(kotlin("gradle-plugin", version = "1.9.0"))
    }
}

allprojects {
    repositories {
        mavenCentral()
        google()
    }
}

Add or overwrite app/build.gradle.kts with:

plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("kapt")
}

android {
    compileSdk = 30
    namespace = "example.example"
    buildFeatures {
        viewBinding = true
    }
    defaultConfig {
        applicationId = "example.example"
        minSdk = 15
        targetSdk = 31
        versionCode = 1
        versionName = "1.0"
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}


dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    implementation(kotlin("stdlib"))
}

Add or overwrite app/src/main/AndroidManifest.xml with:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:allowBackup="true"
        android:supportsRtl="true">
        <activity android:exported="true" android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Add or overwrite app/src/main/kotlin/example/MainActivity.kt (make sure it's .kt) with:

package example.example

import android.app.Activity
import android.os.Bundle
import android.widget.TextView
import android.view.Window

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        this.requestWindowFeature(Window.FEATURE_NO_TITLE)
        var textView = TextView(this)
        textView.setText("Hello world")
        setContentView(textView)
    }
}

A test file will have been autogenerated, delete it (app/src/test/kotlin/example/AppTest.kt), also delete the default main file (app/src/main/kotlin/example/App.kt).

You should now be able to run gradle assembleDebug to make a debug build and gradle assemble for a release build.

Note: for a real app, change the example and example.example fields in the configuration to the actual names/reverse domains of your app and organization.

Testing with Waydroid

Note: Waydroid requires about 1 GB of free RAM in order to start. If you notice it failing to start, make sure you have enough RAM. You may need to close some programs.

We'll use Waydroid in a Weston window to test the app. This is an alternative to avdmanager that uses LineageOS and is better integrated with Linux. Get the Waydroid repository set up according to https://docs.waydro.id/usage/install-on-desktops:

$ sudo apt install curl ca-certificates
$ curl https://repo.waydro.id | sudo bash

And install the necessary packages:

$ sudo apt install waydroid weston iptables

Initialize Waydroid:

$ sudo waydroid init

Note: If you are running in a virtual machine or if you have an Nvidia GPU, enable software rendering according to https://docs.waydro.id/faq/get-waydroid-to-work-through-a-vm

Then start up a Weston window:

$ weston -B x11-backend.so -s waydroidweston

Click the terminal icon in the upper left hand corner, and launch Waydroid (it will report a failure to start the clipboard manager):

$ waydroid show-full-ui

Once it loads, go back to the terminal that's in your project directory and install the apk (either the debug or the release):

$ waydroid app install app/build/outputs/apk/debug/app-debug.apk
$ waydroid app install app/build/outputs/apk/release/app-release-unsigned.apk # this will not actually install anything, as it is unsigned

You can then open it from the launcher in Waydroid, you should see a blank app window with Hello, world in the upper left hand corner.

You can close Waydroid using:

$ waydroid session stop

Acknowledgements

This guide is based on a prior guide written by Nicolas Goy/The Missing Bit: https://www.kuon.ch/post/2020-01-12-android-app/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment