Skip to content

ADFA-3468: Add plugin template contribution API#1121

Closed
Daniel-ADFA wants to merge 1 commit into
stagefrom
ADFA-2683-add-plugins-template-support
Closed

ADFA-3468: Add plugin template contribution API#1121
Daniel-ADFA wants to merge 1 commit into
stagefrom
ADFA-2683-add-plugins-template-support

Conversation

@Daniel-ADFA
Copy link
Copy Markdown
Contributor

Screen.Recording.2026-03-26.at.01.15.28.mov

@Daniel-ADFA Daniel-ADFA requested a review from jomen-adfa March 26, 2026 00:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

This change introduces a plugin-based template management subsystem, enabling plugins to create, register, and manage IDE template files (.cgt format). It includes new builder APIs for template construction, plugin lifecycle integration for template extraction and cleanup, and conditional UI controls for template parameters.

Changes

Cohort / File(s) Summary
Plugin API — Service Interface
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt
Defines public interface for template management with methods to create template builders, register/unregister templates, query registration status, list registered templates, and reload templates.
Plugin API — Template Builder
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt
Introduces builder class for constructing .cgt template packages with support for metadata, boolean options (language/minSdk/packageName), text/checkbox parameters, template/static/binary files, and thumbnail configuration.
Plugin Manager — Core & Project Management
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt
Added template reload listener support in PluginManager; introduced PluginProjectManager singleton for extracting bundled CGT templates from plugins and cleaning up template files on plugin unload.
Plugin Manager — Service Implementation
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt
Implements IdeTemplateService with template registration/unregistration logic, permission validation, file operations in Environment.TEMPLATES_DIR, and cleanup utilities.
Templates API — Base & Builder
templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt, templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt
Added showPackageName parameter to baseZipProject to conditionally control package name field and name→package synchronization; removed forced nullability on thumbData in ProjectTemplateBuilder.
Templates Implementation — ZIP & Serialization
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt, templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt
Made packageName optional in RequiredParametersJson; updated ZipTemplateReader to derive showPackageName from template metadata and pass it to baseZipProject.
Templates Implementation — Discovery
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt
Removed debug-level logging statements during template initialization.
Fragment — UI
app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt
Removed three descriptive comment lines from reloadTemplates() method.

Sequence Diagram

sequenceDiagram
    participant Plugin as Plugin Load
    participant PluginMgr as PluginManager
    participant ProjectMgr as PluginProjectManager
    participant FS as File System
    participant TemplateSvc as IdeTemplateServiceImpl
    participant UI as UI/Listener

    Plugin->>PluginMgr: loadPlugin(pluginId)
    PluginMgr->>ProjectMgr: extractBundledCgtTemplates(pluginId)
    ProjectMgr->>FS: scan classLoader for .cgt files
    FS-->>ProjectMgr: list .cgt resources
    ProjectMgr->>FS: copy plugin_{pluginId}_*.cgt to TEMPLATES_DIR
    PluginMgr->>TemplateSvc: createService(pluginId)
    TemplateSvc->>TemplateSvc: initialize with plugin prefix
    PluginMgr->>UI: invoke templateReloadListener()
    
    Plugin->>TemplateSvc: registerTemplate(cgtFile)
    TemplateSvc->>TemplateSvc: validatePermissions()
    TemplateSvc->>FS: copy .cgt with plugin prefix
    TemplateSvc->>UI: onTemplatesChanged()
    
    Plugin->>PluginMgr: unloadPlugin(pluginId)
    PluginMgr->>TemplateSvc: cleanupAllTemplates()
    TemplateSvc->>FS: delete plugin_{pluginId}_*.cgt files
    TemplateSvc->>UI: onTemplatesChanged()
    PluginMgr->>ProjectMgr: cleanupPluginTemplates(pluginId)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Reviewers

  • jomen-adfa
  • itsaky-adfa

Poem

🐰✨ Hoppy templates hop into place,
Plugin managers set a blazing pace!
Builders craft .cgt with flair,
Parameters optional, controls so fair—
Templates reloaded without a care! 🎨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description contains only a video link without any textual explanation of the changeset or objectives. Provide a text-based description explaining the purpose, changes, and implementation details of the plugin template contribution API alongside or instead of the video link.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add plugin template contribution API' directly and clearly describes the main change: introducing a new plugin template contribution API with interface IdeTemplateService, implementation IdeTemplateServiceImpl, and builder CgtTemplateBuilder.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-2683-add-plugins-template-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (1)

402-404: ⚠️ Potential issue | 🟠 Major

Add missing call to extractBundledCgtTemplates during plugin load.

The PluginProjectManager.extractBundledCgtTemplates(pluginId, classLoader) is never invoked during plugin loading, creating an asymmetric lifecycle: cleanupPluginTemplates is called during unload (line 471) but the corresponding extraction call is absent during load.

Both methods share the same naming convention and reload listener, indicating they're designed as paired operations. Without the extraction call, plugins cannot bundle .cgt template files that should be auto-extracted.

Add PluginProjectManager.getInstance().extractBundledCgtTemplates(manifest.id, classLoader) in the loadPlugin method after successful plugin initialization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`
around lines 402 - 404, The plugin load path in loadPlugin creates and registers
LoadedPlugin but never calls
PluginProjectManager.getInstance().extractBundledCgtTemplates(manifest.id,
classLoader), causing bundled .cgt templates to never be extracted
(cleanupPluginTemplates is called on unload). After successful plugin
initialization and after creating/putting the LoadedPlugin (the block that
constructs LoadedPlugin and sets loadedPlugins[manifest.id]), add a call to
PluginProjectManager.getInstance().extractBundledCgtTemplates(manifest.id,
classLoader) so extraction runs during load and matches cleanupPluginTemplates
during unload.
🧹 Nitpick comments (6)
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt (1)

6-19: Clean interface design.

The interface is well-structured with a clear contract for template management. Consider adding KDoc comments for each method to document expected behavior, parameters, and return values for plugin developers.

📝 Example documentation
 interface IdeTemplateService {
 
+    /**
+     * Creates a new template builder for constructing a .cgt template archive.
+     * `@param` name The display name of the template
+     * `@return` A builder instance for fluent template construction
+     */
     fun createTemplateBuilder(name: String): CgtTemplateBuilder
 
+    /**
+     * Registers a .cgt template file with the IDE.
+     * `@param` cgtFile The template file to register (must exist and have .cgt extension)
+     * `@return` true if registration succeeded, false otherwise
+     */
     fun registerTemplate(cgtFile: File): Boolean
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt`
around lines 6 - 19, Add KDoc comments to the IdeTemplateService interface and
each method (createTemplateBuilder, registerTemplate, unregisterTemplate,
isTemplateRegistered, getRegisteredTemplates, reloadTemplates) to document
expected behavior, parameters and return values for plugin authors; for each
method include a brief description, `@param` entries for parameters (e.g., name,
cgtFile, templateFileName) and `@return` descriptions for Boolean/List results,
and note any side-effects or exceptions (e.g., reloadTemplates mutates internal
state) so implementers and callers have clear contract guidance.
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt (2)

223-226: BinaryFileEntry doesn't override equals/hashCode for ByteArray.

The data class BinaryFileEntry includes a ByteArray property. Kotlin data classes use structural equality, but ByteArray.equals() uses referential equality. This can cause unexpected behavior if these entries are compared or used in collections.

📝 Note

If BinaryFileEntry instances are never compared or stored in sets/maps, this is fine. Otherwise, consider overriding equals/hashCode to use contentEquals for the byte array, or convert to a regular class.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt`
around lines 223 - 226, BinaryFileEntry uses a ByteArray which defaults to
referential equality causing incorrect comparisons/hashing; update
BinaryFileEntry to override equals and hashCode (or convert to a regular class)
so the bytes are compared by content: implement equals to use
bytes.contentEquals(other.bytes) and compare path, and implement hashCode to
combine path.hashCode() with bytes.contentHashCode(); ensure the class still has
same property names (BinaryFileEntry, path, bytes) so usages remain unchanged.

158-188: The "placeholder": null field is not in the expected JSON schema.

Line 172 adds "placeholder": null which doesn't exist in ParametersJson (see templates-impl/.../ZipJson.kt). While GSON ignores unknown fields by default, this is a fragile workaround for trailing comma handling.

Consider using a JSON library (like Gson or kotlinx.serialization) to generate the metadata JSON safely, or restructure the string building to properly handle trailing commas.

♻️ Alternative approach using buildList
private fun writeMetadata(zip: ZipOutputStream, dirName: String) {
    val requiredParts = buildList {
        add(""""appName": {"identifier": "APP_NAME"}""")
        if (showPackageName) add(""""packageName": {"identifier": "PACKAGE_NAME"}""")
        add(""""saveLocation": {"identifier": "SAVE_LOCATION"}""")
    }
    val requiredBlock = requiredParts.joinToString(",\n        ")
    // ... similar pattern for other blocks
}
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (1)

471-476: Redundant template cleanup calls.

Both PluginProjectManager.cleanupPluginTemplates(pluginId) and IdeTemplateServiceImpl.cleanupAllTemplates() delete files matching the same prefix pattern (plugin_{pluginId}_) from Environment.TEMPLATES_DIR. The second call becomes a no-op after the first completes.

Consider consolidating to a single cleanup mechanism or documenting the intent if these are meant to handle different scenarios.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`
around lines 471 - 476, The two successive cleanup calls are redundant:
PluginProjectManager.getInstance().cleanupPluginTemplates(pluginId) and
IdeTemplateServiceImpl.cleanupAllTemplates() both remove files with the same
prefix (plugin_{pluginId}_) from Environment.TEMPLATES_DIR; choose one canonical
cleanup path (either keep the specialized
PluginProjectManager.cleanupPluginTemplates(pluginId) and remove the
IdeTemplateServiceImpl.cleanupAllTemplates() call, or make cleanupAllTemplates()
accept a pluginId/targeted scope and call that instead) or add a clear comment
documenting why both are required for different scenarios; update the code to
call only the consolidated method (or adjust signature/behavior of
IdeTemplateServiceImpl.cleanupAllTemplates) and remove the no-op duplicate to
avoid needless work.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt (2)

53-66: Consider logging when unregisterTemplate is called for non-existent file.

Line 55 silently returns false when the file doesn't exist. A debug log would help troubleshoot scenarios where a plugin expects a template to exist but it doesn't.

📝 Proposed enhancement
     override fun unregisterTemplate(templateFileName: String): Boolean {
         val destFile = File(Environment.TEMPLATES_DIR, prefixedName(templateFileName))
-        if (!destFile.exists()) return false
+        if (!destFile.exists()) {
+            log.debug("Template not found for unregistration: ${destFile.name}")
+            return false
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt`
around lines 53 - 66, When unregisterTemplate is called and the target destFile
(constructed via File(Environment.TEMPLATES_DIR,
prefixedName(templateFileName))) does not exist, add a debug/info log before
returning false to record that the template was missing for this pluginId; keep
the early return behavior but ensure you log a clear message like "Plugin
{pluginId} attempted to unregister missing template: {destFile.name}" so callers
can trace why false was returned without changing onTemplatesChanged() or the
existing try/catch.

30-51: Consider narrowing exception handling in registerTemplate.

The broad catch (e: Exception) on line 47 catches all exception types. Based on learnings, prefer catching specific exceptions like IOException or SecurityException that are expected from file operations. This aligns with fail-fast behavior during development.

♻️ Proposed fix
         return try {
             cgtFile.copyTo(destFile, overwrite = true)
             log.info("Plugin $pluginId registered template: ${destFile.name}")
             onTemplatesChanged()
             true
-        } catch (e: Exception) {
+        } catch (e: IOException) {
             log.error("Failed to register template for plugin $pluginId", e)
             false
         }

Based on learnings: "prefer narrow exception handling that catches only the specific exception type reported in crashes (such as IllegalArgumentException) instead of a broad catch-all".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt`
around lines 30 - 51, The registerTemplate method currently catches all
exceptions with catch (e: Exception); narrow this to only the expected
file-operation exceptions (e.g., catch IOException and catch SecurityException,
and optionally catch IllegalArgumentException if copyTo can throw it) so we
handle known failure modes and log them, then return false; rethrow or let other
unexpected exceptions propagate instead of swallowing them. Update the try/catch
around cgtFile.copyTo(...) in registerTemplate to use specific catches
(IOException, SecurityException[, IllegalArgumentException]) that call log.error
with the exception and return false, leaving any other exceptions unhandled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt`:
- Around line 80-95: The current build() creates dirName by stripping ASCII
alphanumerics from templateName (templateName.replace(Regex("[^a-zA-Z0-9]"),
"")), which can produce an empty dirName for non-ASCII names; update build() to
ensure dirName is never empty by falling back to a deterministic safe name (for
example: use a normalized/slugified Unicode-safe version of templateName or, if
that results in empty, a short hash/UUID-based fallback like "template_<hash>"),
and then use that dirName for Zip entries; change only the dirName generation
logic in build() so
writeIndex/writeMetadata/writeThumbnail/writeTemplateFiles/writeStaticFiles/writeBinaryFiles
receive a non-empty, filesystem-safe dirName.
- Around line 39-41: thumbnailFromAssets currently calls
context.androidContext.assets.open(assetPath) which can throw IOException; wrap
the asset open/read in a try/catch that catches IOException and rethrows a clear
unchecked exception (e.g., IllegalArgumentException or RuntimeException)
containing the assetPath and the original exception so callers get actionable
info, or alternatively log the error via PluginContext before rethrowing; update
the body of thumbnailFromAssets to perform the try/catch around the open/read
and set this.thumbnail only on success, referencing thumbnailFromAssets,
PluginContext, and assetPath in the error message.
- Around line 97-105: toPebbleSyntax currently double-escapes already-escaped
Pebble sequences (e.g., "${{") because sequential replace calls collide; fix by
implementing a three-step placeholder approach inside toPebbleSyntax: first
replace already-escaped sequences ("${{", "${%", "${#}") with unique temporary
tokens, then perform the normal escaping for raw Pebble syntax ("{{", "{%",
"{#"), and finally restore the temporary tokens back to their original escaped
forms; update the function to use distinct placeholder names to avoid collisions
and ensure all three patterns ({{, {%, {#}) and their pre-escaped forms are
handled.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt`:
- Around line 43-69: The extractCgtFromPath function relies on
classLoader.getResourceAsStream(basePath) to enumerate directory entries which
is unreliable; change it to read an explicit manifest/index file (e.g.,
"$basePath/index.txt" or "templates/index.txt") via
classLoader.getResourceAsStream(manifestPath) and parse its newline-separated
entries, then for each entry call
classLoader.getResourceAsStream("$basePath$entry") as you already do; update the
logic around entries (and the emptyList() fallback) to fail gracefully if the
manifest is missing and document or log that the plugin must include the
manifest; keep references to CGT_EXTENSION, PLUGIN_CGT_PREFIX and the destFile
creation intact.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt`:
- Around line 84-95: In cleanupAllTemplates(), replace the silent files.forEach
{ it.delete() } with a loop that captures the boolean result of File.delete();
for each delete() that returns false, emit a warning log mentioning the file
name (use the class logger/ log or existing logging mechanism), and track
whether any file was actually deleted so onTemplatesChanged() is called only if
at least one deletion succeeded; reference the methods/values prefixedName(""),
Environment.TEMPLATES_DIR, CGT_EXTENSION and the onTemplatesChanged() call when
making the change.

In `@templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt`:
- Around line 346-351: The project name→packageName synchronization is disabled
when showPackageName is false, leaving packageName.value stale for later
consumers (e.g., ModuleTemplateData, ZipRecipeExecutor); move or restore the
observer so projectName.observe always updates packageName by calling
AndroidUtils.appNameToPackageName and packageName.setValue regardless of
showPackageName, i.e., remove the conditional around projectName.observe (or
attach a separate unconditional listener) so packageName.value stays in sync
when projectName changes even if the package input is hidden.

---

Outside diff comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 402-404: The plugin load path in loadPlugin creates and registers
LoadedPlugin but never calls
PluginProjectManager.getInstance().extractBundledCgtTemplates(manifest.id,
classLoader), causing bundled .cgt templates to never be extracted
(cleanupPluginTemplates is called on unload). After successful plugin
initialization and after creating/putting the LoadedPlugin (the block that
constructs LoadedPlugin and sets loadedPlugins[manifest.id]), add a call to
PluginProjectManager.getInstance().extractBundledCgtTemplates(manifest.id,
classLoader) so extraction runs during load and matches cleanupPluginTemplates
during unload.

---

Nitpick comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt`:
- Around line 6-19: Add KDoc comments to the IdeTemplateService interface and
each method (createTemplateBuilder, registerTemplate, unregisterTemplate,
isTemplateRegistered, getRegisteredTemplates, reloadTemplates) to document
expected behavior, parameters and return values for plugin authors; for each
method include a brief description, `@param` entries for parameters (e.g., name,
cgtFile, templateFileName) and `@return` descriptions for Boolean/List results,
and note any side-effects or exceptions (e.g., reloadTemplates mutates internal
state) so implementers and callers have clear contract guidance.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt`:
- Around line 223-226: BinaryFileEntry uses a ByteArray which defaults to
referential equality causing incorrect comparisons/hashing; update
BinaryFileEntry to override equals and hashCode (or convert to a regular class)
so the bytes are compared by content: implement equals to use
bytes.contentEquals(other.bytes) and compare path, and implement hashCode to
combine path.hashCode() with bytes.contentHashCode(); ensure the class still has
same property names (BinaryFileEntry, path, bytes) so usages remain unchanged.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 471-476: The two successive cleanup calls are redundant:
PluginProjectManager.getInstance().cleanupPluginTemplates(pluginId) and
IdeTemplateServiceImpl.cleanupAllTemplates() both remove files with the same
prefix (plugin_{pluginId}_) from Environment.TEMPLATES_DIR; choose one canonical
cleanup path (either keep the specialized
PluginProjectManager.cleanupPluginTemplates(pluginId) and remove the
IdeTemplateServiceImpl.cleanupAllTemplates() call, or make cleanupAllTemplates()
accept a pluginId/targeted scope and call that instead) or add a clear comment
documenting why both are required for different scenarios; update the code to
call only the consolidated method (or adjust signature/behavior of
IdeTemplateServiceImpl.cleanupAllTemplates) and remove the no-op duplicate to
avoid needless work.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt`:
- Around line 53-66: When unregisterTemplate is called and the target destFile
(constructed via File(Environment.TEMPLATES_DIR,
prefixedName(templateFileName))) does not exist, add a debug/info log before
returning false to record that the template was missing for this pluginId; keep
the early return behavior but ensure you log a clear message like "Plugin
{pluginId} attempted to unregister missing template: {destFile.name}" so callers
can trace why false was returned without changing onTemplatesChanged() or the
existing try/catch.
- Around line 30-51: The registerTemplate method currently catches all
exceptions with catch (e: Exception); narrow this to only the expected
file-operation exceptions (e.g., catch IOException and catch SecurityException,
and optionally catch IllegalArgumentException if copyTo can throw it) so we
handle known failure modes and log them, then return false; rethrow or let other
unexpected exceptions propagate instead of swallowing them. Update the try/catch
around cgtFile.copyTo(...) in registerTemplate to use specific catches
(IOException, SecurityException[, IllegalArgumentException]) that call log.error
with the exception and return false, leaving any other exceptions unhandled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d156879-f93e-411f-b2d4-8497c9c9f3db

📥 Commits

Reviewing files that changed from the base of the PR and between f011e8b and 83e0ddc.

📒 Files selected for processing (11)
  • app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt
  • templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt
  • templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt

Comment on lines +84 to +95
fun cleanupAllTemplates() {
val prefix = prefixedName("")
val files = Environment.TEMPLATES_DIR
.listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION }
?: return

files.forEach { it.delete() }

if (files.isNotEmpty()) {
onTemplatesChanged()
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Delete failures are silently ignored in cleanupAllTemplates.

The files.forEach { it.delete() } on line 90 doesn't check or log whether deletion succeeded. A file could remain due to permissions or locks, leaving stale templates.

🛡️ Proposed fix to log delete failures
     fun cleanupAllTemplates() {
         val prefix = prefixedName("")
         val files = Environment.TEMPLATES_DIR
             .listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION }
             ?: return
 
-        files.forEach { it.delete() }
+        var deletedCount = 0
+        files.forEach { file ->
+            if (file.delete()) {
+                deletedCount++
+            } else {
+                log.warn("Failed to delete template file: ${file.name}")
+            }
+        }
 
-        if (files.isNotEmpty()) {
+        if (deletedCount > 0) {
+            log.info("Cleaned up $deletedCount template files for plugin $pluginId")
             onTemplatesChanged()
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun cleanupAllTemplates() {
val prefix = prefixedName("")
val files = Environment.TEMPLATES_DIR
.listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION }
?: return
files.forEach { it.delete() }
if (files.isNotEmpty()) {
onTemplatesChanged()
}
}
fun cleanupAllTemplates() {
val prefix = prefixedName("")
val files = Environment.TEMPLATES_DIR
.listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION }
?: return
var deletedCount = 0
files.forEach { file ->
if (file.delete()) {
deletedCount++
} else {
log.warn("Failed to delete template file: ${file.name}")
}
}
if (deletedCount > 0) {
log.info("Cleaned up $deletedCount template files for plugin $pluginId")
onTemplatesChanged()
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt`
around lines 84 - 95, In cleanupAllTemplates(), replace the silent files.forEach
{ it.delete() } with a loop that captures the boolean result of File.delete();
for each delete() that returns false, emit a warning log mentioning the file
name (use the class logger/ log or existing logging mechanism), and track
whether any file was actually deleted so onTemplatesChanged() is called only if
at least one deletion succeeded; reference the methods/values prefixedName(""),
Environment.TEMPLATES_DIR, CGT_EXTENSION and the onTemplatesChanged() call when
making the change.

Comment on lines +346 to 351
if (showPackageName) {
projectName.observe { name ->
val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
packageName.setValue(newPackage)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep package value synced even when the package field is hidden.

On Line 346, disabling the observer when showPackageName=false leaves packageName.value stale/default, but that value is still consumed later (e.g., ModuleTemplateData on Line 412 and ZIP path replacement in ZipRecipeExecutor). This can generate incorrect package paths/content for hidden-package templates.

Suggested fix
-        if (showPackageName) {
-            projectName.observe { name ->
-                val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
-                packageName.setValue(newPackage)
-            }
-        }
+        projectName.observe { name ->
+            val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
+            packageName.setValue(newPackage)
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (showPackageName) {
projectName.observe { name ->
val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
packageName.setValue(newPackage)
}
}
projectName.observe { name ->
val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
packageName.setValue(newPackage)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt`
around lines 346 - 351, The project name→packageName synchronization is disabled
when showPackageName is false, leaving packageName.value stale for later
consumers (e.g., ModuleTemplateData, ZipRecipeExecutor); move or restore the
observer so projectName.observe always updates packageName by calling
AndroidUtils.appNameToPackageName and packageName.setValue regardless of
showPackageName, i.e., remove the conditional around projectName.observe (or
attach a separate unconditional listener) so packageName.value stays in sync
when projectName changes even if the package input is hidden.

@Daniel-ADFA Daniel-ADFA changed the title Add plugin template contribution API ADFA-3468: Add plugin template contribution API Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants