Setting Up an Android project using Gradle without using an IDE
Modern Android development offers many tools to streamline setup, but understanding the build system is crucial for debugging, customizing, and scaling projects efficiently. While Android Studio provides an easy wizard to generate projects, blindly relying on it can be limiting. This article walks through the minimal manual setup of an Android project using Gradle, focusing on clarity and control over generated files. This guide provides a deeper understanding of how Gradle structures Android projects, empowering developers to make informed configuration decisions.
The Evolution of Android Build Systems: From Ant to Gradle
Android development has undergone significant transformations since its inception in 2008. The tools and languages used to build Android applications have evolved to improve developer productivity, project scalability, and build performance.
Early Days: Apache Ant and Eclipse In the early years of Android, developers primarily used Eclipse IDE with the Android Development Tools (ADT) plugin. The build system was based on Apache Ant, a tool that required XML-based configuration and offered limited flexibility.
The Shift to Gradle and Android Studio In 2013, Android Studio was introduced as the official IDE, replacing Eclipse. Alongside it, Google adopted Gradle as the default build system, bringing:
- Declarative build scripts written in Groovy (later Kotlin).
- Dependency management via Maven repositories.
- Improved build performance with incremental compilation and parallel execution.
- Customizable build variants (e.g., debug, release, free version, pro version).
The Adoption of Kotlin For nearly a decade, Java was the primary language for Android development. In 2017, Google officially announced Kotlin as a first-class language for Android development. In 2019, Google announced that the Kotlin programming language is the preferred language for Android developers.
Prerequisites
Before setting up the project, ensure that the necessary tools are installed. This guide assumes a development environment with a Java Development Kit (JDK), Gradle, and the Android Software Development Kit (SDK).
Java Development Kit (JDK)
Android development requires a Java runtime environment. For this setup, Eclipse Temurin JDK is used, but any compatible JDK version should work. The recommended version is JDK 17, as it aligns with the requirements of modern Android projects.
- Download Eclipse Temurin JDK: https://adoptium.net/temurin/releases/
-
Ensure that Java is installed by running:
java -version
This should return the installed Java version. If Java is not installed or an incorrect version is displayed, update the system path or reinstall the JDK.
Gradle
Gradle is the build automation tool used to compile and package the Android application. While Gradle can be installed globally, this guide recommends using a project-specific Gradle wrapper, which ensures version consistency across different development environments.
- Download Gradle: https://gradle.org/install/
-
Verify installation by running:
gradle -v
-
Install the wrapper in your project folder by running:
gradle wrapper --gradle-version 8.12.1
Android SDK
The Android SDK provides the necessary tools, libraries, and emulator support for developing and testing Android applications. There are two primary installation methods:
- Using Android Studio (recommended) – Android Studio includes an SDK manager for easy installation.
- Using the standalone command-line tools – If Android Studio is not required, the SDK can be installed separately.
- Download Android Studio or SDK Tools: https://developer.android.com/studio#command-tools
- After installation, verify the SDK location:
- On macOS:
$HOME/Library/Android/sdk
- On Linux:
$HOME/Android/Sdk
- On Windows:
%LOCALAPPDATA%\Android\Sdk
- On macOS:
Optionally, set the ANDROID_SDK_ROOT
environment variable to point to the correct SDK path.
export ANDROID_HOME=/path/to/android/sdk
export PATH=$ANDROID_HOME/platform-tools:$PATH
adb devices
Project Structure
The folder structure for the project is organized as follows:
project-root/
├── .gitignore
├── gradle.properties
├── local.properties
├── settings.gradle.kts
├── gradle
│ └── libs.versions.toml
└── src
└── androidApp
├── build.gradle.kts
└── src
└── main
├── AndroidManifest.xml
├── kotlin
│ └── MainActivity.kt
└── res
├── drawable
│ ├── ic_launcher_background.xml
│ └── ic_launcher_foreground.xml
├── layout
│ └── activity_main.xml
├── mipmap-anydpi-v26
│ └── ic_launcher.xml
└── values
├── strings.xml
└── styles.xml
Information
Android projects usually don't have modules under a src
directory. If you preferr a more standard layout, put your modules
in the root directory. Adopting a src layout aligns the project structure with other languages like dotnet, c++ and python.
Top level files
File | Purpose |
---|---|
settings.gradle.kts | Defines project-level settings, including which modules should be included. |
gradle.properties | Stores global Gradle properties, like JVM arguments, versioning, or flags. |
local.properties | Stores local machine-specific settings, such as the Android SDK path. This file is not meant for version control. |
.gitignore | Defines files and directories that should not be tracked by Git, such as build artifacts, .gradle/, and local.properties. |
gradle/libs.versions.toml | Version catalog file that centralizes dependency versions. |
settings.gradle.kts
This file configures where plugins are fetched from, where dependencies are fetched from and finally includes all modules that make up this project.
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "<PROJECT NAME>"
include(":src:androidApp")
gradle.properties
This file declares global properties that can later be accessed in your build scripts as project.properties["NAME"]
. I
just use it to enable AndroidX which is a more modern support library for Android.
android.useAndroidX=true
local.properties
This optional file declares settings for the current user. This file should not be checked into version control.
sdk.dir=/path/to/Library/Android/sdk
You can also set the environment variable ANDROID_SDK_ROOT
.
libs.versions.toml
The libs.versions.toml file centralizes dependency versions, ensuring consistency across different modules. Unlike manually defining versions inside build.gradle.kts, this method reduces redundancy and makes dependency upgrades easier.
[versions]
android-compileSdk = "35"
android-minSdk = "24"
android-targetSdk = "35"
[libraries]
google-material = { group = "com.google.android.material", name = "material", version = "1.12.0" }
[plugins]
android-application = { id = "com.android.application", version = "8.8.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.1.10" }
The contents of this file is referenced in other parts of the build. For instance, the version fields are used when setting up the android build.
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
This ensures that we use the same SDK values throughout the build.
Libraries are imported in the modules like this:
implementation(libs.google.material)
As you can see, dashes are normalized into dots.
It is also possible to reference versions directly.
[versions]
material-version = "1.12.0"
[libraries]
google-material = { group = "com.google.android.material", name = "material", version.ref = "material-version" }
prefer avoiding extra indirection, even if it means updating a few duplicate versions manually.
Android module files
File | Purpose |
---|---|
build.gradle.kts | The main Gradle build script for the root project. It may define dependencies, repositories, or global Gradle settings. |
kotlin/MainActivity.kt | The entry point of the Android app. This is the main activity that gets launched. |
res/drawable/ | Stores imageassets (icons, backgrounds, etc.). |
res/layout/activity_main.xml | Defines the UI layout for MainActivity. |
res/values/strings.xml | Stores app text values (e.g., "Welcome to Trailmaker"). |
res/values/styles.xml | Defines themes and styles for UI elements. |
Modules are defined under the src
directory. Each module is included in the top level settings.gradle.kts
using an include
statement.
Android module
I tend to organize my modules under a src
folder to keep things similar across different types of projects. This might not be the most common so if you prefer, put your modules in the root folder and adjust the includes accordingly.
build.settings.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
kotlin {
jvmToolchain(17)
sourceSets {
main {
dependencies {
implementation(libs.google.material)
}
}
}
}
android {
namespace = "<namespace>"
compileSdk = libs.versions.android.compileSdk.get().toInt()
buildFeatures {
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
applicationId = "<application id>"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:icon="@mipmap/ic_launcher"
android:label="Demo App"
android:theme="@style/AppTheme"
>
<activity
android:name=".MainActivity"
android:exported="false"
android:launchMode="singleTop"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<application>
element
<application
android:icon="@mipmap/ic_launcher"
android:label="Demo App"
android:theme="@style/AppTheme"
>
Defines the application-level settings. The attributes:
- android:icon="@mipmap/ic_launcher": Specifies the app’s launcher icon, located in the mipmap folder.
- android:label="Demo App": Sets the app’s name displayed to users.
- android:theme="@style/AppTheme": Applies a theme to the application, typically defined in res/values/styles.xml.
<activity>
element
<activity
android:name=".MainActivity"
android:exported="false"
android:launchMode="singleTop"
>
Defines an activity in the application.
- android:name=".MainActivity": The name of the activity. Since it starts with . (dot), it refers to com.yourpackage.MainActivity. It gets it's default value from
android.namespace
defined inbuild.settings.kts
. It is entirely possible to name the MainActivity class something else. - android:exported="false": Disallows the activity to be launched by external apps or system components.
- android:launchMode="singleTop": Ensures that if the activity is already at the top of the stack, a new instance is not created. Instead, onNewIntent() is called.
<intent-filter>
element
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- android.intent.action.MAIN: This makes MainActivity the entry point of the app.
- android.intent.category.LAUNCHER: Specifies that this activity should be shown in the launcher screen (app drawer).
MainActivity.kt
package <namespace>
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
final class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="someName">some string</string>
</resources>
styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
</style>
</resources>
Application Icon Setup
In modern Android development, adaptive icons provide flexibility across different screen sizes and launcher designs. Instead of using multiple rasterized image files, adaptive icons use two layers:
Foreground layer (ic_launcher_foreground.xml) – Contains the primary icon content. Background layer (ic_launcher_background.xml) – Defines the background shape or color. Icon definition (ic_launcher.xml) – Declares the adaptive icon setup.
Building and running your application
With the project set up, you can now build and run your Android application using Gradle.
Building the APK
To compile the project and generate an APK, run the following command:
./gradlew assembleDebug
This will produce an APK in the src/androidApp/build/outputs/apk/debug/ directory. If you need a release build, use:
./gradlew assembleRelease
For release builds, ensure you have a signing configuration set up in your build.gradle.kts.
Installing and Running on a Device
Once the APK is built, you can install it on a connected device or emulator:
./gradlew installDebug
This automatically installs the debug APK onto a device. If multiple devices or emulators are connected, specify the target device using:
adb -s <device_id> install src/androidApp/build/outputs/apk/debug/androidApp-debug.apk
You can launch the app manually from the device or via:
adb shell am start -n <your.package.name>/.MainActivity
Running in an Emulator If you’re testing on an emulator, ensure you have an AVD (Android Virtual Device) set up:
emulator -avd <AVD_NAME>
If you don’t have an AVD configured, create one using:
avdmanager create avd -n myEmulator -k "system-images;android-35;google_apis;x86_64"
Once the emulator is running, you can install and launch the app as described above.
Cleaning
To ensure a fresh build, clear any cached outputs before rebuilding:
./gradlew clean
Conclusion
Wizards provide a lot of benefits when quickly trying something out, but as I have shown here, the machinery behind building an Android app is quite manageable by hand and increases the understanding of the build system.