Skip to main content

Overview

Android Code Studio provides comprehensive Gradle build system integration through the Gradle Tooling API, enabling full project initialization, task execution, and build management directly on Android devices.

Build Service

The BuildService interface is the primary entry point for all build operations:
interface BuildService {
  companion object {
    @JvmField val KEY_BUILD_SERVICE = Key<BuildService>()
    @JvmField val KEY_PROJECT_PROXY = Key<IProject>()
  }
  
  // Build status
  val isBuildInProgress: Boolean
  
  // Server management
  fun isToolingServerStarted(): Boolean
  fun metadata(): CompletableFuture<ToolingServerMetadata>
  
  // Project operations
  fun initializeProject(
    params: InitializeProjectParams
  ): CompletableFuture<InitializeResult>
  
  // Task execution
  fun executeTasks(
    vararg tasks: String
  ): CompletableFuture<TaskExecutionResult>
  
  // Build control
  fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
}

Getting Started

Accessing the Build Service

import com.tom.rv2ide.lookup.Lookup
import com.tom.rv2ide.projects.builder.BuildService

// Get build service from lookup
val buildService = Lookup.getDefault()
  .lookup(BuildService.KEY_BUILD_SERVICE)
  ?: throw IllegalStateException("Build service not available")
The BuildService is registered in the Lookup during IDE initialization and remains available throughout the application lifecycle.

Project Initialization

Initialize a Gradle project before performing any build operations:
val params = InitializeProjectParams(
  projectDir = File("/storage/emulated/0/AndroidProjects/MyApp")
)

buildService.initializeProject(params)
  .thenAccept { result ->
    if (result.success) {
      println("Project initialized successfully")
      println("Gradle version: ${result.gradleVersion}")
      
      // Access project model
      val project = Lookup.getDefault()
        .lookup(BuildService.KEY_PROJECT_PROXY)
    } else {
      println("Initialization failed: ${result.error}")
    }
  }
  .exceptionally { throwable ->
    println("Exception during init: ${throwable.message}")
    null
  }

Task Execution

Running Gradle Tasks

Execute Gradle tasks asynchronously:
// Single task
buildService.executeTasks("assembleDebug")
  .thenAccept { result ->
    println("Build successful: ${result.isSuccessful}")
    
    // Check executed tasks
    result.tasks.forEach { task ->
      println("${task.path}: ${task.result}")
    }
  }

// Multiple tasks
buildService.executeTasks("clean", "assembleDebug")
  .thenAccept { result ->
    // Handle result
  }

// Module-specific task
buildService.executeTasks(":app:assembleDebug")
  .thenAccept { result ->
    // Handle result
  }

Common Build Tasks

assemble

Build the project output (APK/AAR)

clean

Delete build outputs and clean project

build

Assemble and run tests

test

Run unit tests

lint

Run Android lint checks

dependencies

Display project dependencies

Build Variants

Android projects support different build variants:
// Debug variant
buildService.executeTasks("assembleDebug")

// Release variant
buildService.executeTasks("assembleRelease")

// Flavor-specific
buildService.executeTasks("assembleFreeDebug")
buildService.executeTasks("assembleProRelease")

// Module-specific variant
buildService.executeTasks(":app:assembleDebug")

Build Results

The TaskExecutionResult provides detailed information about the build:
data class TaskExecutionResult(
  val isSuccessful: Boolean,
  val tasks: List<TaskExecutionInfo>,
  val failures: List<BuildFailure>,
  val output: String
)

data class TaskExecutionInfo(
  val path: String,
  val result: TaskResult,
  val executionTime: Long
)

enum class TaskResult {
  SUCCESS,
  FAILED,
  UP_TO_DATE,
  FROM_CACHE,
  SKIPPED
}

Processing Build Results

buildService.executeTasks("assembleDebug")
  .thenAccept { result ->
    if (result.isSuccessful) {
      // Build succeeded
      val upToDateTasks = result.tasks.count { 
        it.result == TaskResult.UP_TO_DATE 
      }
      val executedTasks = result.tasks.count { 
        it.result == TaskResult.SUCCESS 
      }
      
      println("Executed: $executedTasks, Up-to-date: $upToDateTasks")
      
      // Find output APK
      val apkPath = findApkOutput(result)
    } else {
      // Build failed
      result.failures.forEach { failure ->
        println("Failure: ${failure.message}")
        println("Location: ${failure.location}")
        failure.stacktrace?.let { println(it) }
      }
    }
    
    // Always available: full build output
    println(result.output)
  }

Build Cancellation

Cancel long-running builds:
// Check if build is running
if (buildService.isBuildInProgress) {
  // Cancel the build
  buildService.cancelCurrentBuild()
    .thenAccept { result ->
      if (result.success) {
        println("Build cancelled successfully")
      } else {
        println("Failed to cancel build: ${result.message}")
      }
    }
}
Build cancellation is a best-effort operation. Some build operations may not be immediately cancellable.

Tooling Server

Server Status

Check Tooling API server status:
// Check if server is running
if (buildService.isToolingServerStarted()) {
  // Server is ready for build operations
  
  // Get server metadata
  buildService.metadata()
    .thenAccept { metadata ->
      println("Server version: ${metadata.version}")
      println("Gradle version: ${metadata.gradleVersion}")
      println("Server capabilities: ${metadata.capabilities}")
    }
} else {
  // Server needs to be started
  println("Tooling server not started")
}

Server Metadata

data class ToolingServerMetadata(
  val version: String,
  val gradleVersion: String,
  val capabilities: List<String>,
  val supportedFeatures: Set<String>
)

Gradle File Parsing

Android Code Studio includes utilities for parsing Gradle build files:
class GradleFileParser {
  fun parseBuildScript(file: File): BuildScript
  fun extractDependencies(file: File): List<Dependency>
  fun extractPlugins(file: File): List<Plugin>
}

data class BuildScript(
  val plugins: List<Plugin>,
  val dependencies: List<Dependency>,
  val android: AndroidConfig?,
  val properties: Map<String, String>
)

Parsing Build Files

val parser = GradleFileParser()
val buildFile = File(projectDir, "app/build.gradle")

val dependencies = parser.extractDependencies(buildFile)
dependencies.forEach { dep ->
  println("${dep.configuration}: ${dep.notation}")
}

Build Events

Listen to build events for progress tracking:
interface BuildEventListener {
  fun onBuildStarted()
  fun onTaskStarted(taskPath: String)
  fun onTaskFinished(taskPath: String, result: TaskResult)
  fun onBuildFinished(result: TaskExecutionResult)
  fun onBuildFailed(failures: List<BuildFailure>)
}

// Register listener
buildService.registerEventListener(listener)

Advanced Features

Custom Gradle Arguments

// Execute with custom Gradle arguments
val params = InitializeProjectParams(
  projectDir = projectDir,
  options = listOf(
    "--parallel",
    "--build-cache",
    "--offline",
    "-Dorg.gradle.jvmargs=-Xmx2g"
  )
)

buildService.initializeProject(params)

Environment Variables

val params = InitializeProjectParams(
  projectDir = projectDir,
  environmentVariables = mapOf(
    "ANDROID_HOME" to "/data/data/com.tom.rv2ide/files/android-sdk",
    "JAVA_HOME" to "/data/data/com.tom.rv2ide/files/jdk",
    "GRADLE_USER_HOME" to gradleUserHome
  )
)

Project Properties

// Pass project properties
val params = InitializeProjectParams(
  projectDir = projectDir,
  projectProperties = mapOf(
    "android.useAndroidX" to "true",
    "android.enableJetifier" to "false"
  )
)
Optimize build performance with these settings:
  • Parallel Execution: --parallel
  • Build Cache: --build-cache
  • Configuration Cache: --configuration-cache
  • Daemon: Gradle daemon runs by default
  • Incremental Builds: Enabled automatically

Error Handling

Build Failures

buildService.executeTasks("assembleDebug")
  .thenAccept { result ->
    if (!result.isSuccessful) {
      result.failures.forEach { failure ->
        when (failure.type) {
          FailureType.COMPILATION_ERROR -> {
            // Handle compilation errors
            println("Compilation error: ${failure.message}")
            failure.location?.let { loc ->
              println("File: ${loc.file}:${loc.line}")
            }
          }
          FailureType.TASK_EXECUTION_FAILURE -> {
            // Handle task execution failures
            println("Task failed: ${failure.task}")
          }
          FailureType.DEPENDENCY_RESOLUTION -> {
            // Handle dependency issues
            println("Dependency error: ${failure.message}")
          }
          else -> {
            println("Build error: ${failure.message}")
          }
        }
      }
    }
  }
  .exceptionally { throwable ->
    // Handle exceptions
    when (throwable) {
      is BuildCancelledException -> 
        println("Build was cancelled")
      is GradleConnectionException -> 
        println("Failed to connect to Gradle")
      else -> 
        println("Build exception: ${throwable.message}")
    }
    null
  }

Best Practices

Initialize First

Always initialize the project before executing tasks

Handle Async

Use CompletableFuture callbacks for async operations

Check Status

Verify build status before starting new builds

Cancel on Exit

Cancel running builds when leaving the build screen

Integration Examples

Build and Install

suspend fun buildAndInstall() {
  try {
    // Execute build task
    val result = buildService.executeTasks("installDebug").await()
    
    if (result.isSuccessful) {
      // APK installed successfully
      showNotification("App installed successfully")
    } else {
      // Show build errors
      showBuildErrors(result.failures)
    }
  } catch (e: Exception) {
    showError("Build failed: ${e.message}")
  }
}

Incremental Build

// Check for changes and build
if (hasUnsavedFiles()) {
  saveAllFiles()
}

// Execute incremental build
buildService.executeTasks("assembleDebug")
  .thenAccept { result ->
    val upToDate = result.tasks.count { 
      it.result == TaskResult.UP_TO_DATE 
    }
    println("$upToDate tasks were up-to-date")
  }
Use Gradle’s incremental build capabilities by avoiding clean builds when possible. This significantly speeds up build times.

Build docs developers (and LLMs) love