Skip to main content

Overview

Android Code Studio provides a robust project management system that handles Gradle-based Android projects with full support for multi-module builds, dependency resolution, and workspace configuration.

Architecture

The project management system is built around two core interfaces:

IProjectManager

Main entry point for project operations, initialization, and lifecycle management

IWorkspace

Represents the complete project structure with all modules and dependencies

Project Manager

The IProjectManager interface provides centralized project management:
interface IProjectManager {
  // Project directory access
  val projectDir: File
  val projectDirPath: String
  
  // Synchronization issues
  val projectSyncIssues: ProjectSyncIssues?
  
  // Project operations
  fun openProject(directory: File)
  fun openProject(path: String)
  
  // Setup with Tooling API
  suspend fun setupProject(
    project: IProject = Lookup.getDefault()
      .lookup(BuildService.KEY_PROJECT_PROXY)!!
  )
  
  // Workspace access
  fun getWorkspace(): IWorkspace?
  fun requireWorkspace(): IWorkspace
  
  // File change notifications
  fun notifyFileCreated(file: File)
  fun notifyFileDeleted(file: File)
  fun notifyFileRenamed(from: File, to: File)
  
  // Lifecycle
  fun destroy()
}

Getting the Project Manager

// Singleton access
val projectManager = IProjectManager.getInstance()

// Open a project
projectManager.openProject("/storage/emulated/0/MyAndroidApp")

// Setup project with Tooling API
projectManager.setupProject()

// Access workspace
val workspace = projectManager.requireWorkspace()
The Project Manager uses the ServiceLoader pattern for dependency injection, making it easy to test and extend.

Workspace

The IWorkspace interface represents the complete project structure:
interface IWorkspace {
  // Project access
  fun getProjectDir(): File
  fun getRootProject(): GradleProject
  fun getSubProjects(): List<GradleProject>
  
  // Project lookup
  fun findProject(path: String): GradleProject?
  fun getProject(path: String): GradleProject
  
  // Android modules
  fun androidProjects(): Sequence<AndroidModule>
  
  // Build variant selections
  fun getAndroidVariantSelections(): Map<String, BuildVariantInfo>
  
  // Sync issues
  fun getProjectSyncIssues(): ProjectSyncIssues
  
  // File lookups
  fun findModuleForFile(file: Path, checkExistance: Boolean = false): ModuleProject?
  fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject?
  fun containsSourceFile(file: Path): Boolean
  fun isAndroidResource(file: File): Boolean
}

Working with Projects

// Get root project
val rootProject = workspace.getRootProject()

// Get all subprojects
val subprojects = workspace.getSubProjects()

// Find specific project by path
val appProject = workspace.findProject(":app")
if (appProject != null) {
  // Work with project
}

// Get project (throws if not found)
try {
  val libProject = workspace.getProject(":library")
} catch (e: IWorkspace.ProjectNotFoundException) {
  // Handle missing project
}

Module Projects

The workspace contains different types of module projects:

Gradle Project

abstract class GradleProject {
  abstract val path: String
  abstract val name: String
  abstract val description: String
  abstract val projectDir: File
  abstract val buildDir: File
  abstract val buildScript: File
  abstract val tasks: List<TaskModel>
}

Android Module

class AndroidModule : ModuleProject {
  val namespace: String
  val buildVariant: String
  val androidProject: AndroidProject
  
  // Source sets
  val sourceSets: List<SourceSet>
  
  // Resources
  val resourceDirs: List<File>
  
  // Build configuration
  val compileSdkVersion: String
  val minSdkVersion: Int
  val targetSdkVersion: Int
}

Java Module

class JavaModule : ModuleProject {
  // Java-specific configuration
  val sourceCompatibility: String
  val targetCompatibility: String
  val sourceSets: List<SourceSet>
}
Android Code Studio supports various Gradle project types:
  • Android Application - Main app modules with manifest
  • Android Library - Reusable Android libraries
  • Java Library - Pure Java/Kotlin modules
  • Dynamic Feature - Dynamic feature modules

File Management

The FileManager handles file operations and change notifications:
class FileManager {
  // File change tracking
  fun notifyFileCreated(file: File)
  fun notifyFileDeleted(file: File)
  fun notifyFileRenamed(from: File, to: File)
  
  // File operations
  fun readFile(file: File): String
  fun writeFile(file: File, content: String)
  fun createFile(file: File): Boolean
  fun deleteFile(file: File): Boolean
}

File Change Notifications

Always notify the project manager of file changes to keep the workspace in sync:
// Create a new file
val newFile = File(projectDir, "src/main/java/com/example/NewClass.kt")
if (newFile.createNewFile()) {
  projectManager.notifyFileCreated(newFile)
}

// Delete a file
if (file.delete()) {
  projectManager.notifyFileDeleted(file)
}

// Rename a file
val oldFile = File(projectDir, "OldName.kt")
val newFile = File(projectDir, "NewName.kt")
if (oldFile.renameTo(newFile)) {
  projectManager.notifyFileRenamed(oldFile, newFile)
}

Classpath Management

Android Code Studio provides advanced classpath resolution:

ZipFileClasspathReader

Read classes from JAR and AAR files

JarFsClasspathReader

Java FileSystem-based classpath reading

VersionCatalogClasspathProvider

Gradle version catalog support

BootClasspathProvider

Android SDK boot classpath resolution

Classpath API

interface IClasspathReader {
  fun readClasses(path: Path): Sequence<ClassInfo>
}

data class ClassInfo(
  val name: String,
  val packageName: String,
  val isPublic: Boolean,
  val isAbstract: Boolean,
  val isInterface: Boolean,
  val methods: List<MethodInfo>,
  val fields: List<FieldInfo>
)

Version Catalog Support

Android Code Studio supports Gradle version catalogs:
class VersionCatalogParser {
  fun parse(catalogFile: File): VersionCatalog
}

data class VersionCatalog(
  val versions: Map<String, String>,
  val libraries: Map<String, LibraryDependency>,
  val plugins: Map<String, PluginDependency>
)

Using Version Catalogs

// Parse version catalog
val catalogFile = File(projectDir, "gradle/libs.versions.toml")
val parser = VersionCatalogParser()
val catalog = parser.parse(catalogFile)

// Access dependencies
val androidxCore = catalog.libraries["androidx-core-ktx"]
println("Version: ${androidxCore?.version}")

Build Service Integration

The project manager integrates with the Gradle Tooling API through BuildService:
interface BuildService {
  val isBuildInProgress: Boolean
  
  // Server status
  fun isToolingServerStarted(): Boolean
  fun metadata(): CompletableFuture<ToolingServerMetadata>
  
  // Project initialization
  fun initializeProject(
    params: InitializeProjectParams
  ): CompletableFuture<InitializeResult>
  
  // Task execution
  fun executeTasks(
    vararg tasks: String
  ): CompletableFuture<TaskExecutionResult>
  
  // Build cancellation
  fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
}

Build Operations

val buildService = Lookup.getDefault()
  .lookup(BuildService.KEY_BUILD_SERVICE)

val params = InitializeProjectParams(
  projectDir = projectDir,
  gradleInstallation = gradleHome
)

buildService.initializeProject(params)
  .thenAccept { result ->
    println("Project initialized: ${result.success}")
  }

Project Synchronization

Handle project synchronization and issues:
// Check for sync issues
val syncIssues = workspace.getProjectSyncIssues()

if (syncIssues.hasErrors()) {
  syncIssues.errors.forEach { error ->
    println("Error: ${error.message}")
    println("Location: ${error.location}")
  }
}

if (syncIssues.hasWarnings()) {
  syncIssues.warnings.forEach { warning ->
    println("Warning: ${warning.message}")
  }
}
Project synchronization issues are collected during Gradle sync and can be displayed to users for troubleshooting.

Active Document Tracking

Track currently opened files:
data class ActiveDocument(
  val file: File,
  val content: String,
  val version: Int
)

data class ActiveJavaDocument(
  val file: File,
  val content: String,
  val version: Int,
  val module: JavaModule
) : ActiveDocument

Best Practices

Use Workspace API

Access projects through the workspace for consistency

Notify Changes

Always notify the project manager of file system changes

Handle Exceptions

Check for ProjectNotFoundException when accessing projects

Check Build Status

Verify build status before executing tasks

Error Handling

// Handle workspace not configured
try {
  val workspace = projectManager.requireWorkspace()
} catch (e: IWorkspace.NotConfiguredException) {
  // Workspace not ready, initialize project first
  projectManager.openProject(projectDir)
  projectManager.setupProject()
}

// Handle project not found
try {
  val project = workspace.getProject(":nonexistent")
} catch (e: IWorkspace.ProjectNotFoundException) {
  // Project doesn't exist in workspace
  println("Project not found: ${e.message}")
}
The project management system is designed to be thread-safe and can handle concurrent operations from multiple components.

Build docs developers (and LLMs) love