Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/android-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Android CI with Docker

on:
push:
branches: [ "main", "feat/*", "feature/*" ]
pull_request:
branches: [ "main" ]

jobs:
build-in-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build Docker Image
run: docker build -t android-ci .

- name: Run Gradle Build in Docker
# Mount current directory to /app
# Use the image built in previous step
# Run './gradlew build'
run: |
docker run --rm \
-v ${{ github.workspace }}:/app \
-w /app \
android-ci \
bash -c "chmod +x gradlew && ./gradlew build"

- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: app-debug
path: app/build/outputs/apk/debug/app-debug.apk
Binary file added .gradle/8.2/checksums/checksums.lock
Binary file not shown.
Binary file added .gradle/8.2/checksums/md5-checksums.bin
Binary file not shown.
Binary file added .gradle/8.2/checksums/sha1-checksums.bin
Binary file not shown.
Binary file not shown.
Empty file.
Binary file added .gradle/8.2/fileChanges/last-build.bin
Binary file not shown.
Binary file added .gradle/8.2/fileHashes/fileHashes.lock
Binary file not shown.
Empty file added .gradle/8.2/gc.properties
Empty file.
Binary file added .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
2 changes: 2 additions & 0 deletions .gradle/buildOutputCleanup/cache.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Wed Jan 21 10:42:29 CST 2026
gradle.version=8.2
Empty file added .gradle/vcs-1/gc.properties
Empty file.
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
FROM eclipse-temurin:17-jdk

# Set environment variables
ENV ANDROID_HOME /opt/android-sdk
ENV PATH ${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools

# Install necessary system packages
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
unzip \
git \
&& rm -rf /var/lib/apt/lists/*

# Download and install Android Command Line Tools
# Version: cmdline-tools;latest (checked from developer.android.com, typically part of commandlinetools-linux-*.zip)
# Using specific version suitable for stability.
# As of early 2024, cmdline-tools 11.0 is common, checking official link pattern.
# https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip is a recent one.
# We will use a reasonably recent valid URL.
ARG CMDLINE_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip

RUN mkdir -p ${ANDROID_HOME}/cmdline-tools \
&& curl -o cmdline-tools.zip ${CMDLINE_TOOLS_URL} \
&& unzip cmdline-tools.zip -d ${ANDROID_HOME}/cmdline-tools \
&& mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \
&& rm cmdline-tools.zip

# Accept licenses
RUN yes | sdkmanager --licenses

# Install Gradle 8.2
RUN curl -L https://services.gradle.org/distributions/gradle-8.2-bin.zip -o gradle.zip \
&& unzip gradle.zip -d /opt \
&& rm gradle.zip \
&& mv /opt/gradle-8.2 /opt/gradle
ENV PATH ${PATH}:/opt/gradle/bin

# Install SDK packages
# Based on build.gradle: compileSdk 34, minSdk 24
RUN sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"

WORKDIR /app
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# android-tutorial
# Android Build with Docker and GitHub Actions

This repository contains a sample Android project and a tutorial on how to set up a CI/CD pipeline using Docker and GitHub Actions.

## Contents

- [App Source Code](./app/)
- [Dockerfile](./Dockerfile)
- [GitHub Actions Workflow](./.github/workflows/android-docker.yml)
- [**TUTORIAL**: Step-by-Step Guide](./TUTORIAL.md)
79 changes: 79 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Tutorial: Android GitHub Actions in Docker

This tutorial guides you through setting up a CI/CD pipeline for your Android application using GitHub Actions and Docker. This ensures a consistent build environment and automates your build process.

## Prerequisites

- An Android project (already set up in this repository).
- Docker installed on your development machine (for local testing).
- A GitHub repository.

## 1. The Docker Build Environment

We use a `Dockerfile` to define an immutable build environment. This container includes:
- **OpenJDK 17**: The Java version required by the Gradle build.
- **Android Command Line Tools**: Necessary for managing the Android SDK.
- **Android SDK Components**: Specifically `platforms;android-34` and `build-tools;34.0.0` as defined in `app/build.gradle`.

### Key Dockerfile Sections

```dockerfile
# Base image
FROM openjdk:17-jdk-slim

# Install system dependencies
RUN apt-get update && apt-get install -y curl unzip git ...

# Download Command Line Tools
RUN curl -o cmdline-tools.zip ...

# Install SDK Packages
RUN sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
```

## 2. GitHub Actions Workflow

The workflow is defined in `.github/workflows/android-docker.yml`. It triggers on pushes and pull requests to the `main` branch.

### Workflow Breakdown

1. **Checkout Code**: Retrieves your project source code.
2. **Build Docker Image**: Builds the container defined in your `Dockerfile`.
3. **Run Build**: Mounts the project source code into the container and runs `./gradlew build`.

```yaml
- name: Run Gradle Build in Docker
run: |
docker run --rm \
-v ${{ github.workspace }}:/app \
-w /app \
android-ci \
./gradlew build
```

## 3. Running Locally

You can test the build environment locally using Docker before pushing to GitHub.

### Step 1: Build the Image

```bash
docker build -t android-ci .
```

### Step 2: Run the Build

```bash
docker run --rm -v $(pwd):/app -w /app android-ci ./gradlew assembleDebug
```

This command:
- `--rm`: Removes the container after it exits.
- `-v $(pwd):/app`: Maps your current directory to `/app` inside the container.
- `-w /app`: Sets the working directory to `/app`.
- `android-ci`: Uses the image you just built.
- `./gradlew assembleDebug`: Runs the Gradle task to build the debug APK.

## Conclusion

By containerizing your build environment, you eliminate "it works on my machine" issues and simplify your CI configuration. This setup forms the foundation for more advanced pipelines, including running tests and deploying to the Play Store.
37 changes: 37 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
plugins {
id 'com.android.application'
}

android {
namespace 'com.example.myapplication'
compileSdk 34

defaultConfig {
applicationId "com.example.myapplication"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
26 changes: 26 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">

<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
12 changes: 12 additions & 0 deletions app/src/main/java/com/example/myapplication/MainActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.myapplication;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// No layout required for this basic build test
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/mipmap/ic_launcher.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/mipmap/ic_launcher_round.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M54,54m-54,0a54,54 0 1,1 108,0a54,54 0 1,1 -108,0"/>
</vector>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">My Application</string>
</resources>
6 changes: 6 additions & 0 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/xml/backup_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- Rules for full backup -->
</full-backup-content>
5 changes: 5 additions & 0 deletions app/src/main/res/xml/data_extraction_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup />
<device-transfer />
</data-extraction-rules>
18 changes: 18 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:8.2.0"
}
}

plugins {
id 'com.android.application' version '8.2.0' apply false
}

task clean(type: Delete) {
delete rootProject.buildDir
}
18 changes: 18 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8

# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.enableJetifier=true
5 changes: 5 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
12 changes: 12 additions & 0 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "My Application"
include ':app'