Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7e24df3
ADFA-4928: Wire up Jetpack Compose in the app module
yaturner Jul 30, 2026
2e71861
ADFA-4928: Port Plugin Manager screen to Compose
yaturner Jul 30, 2026
7b6f8b4
ADFA-4928: Add Templates data layer
yaturner Jul 30, 2026
a62c008
ADFA-4928: Add Templates tab Compose UI
yaturner Jul 30, 2026
81e3797
ADFA-4928: Wire Plugins and Templates tabs together
yaturner Jul 30, 2026
3a9cd97
ADFA-4928: Complete tab wiring (PluginManagerContent refactor + activ…
yaturner Jul 30, 2026
4fd933d
ADFA-4928: Rename the preferences entry to Extensions Manager
yaturner Jul 30, 2026
7207d90
ADFA-4928: Warm Application.filesDir off the main thread
yaturner Jul 31, 2026
755445a
ADFA-4928: Narrow the plugin install file picker to .cgp-like files
yaturner Jul 31, 2026
2cb3d2f
ADFA-4928: Harden TemplateRepositoryImpl file operations
yaturner Aug 5, 2026
35ad990
ADFA-4928: Keep plugin picker/install work off the main thread
yaturner Aug 5, 2026
75d5f44
ADFA-4928: Fix manager UI correctness/accessibility issues
yaturner Aug 5, 2026
98344cc
ADFA-4928: Add KDoc for TemplateMetadata/CgtFileItem
yaturner Aug 5, 2026
46231d6
ADFA-4928: Enable JUnit Jupiter for app unit tests
yaturner Aug 5, 2026
0c9406c
ADFA-4928: Fix regressions CodeRabbit's re-review found in prior fixes
yaturner Aug 5, 2026
2b7be22
ADFA-4928: Address human review comments from hal-eisen-adfa
yaturner Aug 5, 2026
2acedf5
ADFA-4928: Fix double system-bar insets and non-lifecycle-scoped effe…
yaturner Aug 10, 2026
9d124ac
ADFA-4928: Address second review pass from hal-eisen-adfa
yaturner Aug 10, 2026
2b7a636
ADFA-4928: Guard FileImage's downsampling loop against a non-positive…
yaturner Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

Code On The Go (CoGo) is a full Android IDE that runs **on the device** — it edits, builds, and deploys real Android apps offline, embedding a Termux toolchain and running an actual Gradle build in a separate process via the `tooling-api`. It is the maintained successor to AndroidIDE, so the codebase namespace is still `com.itsaky.androidide`.

There is **no single architectural philosophy** across the whole app. This large, layered application is still **predominantly View-based**: newer feature surfaces (plugin manager, AI agent, git, project list) follow a deliberate **Unidirectional Data Flow (UDF)** with Koin DI, `ViewModel` + `StateFlow`, sealed UI-state/effect types, and repositories, while older surfaces still use `LiveData` and talk to GreenRobot EventBus directly. New work follows the UDF pattern documented below, and new UI is built in **Jetpack Compose** ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)) — Compose replaces the view layer only; the UDF stack (ViewModel + `StateFlow`, Koin, repositories) is unchanged. Existing XML/View screens remain until substantially reworked.
There is **no single architectural philosophy** across the whole app. This large, layered application is still **predominantly View-based**: newer feature surfaces (plugin manager, AI agent, git, project list) follow a deliberate **Unidirectional Data Flow (UDF)** with Koin DI, `ViewModel` + `StateFlow`, sealed UI-state/effect types, and repositories, while older surfaces still use `LiveData` and talk to GreenRobot EventBus directly. New work follows the UDF pattern documented below, and new UI is built in **Jetpack Compose** ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)) — Compose replaces the view layer only; the UDF stack (ViewModel + `StateFlow`, Koin, repositories) is unchanged. Existing XML/View screens remain until substantially reworked. The first production example is the **Manager** screen (`PluginManagerActivity`) — merged Plugins/Templates tabs built with `Scaffold`/`TabRow`/`HorizontalPager` (ADFA-4928).

## Core Architecture & Data Flow

Feature code layers as **UI → ViewModel → Repository → data source**, with state flowing up and events/intents flowing down. Koin provides dependencies (`coreModule`, `pluginModule`), constructor-injected into ViewModels.
Feature code layers as **UI → ViewModel → Repository → data source**, with state flowing up and events/intents flowing down. Koin provides dependencies (`coreModule`, `pluginModule`, `templateModule`), constructor-injected into ViewModels.

- **Data sources** — Room (`RecentProjectRoomDatabase` + DAO, `suspend` functions), raw SQLite (`SQLiteOpenHelper`, e.g. `localWebServer/WebServer`), the filesystem/preferences, the embedded `tooling-api` (on-device Gradle), and external clients (Gemini via the Google GenAI SDK, on-device llama.cpp, JGit). Most are exposed through `suspend` functions.
- **Repositories** — e.g. `agent/repository/GeminiRepository`, `repositories/PluginRepository`, `repositories/BreakpointRepository`. They wrap data sources and hide threading/IO from the ViewModel.
- **Repositories** — e.g. `agent/repository/GeminiRepository`, `repositories/PluginRepository`, `repositories/TemplateRepository`, `repositories/BreakpointRepository`. They wrap data sources and hide threading/IO from the ViewModel.
- **ViewModels** — run work in `viewModelScope` on `Dispatchers.IO`, hold a private `MutableStateFlow`/`MutableSharedFlow`, and expose read-only `StateFlow`/`SharedFlow`. One-shot effects (toasts, navigation, dialogs) go through a separate `SharedFlow` of a sealed `*UiEffect` type.
- **UI (Fragments / Activities / Views)** — collect state in a lifecycle-aware coroutine and render it; user actions return to the ViewModel as method calls or sealed `*UiEvent` intents. The existing UI is **Android Views + Fragments + RecyclerView adapters**; new UI is Jetpack Compose ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)). (`compose-preview` previews the *user's* Compose code, not CoGo's own.)
- **UI (Fragments / Activities / Views)** — collect state in a lifecycle-aware coroutine and render it; user actions return to the ViewModel as method calls or sealed `*UiEvent` intents. The existing UI is **Android Views + Fragments + RecyclerView adapters**; new UI is Jetpack Compose ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)) — first used in the Manager screen (`ui/compose/ManagerScreen.kt`, ADFA-4928). (`compose-preview` previews the *user's* Compose code, not CoGo's own.)

```
┌─────────────────────────────────────────────┐
Expand Down Expand Up @@ -83,14 +83,14 @@ These structural facts shape every module. Day-to-day build *commands* live in `
- **SDK levels** (`build-logic/.../build/config/BuildConfig.kt`): `COMPILE_SDK=36`, `MIN_SDK=28`, `TARGET_SDK=28`. **`TARGET_SDK` is deliberately pinned at 28:** higher targets enforce W^X (write-xor-execute), which blocks executing code from app-writable files. That is fatal for an on-device IDE that compiles and runs code (Gradle, `javac`, Termux binaries), so it is a hard requirement, not tech debt. `MIN_SDK_FOR_APPS_BUILT_WITH_COGO=16` is the floor for the apps a *user* builds with CoGo — distinct from CoGo's own `MIN_SDK`.
- **Native asset bundling.** The on-device LLM (`llama-impl`) ships as a per-flavor native AAR, wired through the root `build.gradle.kts` (`bundleLlamaV8Assets` / `assembleV8Assets`, …); prebuilt per-flavor assets live under `assets/release/v7/` and `assets/release/v8/`.
- **Native lib compression** (ADFA-2306, ADFA-4729). The app manifest hard-codes `android:extractNativeLibs="true"` (required: the installer must materialize libs in `nativeLibraryDir`, e.g. `libshizuku.so` is an executable the adb shell runs from there). That attribute overrides the `jniLibs.useLegacyPackaging` DSL, so AGP packages `lib/<abi>/*.so` deflate-compressed in **every** APK — ~5.9 MB smaller (`libtree-sitter-kotlin.so` alone is 4.18 MB → 339 kB). The trap is the `recompressApk` post-step (release always, debug in CI only): its no-compress lists in `app/build.gradle.kts` must NOT contain `"so"`, or it silently re-stores the libs and undoes the saving — which is what ADFA-2306 fixed for release and ADFA-4729 for CI debug. Locally built debug APKs (including the e2e farm's) never run that step and were always fine.
- **`app` package layout is by concern, not feature:** `activities`, `fragments`, `services`, `di`, `agent`, `viewmodel(s)`, `repositories`, `roomData`, `localWebServer`, `preferences`, `ui`, `utils`, ….
- **`app` package layout is by concern, not feature:** `activities`, `fragments`, `services`, `di`, `agent`, `viewmodel(s)`, `repositories`, `roomData`, `localWebServer`, `preferences`, `ui` (Compose screens live under `ui/compose`), `templates/manager` (the Manager screen's `.cgt`-parsing data layer, with direct filesystem access to `Environment.TEMPLATES_DIR` — distinct from the plugin-facing `IdeTemplateService` in `plugin-api`/`plugin-manager`), `utils`, ….

## Technology Stack

| Concern | Library / Approach |
|---|---|
| UI | **Jetpack Compose for all new UI** ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)). The existing majority is still Android Views + Fragments + `RecyclerView` (Material Components); those legacy screens stay until reworked, but new IDE UI is Compose-only. |
| Dependency Injection | **Koin** (`org.koin`) — `coreModule`/`pluginModule`, `startKoin` in `IDEApplication`, plus a `ServiceLocator : KoinComponent` for lazy post-startup access. No Hilt/Dagger. |
| UI | **Jetpack Compose for all new UI** ([ADR 0009](docs/adr/0009-jetpack-compose-for-new-ui.md)); first production screen is the Manager screen (Plugins/Templates tabs, `app/.../ui/compose/`, ADFA-4928). The existing majority is still Android Views + Fragments + `RecyclerView` (Material Components); those legacy screens stay until reworked, but new IDE UI is Compose-only. |
| Dependency Injection | **Koin** (`org.koin`) — `coreModule`/`pluginModule`/`templateModule`, `startKoin` in `IDEApplication`, plus a `ServiceLocator : KoinComponent` for lazy post-startup access. No Hilt/Dagger. |
| Asynchronous work | **Kotlin Coroutines + Flow** (`StateFlow`/`SharedFlow`, `viewModelScope`, app-scoped `CoroutineScope(SupervisorJob() + Dispatchers.IO)`); **GreenRobot EventBus** for cross-subsystem events. |
| Networking | Offline-first; no general REST layer. External I/O is **Google GenAI SDK** (Gemini), **on-device llama.cpp**, and **JGit** (git). Retrofit is in the catalog but effectively unused in app code. |
| Database / Persistence | **Room** is the default for relational/queryable data; **filesystem + preferences (DataStore)** for non-relational settings. **Raw SQLite** (`SQLiteDatabase` / `SupportSQLiteOpenHelper`) only for justified exceptions (see policy below). |
Expand Down
23 changes: 23 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ plugins {
// Sentry gradle plugin; the SDK it wires up reports to our GlitchTip backend.
alias(libs.plugins.sentry)
alias(libs.plugins.google.services)
alias(libs.plugins.kotlin.compose)
}

fun propOrEnv(name: String): String =
Expand Down Expand Up @@ -93,6 +94,9 @@ android {
// Skip TreeSitter native library loading in tests
it.systemProperty("java.library.path", System.getProperty("java.library.path"))
it.systemProperty("androidide.test.mode", "true")
// JUnit Platform, so JUnit Jupiter tests run; the vintage engine dependency
// below keeps existing JUnit 4/Robolectric tests running unchanged.
it.useJUnitPlatform()
}
}
}
Expand Down Expand Up @@ -180,6 +184,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}

buildFeatures {
compose = true
}
}

// Sentry gradle plugin config (crash reporting to GlitchTip).
Expand Down Expand Up @@ -263,6 +271,17 @@ dependencies {
implementation(libs.google.flexbox)
implementation(libs.libsu.core)

// Compose (plugin/template manager screen; ADR 0009)
implementation(platform(libs.compose.bom))
implementation(libs.compose.runtime)
implementation(libs.compose.ui)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
implementation(libs.compose.activity)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.lifecycle.runtime.compose)
debugImplementation(libs.compose.ui.tooling)

// Kotlin
implementation(libs.androidx.core.ktx)
implementation(libs.common.kotlin)
Expand Down Expand Up @@ -325,6 +344,10 @@ dependencies {

testImplementation(projects.testing.unit)
testImplementation(libs.core.tests.anroidx.arch)
testImplementation(libs.tests.junit.jupiter)
testRuntimeOnly(libs.tests.junit.platformLauncher)
// Keeps existing JUnit 4/Robolectric tests running under the JUnit Platform.
testRuntimeOnly(libs.tests.junit.vintageEngine)
androidTestImplementation(projects.common)
androidTestImplementation(projects.testing.android) {
exclude(group = "com.google.protobuf", module = "protobuf-lite")
Expand Down
Loading
Loading