Skip to main content

Build System

NASA Explorer uses Gradle with Kotlin DSL for its build system. The project supports multiple build variants and configurations.

Prerequisites

Before building, ensure you have completed the requirements setup:
1

Verify Android Studio

Ensure you have Android Studio Koala or newer installed.
2

Configure API Keys

Create local.properties with your NASA API key:
nasaApiKey=YOUR_NASA_API_KEY
3

Add Firebase Configuration

Place google-services.json in the app/ directory.
Building will fail without the NASA API key and Firebase configuration files. These are required dependencies.

Building from Android Studio

The easiest way to build and run the application.

Initial Setup

1

Open Project

Open the project in Android Studio:
cd NASAExplorer
# Then File > Open in Android Studio
2

Sync Gradle

Android Studio will automatically prompt you to sync Gradle. Click Sync Now to download dependencies.If sync doesn’t start automatically, click: File > Sync Project with Gradle Files
3

Select Build Variant

Open the Build Variants panel (bottom left) and select:
  • debug - For development
  • release - For production builds

Running the App

1

Connect Device

Connect an Android device via USB with USB debugging enabled, or start an Android emulator.
2

Select Target Device

Choose your target device from the device dropdown in the toolbar.
3

Run

Click the Run button (green play icon) or press Shift + F10.Android Studio will build the APK and install it on your device.
The first build may take several minutes as Gradle downloads dependencies. Subsequent builds will be faster thanks to caching.

Building from Command Line

For automation, CI/CD, or advanced users, you can build using Gradle commands.

Debug Build

Build a debug APK for development:
# Linux/macOS
./gradlew assembleDebug

# Windows
gradlew.bat assembleDebug
The APK will be generated at:
app/build/outputs/apk/debug/app-debug.apk

Release Build

Build a release APK optimized for production:
./gradlew assembleRelease
The APK will be generated at:
app/build/outputs/apk/release/app-release.apk
Release builds require signing configuration. Without it, you’ll get an unsigned APK that can only be used for testing.

Install Directly

Build and install the app on a connected device in one command:
./gradlew installDebug

Clean Build

Remove all build artifacts and rebuild from scratch:
./gradlew clean build

Build Configuration

Understanding the build configuration files.

App Build Configuration

The app/build.gradle.kts file defines app-specific build settings:
android {
    namespace = "com.ccandeladev.nasaexplorer"
    compileSdk = 34
    
    defaultConfig {
        applicationId = "com.ccandeladev.nasaexplorer"
        minSdk = 28
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }
}

API Key Injection

The NASA API key is injected from local.properties during build:
// Read local.properties
val localProperties = Properties().apply {
    load(project.rootProject.file("local.properties").inputStream())
}

// Get API key
val nasaApiKey: String = localProperties.getProperty("nasaApiKey") ?: ""

// Inject into BuildConfig
android {
    defaultConfig {
        buildConfigField("String", "NASA_API_KEY", "\"$nasaApiKey\"")
    }
    
    buildFeatures {
        buildConfig = true
    }
}
Access in code:
val apiKey = BuildConfig.NASA_API_KEY

Signing Configuration

To distribute your app, you need to sign it with a keystore.

Creating a Keystore

1

Generate Keystore

Use the keytool command to create a keystore:
keytool -genkey -v -keystore nasa-explorer.keystore \
  -alias nasa-explorer \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000
2

Store Safely

Save the keystore file in a secure location outside the project directory.
Never commit keystore files or passwords to version control.
3

Add Signing Configuration

Add signing configuration to local.properties:
nasaApiKey=YOUR_NASA_API_KEY
storeFile=/path/to/nasa-explorer.keystore
storePassword=YOUR_STORE_PASSWORD
keyAlias=nasa-explorer
keyPassword=YOUR_KEY_PASSWORD

Configure Gradle Signing

Update app/build.gradle.kts to use the keystore:
val localProperties = Properties().apply {
    load(project.rootProject.file("local.properties").inputStream())
}

android {
    signingConfigs {
        create("release") {
            storeFile = file(localProperties.getProperty("storeFile"))
            storePassword = localProperties.getProperty("storePassword")
            keyAlias = localProperties.getProperty("keyAlias")
            keyPassword = localProperties.getProperty("keyPassword")
        }
    }
    
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Code Optimization

ProGuard / R8

The release build uses R8 for code shrinking and obfuscation.
buildTypes {
    release {
        isMinifyEnabled = true
        proguardFiles(
            getDefaultProguardFile("proguard-android-optimize.txt"),
            "proguard-rules.pro"
        )
    }
}
R8 is enabled by default and provides better performance than ProGuard. It automatically handles most Android libraries.

Custom ProGuard Rules

Add custom rules in app/proguard-rules.pro:
# Keep Firebase classes
-keep class com.google.firebase.** { *; }

# Keep Retrofit interfaces
-keep interface com.ccandeladev.nasaexplorer.data.api.** { *; }

# Keep data models for Gson
-keep class com.ccandeladev.nasaexplorer.data.api.NasaResponse { *; }
-keep class com.ccandeladev.nasaexplorer.domain.** { *; }

Build Variants

The project currently has two build types:
VariantPurposeMinificationDebuggable
debugDevelopmentNoYes
releaseProductionYesNo

Adding Custom Build Variants

You can add custom build variants for different environments:
android {
    buildTypes {
        debug {
            applicationIdSuffix = ".debug"
            versionNameSuffix = "-DEBUG"
        }
        
        create("staging") {
            initWith(getByName("debug"))
            applicationIdSuffix = ".staging"
            versionNameSuffix = "-STAGING"
        }
        
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Build Performance

Optimize build performance with these Gradle settings.

gradle.properties Optimizations

# Use more RAM for Gradle daemon
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8

# Enable parallel builds
org.gradle.parallel=true

# Enable Gradle caching
org.gradle.caching=true

# Enable configuration cache (experimental)
org.gradle.configuration-cache=true

# Use Kotlin incremental compilation
kotlin.incremental=true

Build Cache

Enable Gradle build cache in settings.gradle.kts:
buildCache {
    local {
        isEnabled = true
        directory = File(rootDir, "build-cache")
    }
}

Troubleshooting

Common build issues and solutions.

Gradle Sync Failed

1

Invalidate Caches

In Android Studio: File > Invalidate Caches… > Invalidate and Restart
2

Clean Project

./gradlew clean
3

Delete Gradle Cache

rm -rf ~/.gradle/caches
./gradlew clean build --refresh-dependencies

Missing API Key Error

BuildConfig.NASA_API_KEY is empty
Solution: Ensure local.properties exists with your NASA API key:
nasaApiKey=YOUR_API_KEY_HERE

Google Services Plugin Error

File google-services.json is missing
Solution: Download google-services.json from Firebase Console and place it in the app/ directory.

Out of Memory Error

OutOfMemoryError: Java heap space
Solution: Increase Gradle JVM heap size in gradle.properties:
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8

Generating AAB for Play Store

Android App Bundles (AAB) are the recommended format for Play Store distribution.
./gradlew bundleRelease
The AAB will be generated at:
app/build/outputs/bundle/release/app-release.aab
AAB files are smaller and allow Google Play to generate optimized APKs for different device configurations.

CI/CD Integration

Example GitHub Actions workflow for automated builds:
.github/workflows/build.yml
name: Build APK

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      
      - name: Create local.properties
        run: |
          echo "nasaApiKey=${{ secrets.NASA_API_KEY }}" > local.properties
      
      - name: Add google-services.json
        run: |
          echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > app/google-services.json
      
      - name: Build Debug APK
        run: ./gradlew assembleDebug
      
      - name: Upload APK
        uses: actions/upload-artifact@v4
        with:
          name: app-debug
          path: app/build/outputs/apk/debug/app-debug.apk
Store sensitive keys (API keys, keystore passwords) as GitHub Secrets, never commit them to the repository.

Build docs developers (and LLMs) love