Documentation Index Fetch the complete documentation index at: https://mintlify.com/AndroidCSOfficial/android-code-studio/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers best practices and patterns for developing plugins that extend Android Code Studio’s functionality.
Plugin Architecture
Plugin Structure
A typical plugin follows this structure:
my-plugin/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/plugin/
│ │ ├── MyPlugin.kt
│ │ ├── MyLanguageServer.kt
│ │ ├── MyActions.kt
│ │ └── MyTemplates.kt
│ └── resources/
│ └── META-INF/
│ └── services/
│ ├── com.tom.rv2ide.lsp.api.ILanguageServer
│ ├── com.tom.rv2ide.templates.ITemplateProvider
│ └── com.tom.rv2ide.actions.ActionItem
└── build.gradle.kts
Service Registration
Plugins use Java’s ServiceLoader pattern for registration. Create service files in META-INF/services/:
File : META-INF/services/com.tom.rv2ide.lsp.api.ILanguageServer
com.example.plugin.MyLanguageServer
File : META-INF/services/com.tom.rv2ide.templates.ITemplateProvider
com.example.plugin.MyTemplateProvider
Creating a Language Server Plugin
Basic Language Server
Implement a language server for a custom language:
package com.example.plugin
import com.tom.rv2ide.lsp.api. *
import com.tom.rv2ide.lsp.models. *
import com.tom.rv2ide.projects.IWorkspace
import java.nio.file.Path
class MyLanguageServer : ILanguageServer {
override val serverId = "my-language-server"
override var client: ILanguageClient ? = null
private var workspace: IWorkspace ? = null
private val indexer = CodeIndexer ()
override fun shutdown () {
indexer. clear ()
workspace = null
client = null
}
override fun connectClient (client: ILanguageClient ?) {
this .client = client
client?. logMessage ( 1 , "My Language Server connected" )
}
override fun setupWorkspace (workspace: IWorkspace ) {
this .workspace = workspace
// Index workspace
workspace. getSubProjects (). forEach { project ->
indexer. indexProject (project)
}
}
override fun applySettings (settings: IServerSettings ?) {
// Apply user preferences
settings?. let {
configureFromSettings (it)
}
}
override fun complete (params: CompletionParams ?): CompletionResult {
if (params == null ) return CompletionResult.EMPTY
val items = indexer. getCompletions (
params.file,
params.position,
params.prefix
)
return CompletionResult (items)
}
override suspend fun findDefinition (
params: DefinitionParams
): DefinitionResult {
val locations = indexer. findDefinition (
params.file,
params.position
)
return DefinitionResult (locations)
}
override suspend fun findReferences (
params: ReferenceParams
): ReferenceResult {
val locations = indexer. findReferences (
params.file,
params.position,
params.includeDeclaration
)
return ReferenceResult (locations)
}
override suspend fun signatureHelp (
params: SignatureHelpParams
): SignatureHelp {
return indexer. getSignatureHelp (
params.file,
params.position
)
}
override suspend fun analyze (file: Path ): DiagnosticResult {
val diagnostics = indexer. analyze (file)
// Publish to client
client?. publishDiagnostics (file, diagnostics)
return diagnostics
}
override suspend fun expandSelection (
params: ExpandSelectionParams
): Range {
return indexer. expandSelection (params.file, params.range)
}
override fun formatCode (params: FormatCodeParams ?): CodeFormatResult {
if (params == null ) return CodeFormatResult ( false , emptyList ())
val formatted = formatSource (params.content)
val edits = createTextEdits (params.content, formatted)
return CodeFormatResult ( true , edits)
}
override fun handleFailure (failure: LSPFailure ?): Boolean {
if (failure != null ) {
client?. logMessage ( 3 , "Error: ${ failure.message } " )
return true
}
return false
}
}
Register Language Server
Create the service registration file:
File : META-INF/services/com.tom.rv2ide.lsp.api.ILanguageServer
com.example.plugin.MyLanguageServer
Creating Action Plugins
Define Custom Actions
Create actions that extend IDE functionality:
package com.example.plugin
import com.tom.rv2ide.actions. *
import com.tom.rv2ide.editor.api.IEditor
import android.graphics.drawable.Drawable
class RunWithOptionsAction : ActionItem {
override val id = "plugin.run.with.options"
override var label = "Run with Options"
override var subtitle: String ? = "Run with custom configuration"
override var icon: Drawable ? = null
override var visible = true
override var enabled = true
override var requiresUIThread = false
override var location = ActionItem.Location.EDITOR_TOOLBAR
override val order = 200
override fun prepare ( data : ActionData ) {
// Enable only when project is open
val projectManager = data . get (IProjectManager:: class .java)
enabled = projectManager?. getWorkspace () != null
}
override suspend fun execAction ( data : ActionData ): Any {
// Show options dialog
val options = showOptionsDialog ( data )
if (options != null ) {
// Execute run with options
return executeRun (options)
}
return false
}
override fun postExec ( data : ActionData , result: Any ) {
if (result == true ) {
showToast ( "Run started" )
}
}
}
// Auto-register via service loader
class MyActionProvider : ActionItem by RunWithOptionsAction ()
Action Registration
Create service file for actions:
File : META-INF/services/com.tom.rv2ide.actions.ActionItem
com.example.plugin.RunWithOptionsAction
com.example.plugin.MyOtherAction
Creating Template Plugins
Custom Template Provider
Provide project and file templates:
package com.example.plugin
import com.tom.rv2ide.templates. *
class MyTemplateProvider : ITemplateProvider {
private val templates = mutableListOf < Template <*>>()
init {
loadTemplates ()
}
private fun loadTemplates () {
templates. add ( CustomProjectTemplate ())
templates. add ( CustomFileTemplate ())
templates. add ( CustomActivityTemplate ())
}
override fun getTemplates () = templates. toList ()
override fun getTemplate (templateId: String ) =
templates. find { it.id == templateId }
override fun reload () {
templates. clear ()
loadTemplates ()
}
override fun release () {
templates. clear ()
}
}
class CustomProjectTemplate : ProjectTemplate {
override val id = "custom-project"
override val name = "Custom Project"
override val description = "Creates a custom project structure"
override val category = "Custom"
override val minSdk = 21
override val targetSdk = 34
override val widgets = listOf (
Widget. TextField ( "appName" , "Application Name" , "My App" ),
Widget. TextField ( "packageName" , "Package Name" , "com.example.app" )
)
override suspend fun create (
data : ProjectTemplateData ,
executor: RecipeExecutor
) {
// Create project structure
createDirectories ( data , executor)
generateFiles ( data , executor)
}
}
Plugin Initialization
Plugin Main Class
Create a main plugin class for initialization:
package com.example.plugin
import com.tom.rv2ide.actions.ActionsRegistry
import com.tom.rv2ide.lsp.api.ILanguageServerRegistry
import com.tom.rv2ide.templates.ITemplateProvider
class MyPlugin {
companion object {
private var initialized = false
@JvmStatic
fun initialize () {
if (initialized) return
initialized = true
// Register language server
val lspRegistry = ILanguageServerRegistry. getDefault ()
lspRegistry. register (
MyLanguageServer (),
listOf ( ".myext" , ".custom" )
)
// Register actions
val actionsRegistry = ActionsRegistry. getInstance ()
actionsRegistry. registerAction ( RunWithOptionsAction ())
actionsRegistry. registerAction ( CustomToolAction ())
// Initialize templates
ITemplateProvider. getInstance (reload = true )
println ( "My Plugin initialized" )
}
@JvmStatic
fun shutdown () {
// Clean up resources
initialized = false
}
}
}
Build Configuration
Gradle Build File
Configure your plugin build:
// build.gradle.kts
plugins {
kotlin ( "jvm" )
}
dependencies {
// Android Code Studio APIs
compileOnly ( "com.tom.rv2ide:editor-api:1.0.0" )
compileOnly ( "com.tom.rv2ide:lsp-api:1.0.0" )
compileOnly ( "com.tom.rv2ide:projects:1.0.0" )
compileOnly ( "com.tom.rv2ide:actions:1.0.0" )
compileOnly ( "com.tom.rv2ide:templates-api:1.0.0" )
// Kotlin
implementation ( kotlin ( "stdlib" ))
implementation ( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" )
// Testing
testImplementation ( "junit:junit:4.13.2" )
testImplementation ( kotlin ( "test" ))
}
tasks {
jar {
// Include service files
from ( "src/main/resources" )
}
}
Testing Plugins
Unit Tests
Write tests for your plugin:
import org.junit.Test
import org.junit.Assert. *
import com.example.plugin.MyLanguageServer
class MyLanguageServerTest {
@Test
fun testServerInitialization () {
val server = MyLanguageServer ()
assertNotNull (server.serverId)
assertEquals ( "my-language-server" , server.serverId)
}
@Test
fun testCompletion () {
val server = MyLanguageServer ()
val params = CompletionParams (
file = Paths. get ( "/test/file.txt" ),
position = Position ( 0 , 0 ),
prefix = "test"
)
val result = server. complete (params)
assertNotNull (result)
assertTrue (result.items. isNotEmpty ())
}
}
Best Practices
Initialize resources only when needed: private val indexer by lazy { CodeIndexer () }
private val parser by lazy { MyParser () }
Use coroutines for long operations: override suspend fun analyze (file: Path ): DiagnosticResult {
return withContext (Dispatchers.IO) {
// Heavy analysis work
performAnalysis (file)
}
}
Cache expensive computations: private val completionCache = LRUCache < String , CompletionResult >( 100 )
override fun complete (params: CompletionParams ): CompletionResult {
val key = " ${ params.file } : ${ params.position } "
return completionCache. getOrPut (key) {
computeCompletions (params)
}
}
Error Handling
override suspend fun execAction ( data : ActionData ): Any {
return try {
performAction ( data )
} catch (e: Exception ) {
client?. showMessage ( 3 , "Error: ${ e.message } " )
false
}
}
override fun handleFailure (failure: LSPFailure ?): Boolean {
when (failure?.type) {
LSPFailure.Type.TIMEOUT -> {
client?. showMessage ( 2 , "Operation timed out" )
return true
}
LSPFailure.Type.CANCELLED -> {
// User cancelled, no message needed
return true
}
else -> return false
}
}
Resource Management
class MyLanguageServer : ILanguageServer {
private val resources = mutableListOf < Closeable >()
override fun shutdown () {
// Clean up all resources
resources. forEach { resource ->
try {
resource. close ()
} catch (e: Exception ) {
// Log but don't fail
}
}
resources. clear ()
}
}
Debugging Plugins
Logging
Use the IDE’s logging system:
import com.tom.rv2ide.logging.ILogger
class MyPlugin {
private val logger = ILogger. newInstance ( "MyPlugin" )
fun doSomething () {
logger. debug ( "Starting operation" )
try {
// Do work
logger. info ( "Operation completed" )
} catch (e: Exception ) {
logger. error ( "Operation failed" , e)
}
}
}
Client Messages
Send messages to the IDE:
// Log messages (shown in log console)
client?. logMessage ( 1 , "Info message" )
client?. logMessage ( 2 , "Warning message" )
client?. logMessage ( 3 , "Error message" )
// Show messages (shown to user)
client?. showMessage ( 1 , "Operation successful" )
client?. showMessage ( 2 , "Warning: Consider..." )
client?. showMessage ( 3 , "Error occurred" )
Distribution
Packaging
Package your plugin as a JAR:
The JAR will include:
Compiled classes
Service registration files
Resources
Installation
Users can install plugins by:
Copying the JAR to the plugins directory
Restarting Android Code Studio
The plugin will be auto-loaded via ServiceLoader
Complete Example
Here’s a complete minimal plugin:
// MyPlugin.kt
package com.example.plugin
import com.tom.rv2ide.actions. *
import com.tom.rv2ide.lsp.api. *
import com.tom.rv2ide.templates. *
// Language Server
class MyLanguageServer : ILanguageServer {
override val serverId = "my-lang"
override var client: ILanguageClient ? = null
override fun shutdown () {}
override fun connectClient (client: ILanguageClient ?) { this .client = client }
override fun applySettings (settings: IServerSettings ?) {}
override fun setupWorkspace (workspace: IWorkspace ) {}
override fun complete (params: CompletionParams ?) = CompletionResult.EMPTY
override suspend fun findReferences (params: ReferenceParams ) = ReferenceResult.EMPTY
override suspend fun findDefinition (params: DefinitionParams ) = DefinitionResult.EMPTY
override suspend fun expandSelection (params: ExpandSelectionParams ) = params.range
override suspend fun signatureHelp (params: SignatureHelpParams ) = SignatureHelp.EMPTY
override suspend fun analyze (file: Path ) = DiagnosticResult.NO_UPDATE
}
// Action
class MyAction : ActionItem {
override val id = "my.action"
override var label = "My Action"
override var subtitle: String ? = null
override var icon: Drawable ? = null
override var visible = true
override var enabled = true
override var requiresUIThread = false
override var location = ActionItem.Location.EDITOR_TOOLBAR
override fun prepare ( data : ActionData ) {}
override suspend fun execAction ( data : ActionData ) = true
override fun postExec ( data : ActionData , result: Any ) {}
}
API Overview Complete API reference
Architecture Understanding the IDE structure