Skip to content

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.

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:

  1. Using Android Studio (recommended) – Android Studio includes an SDK manager for easy installation.
  2. Using the standalone command-line tools – If Android Studio is not required, the SDK can be installed separately.

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
Verify that you have a connected Android device by running:

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 in build.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>
Defines how this activity can be launched.

  • 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.