Auto-Increment Build Numbers in Android: A Complete Guide with Custom APK Naming
Learn how to automatically increment version codes, customize output filenames, and access build information in your Android app using Gradle KTS and BuildConfig
The Problem
Manual version management in Android development is tedious and error-prone. Every time you build an APK or AAB, you need to:
- Remember to increment
versionCode - Manually rename output files with version numbers
- Keep track of build numbers across debug and release builds
- Update version info displayed in your app's settings
This tutorial shows you how to automate all of this using Gradle KTS (Kotlin DSL).
What You'll Build
By the end of this guide, you'll have:
- Auto-incrementing version codes that update on every build
- Custom APK/AAB names like
MyApp-v1.4-release-185.apk - BuildConfig fields to display version info in your app
- Persistent version tracking using a
version.propertiesfile
Step 1: Create the Version Properties File
Create a file named version.properties in your project's root directory (same level as your build.gradle.kts):
# Auto-managed version file
# This file is automatically updated when assembling builds
VERSION_CODE=7
Tip: Add this file to Git so version numbers stay consistent across your team.
Step 2: Set Up Gradle Helper Functions
Add these functions to your app/build.gradle.kts file (outside the android {} block):
// Helper function to get Git commit hash
fun getGitHash(): String {
return try {
Runtime.getRuntime()
.exec("git rev-parse --short HEAD")
.inputStream
.bufferedReader()
.readText()
.trim()
} catch (e: Exception) {
"unknown"
}
}
// Get the version properties file
fun getVersionPropertiesFile(): File {
return rootProject.file("version.properties")
}
// Read current version code
fun readVersionCode(): Int {
val versionFile = getVersionPropertiesFile()
if (!versionFile.exists()) {
versionFile.writeText("VERSION_CODE=7\n")
return 7
}
val props = Properties()
versionFile.inputStream().use { stream -> props.load(stream) }
return props.getProperty("VERSION_CODE", "7").toInt()
}
// Increment and save version code
fun incrementAndSaveVersionCode(): Int {
val versionFile = getVersionPropertiesFile()
val currentVersion = readVersionCode()
val newVersion = currentVersion + 1
versionFile.writeText("# Auto-managed version file\n")
versionFile.appendText("# This file is automatically updated when assembling builds\n")
versionFile.appendText("VERSION_CODE=$newVersion\n")
println("✓ Version code incremented: $currentVersion → $newVersion")
return newVersion
}
Don't forget to import at the top:
import java.util.Properties
Step 3: Configure Auto-Increment on Release Builds
Add this code before the android {} block. This logic ensures the version code only increments when you create a release build.
// Auto-increment version code on RELEASE assemble/bundle tasks only
val taskRequests = gradle.startParameter.taskNames
val isAssembleOrBundleReleaseBuild = taskRequests.any { taskName ->
taskName.contains("assembleRelease", ignoreCase = true) ||
taskName.contains("bundleRelease", ignoreCase = true)
}
if (isAssembleOrBundleReleaseBuild) {
// Increment version code for release builds only
incrementAndSaveVersionCode()
println("📦 Building RELEASE with incremented version code: ${readVersionCode()}")
} else {
println("📦 Building with version code: ${readVersionCode()} (no increment for debug builds)")
}
This detects when you run ./gradlew assembleRelease or bundleRelease and automatically increments the version code before the build starts. Debug builds will use the existing version code without changing it.
Step 4: Configure BuildConfig Fields
Inside your android { defaultConfig {} } block, add these BuildConfig fields:
android {
defaultConfig {
applicationId = "com.yourapp.package"
minSdk = 26
targetSdk = 34
versionCode = readVersionCode() // ← Auto-managed
versionName = "1.4"
// Expose version info to your app code
buildConfigField("String", "VERSION_NAME", "\"${versionName}\"")
buildConfigField("int", "VERSION_CODE", "${versionCode}")
buildConfigField("String", "BUILD_TYPE", "\"${name}\"")
buildConfigField("long", "BUILD_TIME", "${System.currentTimeMillis()}L")
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
}
}
Important: Enable BuildConfig in your buildFeatures:
buildFeatures {
buildConfig = true
}
Step 5: Customize APK and AAB Output Names
Add this code after the android {} block to customize output filenames:
androidComponents {
onVariants { variant ->
variant.outputs.forEach { output ->
val versionName = android.defaultConfig.versionName ?: "unknown"
val versionCode = android.defaultConfig.versionCode ?: 0
val buildType = variant.buildType ?: "unknown"
// Custom APK name: MyApp-v1.4-debug-185.apk
val outputFileName = "MyApp-v${versionName}-${buildType}-${versionCode}"
(output as com.android.build.api.variant.impl.VariantOutputImpl)
.outputFileName.set("${outputFileName}.apk")
}
}
// For AAB (Bundle) files
onVariants { variant ->
val versionName = android.defaultConfig.versionName ?: "unknown"
val versionCode = android.defaultConfig.versionCode ?: 0
val buildType = variant.buildType ?: "unknown"
tasks.register("rename${variant.name.replaceFirstChar { it.uppercase() }}Bundle") {
val bundleTask = tasks.named("bundle${variant.name.replaceFirstChar { it.uppercase() }}")
dependsOn(bundleTask)
doLast {
val buildDir = layout.buildDirectory.get().asFile
val bundleDir = File(buildDir, "outputs/bundle/${variant.name}")
bundleDir.listFiles { file -> file.extension == "aab" }?.forEach { aabFile ->
val newName = "MyApp-v${versionName}-${buildType}-${versionCode}.aab"
val newFile = File(aabFile.parentFile, newName)
if (aabFile.exists() && !newFile.exists()) {
aabFile.renameTo(newFile)
println("✓ Renamed bundle to: ${newFile.name}")
}
}
}
}
}
}
---
## Step 6: Create a Build Script (`make.sh`)
To simplify the build process, you can create a shell script named `make.sh` in your project's root directory. This script will provide easy commands for building, cleaning, and installing your app.
Create the `make.sh` file and make it executable:
```bash
touch make.sh
chmod +x make.sh
Add the following content to make.sh:
#!/bin/bash
set -e
function usage() {
cat <<EOL
Usage: $0 [option]
Options:
debug - Assemble the debug build and copy it to the builds/ directory.
release - Assemble the release build and copy it to the builds/ directory.
both - Assemble both debug and release builds and copy them to the builds/ directory.
show - List all .apk and .aab files in the build output directories.
copy - Copy all .apk and .aab files to the builds/ directory without building.
clean - Run 'gradle clean'.
install - Install the debug APK on a connected device.
uninstall - Uninstall the app from a connected device.
help - Show this usage information.
If no arguments are given, this help information is displayed.
EOL
}
function assemble() {
mkdir -p builds
if [[ "$1" == "debug" ]]; then
echo "Assembling Debug build..."
./gradlew assembleDebug
echo "Finding and copying Debug APK..."
DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name "*.apk" | head -n 1)
if [ -n "$DEBUG_APK_FILE" ]; then
cp "$DEBUG_APK_FILE" "builds/"
echo "Debug APK copied to builds/ directory."
else
echo "Debug APK not found."
fi
elif [[ "$1" == "release" ]]; then
echo "Assembling Release build..."
./gradlew assembleRelease
echo "Finding and copying Release AAB..."
RELEASE_AAB_FILE=$(find app/build/outputs/bundle/release -name "*.aab" | head -n 1)
if [ -n "$RELEASE_AAB_FILE" ]; then
cp "$RELEASE_AAB_FILE" "builds/"
echo "Release AAB copied to builds/ directory."
else
echo "Release AAB not found."
fi
else
echo "Building both debug and release variants..."
./gradlew assembleDebug assembleRelease
echo "Copying artifacts to builds/ directory..."
find app/build/outputs/apk/debug -name "*.apk" -exec cp {} builds/ \;
find app/build/outputs/bundle/release -name "*.aab" -exec cp {} builds/ \;
echo "Both versions built and copied. Output in builds/:"
ls -lh builds/
fi
}
function show_builds() {
echo "Showing found APKs and AABs:"
find app/build/outputs -name "*.apk" -o -name "*.aab"
}
function copy_builds() {
echo "Copying all builds to builds/ folder..."
mkdir -p builds
find app/build/outputs/apk -name "*.apk" -exec cp {} builds/ \;
find app/build/outputs/bundle -name "*.aab" -exec cp {} builds/ \;
echo "Copied files:"
ls -lh builds/
}
function clean_only() {
echo "Running gradle clean..."
./gradlew clean
}
function install_app() {
echo "Installing debug APK on connected device..."
APK_FILE=$(find app/build/outputs/apk/debug -name "*.apk" | head -n 1)
if [ -z "$APK_FILE" ]; then
echo "No debug APK found. Please build the debug APK first with './make.sh debug'."
exit 1
fi
echo "Found APK: $APK_FILE. Installing..."
adb install -r "$APK_FILE"
echo "Installation complete."
}
function uninstall_app() {
echo "Uninstalling app from connected device..."
# IMPORTANT: Replace with your app's actual package name
adb uninstall com.yourdev.easycctv
}
if [ $# -eq 0 ]; then
usage
exit 0
fi
case "$1" in
debug)
assemble "debug"
;;
release)
assemble "release"
;;
both)
assemble "both"
;;
show)
show_builds
;;
copy)
copy_builds
;;
clean)
clean_only
;;
install)
install_app
;;
uninstall)
uninstall_app
;;
help)
usage
;;
*)
echo "Unknown argument: $1"
usage
exit 1
;;
esac
Important: Remember to replace com.yourdev.easycctv in the uninstall_app function with your app's actual package name.
Step 7: Use BuildConfig in Your App
}
---
## Step 6: Use BuildConfig in Your App
Now you can access version info anywhere in your Kotlin code:
```kotlin
import com.yourapp.package.BuildConfig
class SettingsScreen : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Display version info
val versionText = "Version ${BuildConfig.VERSION_NAME}\nBuild ${BuildConfig.VERSION_CODE}"
val buildTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.format(Date(BuildConfig.BUILD_TIME))
val gitHash = BuildConfig.GIT_HASH
// Use in your UI
textView.text = """
Version: ${BuildConfig.VERSION_NAME}
Build: ${BuildConfig.VERSION_CODE}
Type: ${BuildConfig.BUILD_TYPE}
Commit: ${BuildConfig.GIT_HASH}
Built: $buildTime
""".trimIndent()
}
}
Example in Jetpack Compose:
@Composable
fun AboutScreen() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Version ${BuildConfig.VERSION_NAME}",
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Build #${BuildConfig.VERSION_CODE}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = "Commit: ${BuildConfig.GIT_HASH}",
style = MaterialTheme.typography.bodySmall
)
}
}
How It Works
- First Release Build: The system reads
VERSION_CODE=7fromversion.properties. - Build Detection: When you run
./make.sh release(which calls./gradlew assembleRelease), Gradle detects it's a release build. - Auto-Increment: The version code increments:
7 → 8. - File Update:
version.propertiesis updated withVERSION_CODE=8. - Build Proceeds: Your AAB is built with version code 8.
- Custom Naming: The output file is renamed to
EasyCCTV-v1.4-release-8.aab. - Debug Build: If you run
./make.sh debug, the version code remains8and is not incremented. The output isEasyCCTV-v1.4-debug-8.apk. - Next Release Build: The next time you run a release build, the version will become
9.
Testing Your Setup
Clean build:
./make.sh cleanBuild release AAB:
./make.sh releaseCheck the console output:
✓ Version code incremented: 185 → 186 📦 Building RELEASE with incremented version code: 186Find your APK:
app/build/outputs/apk/debug/MyApp-v1.4-debug-186.apkCheck the version file:
VERSION_CODE=186Build debug APK:
./make.sh debugCheck the console output (no increment):
📦 Building with version code: 186 (no increment for debug builds)Find your APK:
builds/MyApp-v1.4-debug-186.apk builds/MyApp-v1.4-release-186.aab
Pro Tips
Don't Increment on Sync or Debug
The auto-increment only happens during actual release builds (assembleRelease/bundleRelease), not during Gradle sync or debug builds. This prevents unnecessary version bumps for development builds.
Version Control
Commit version.properties to Git so your team shares the same version numbers:
git add version.properties
git commit -m "Update version to 186"
Reset Version Code
To reset or manually set the version code, just edit version.properties:
VERSION_CODE=100
Build Variants
This works for all build variants:
assembleDebug→MyApp-v1.4-debug-186.apkassembleRelease→MyApp-v1.4-release-186.apkbundleRelease→MyApp-v1.4-release-186.aab
Common Issues & Solutions
Issue: BuildConfig not found
Solution: Make sure you have buildConfig = true in buildFeatures:
buildFeatures {
buildConfig = true
}
Issue: Version doesn't increment
Solution: Ensure you're running a release build command (./make.sh release or ./gradlew assembleRelease), not just a debug build or a Gradle sync.
Issue: Git hash shows "unknown"
Solution: Make sure you're in a Git repository and Git is installed on your system.
Conclusion
You now have a professional version management system that:
- ✅ Automatically increments build numbers
- ✅ Creates descriptively named APK/AAB files
- ✅ Provides version info accessible in your app
- ✅ Works seamlessly with CI/CD pipelines
- ✅ Tracks versions across your development team
No more manual version management! 🎉
Related Topics
- Android Gradle Plugin API
- Semantic Versioning for Android
- CI/CD with GitHub Actions for Android
- Automated Release Management
- Gradle Build Optimization
Last updated: December 2024 | Works with Android Gradle Plugin 8.0+
Need an Android Developer or a full-stack website developer?
I specialize in Kotlin, Jetpack Compose, and Material Design 3. For websites, I use modern web technologies to create responsive and user-friendly experiences. Check out my portfolio or get in touch to discuss your project.


