Skip to content
Mobile
Skill

/android-expert

Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 + tools/r8-verify, permissions, Intent/Context ports. Use when editing amethyst/. NOT for screens/navigation (compose-expert).

From plugin
amethyst
1.6k12 skills

Claude Code

$ npx -y skills add vitorpamplona/amethyst --skill android-expert --agent claude-code

SKILL.md

android-expert.SKILL.md
name: android-expert
description: Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 + tools/r8-verify, permissions, Intent/Context ports. Use when editing amethyst/. NOT for screens/navigation (compose-expert).

android-expert

`amethyst/` is shrinking to an Android shim: entry points, services, system integrations and the actuals of shared ports. Screens, the nav host and the bar / rail / drawer are shared UI in `commonsUI` (see `compose-expert`). A new screen goes there even when only Android shows it.

Source root: `amethyst/src/main/java/com/vitorpamplona/amethyst/` (an Android app module, not KMP: there is no `androidMain` here). Flavor code is in `amethyst/src/play/` and `amethyst/src/fdroid/`.

Entry points

  • `Amethyst.kt`: the `Application`. Builds `AppModules` (`Amethyst.instance`) in the main

process only.

  • `ui/MainActivity.kt`: the single main Activity (`launchMode="singleInstance"`).

`onCreate` calls `enableEdgeToEdge()` and sets `AmethystTheme { ... AmethystApp(Amethyst.instance.sessionManager, AndroidAppRoot) }`. Everything from there down is shared UI.

  • `ui/AndroidAppRoot.kt`: `object AndroidAppRoot : AppRoot`, Android's implementation of the

shared shell port (`commonsUI/.../commons/ui/app/AppRoot.kt`). It registers the Android-only destinations (`androidDestinations` in `ui/navigation/AppNavigation.kt`) and runs `NavigateIfIntentRequested`.

  • `ui/platform/AndroidAppPlatform.kt` (`AppPlatform`) and `ui/platform/AndroidAppServices.kt`

(`AppServices`, the interface is in `commons/.../commons/service/AppServices.kt`): the other two ports the shared UI calls.

  • Other activities have their own windows: `ui/call/CallActivity`,

`ui/screen/loggedIn/nests/room/activity/NestActivity`, `service/playback/pip/PipVideoActivity`, the signer / napplet consent activities, and `HealthConnectRationaleActivity`.

Two processes

The app runs in **main** and **`:napplet`**. Read `references/two-processes.md` before touching `Amethyst.kt`, an Application callback, or anything under `napplet/`. The sandbox activities and services (`NappletHostActivity`, `NappletBrowserActivity`, `NappletHostService`, `NappletBrowserService`) live in the `:nappletHost` module and are declared with `android:process=":napplet"`. The broker side (`napplet/NappletBrokerService.kt`, `NappletLaunchRegistry`, consent) stays in `amethyst/`.

Manifest and intents

`amethyst/src/main/AndroidManifest.xml`; each flavor merges its own `AndroidManifest.xml`.

  • `MainActivity` takes `MAIN`, `VIEW` for `nostr:`, `amethyst:`, `nostrconnect:`, the

wallet-connect schemes and several `https` hosts, and `SEND`.

  • **Share targets are `<activity-alias>`es of MainActivity** (`.ui.ShareAsDMAlias`,

`ShareAsPictureAlias`, `ShareAsShortVideoAlias`, `ShareAsVideoAlias`, `ShareAsHighlightAlias`, `ScanQrCodeAlias`). Which one the user picked is visible only in the launching intent's component class name, which `ShareIntentRouting.targetOf` in `commonsUI/.../commons/ui/navigation/ShareIntentRouting.kt` maps to a `ShareTarget`. Renaming an alias without updating its constant there silently routes the share to New Post. Adding a target means an alias, a constant, and a `ShareTarget` entry.

  • `NavigateIfIntentRequested` (`ui/navigation/AppNavigation.kt`) turns `VIEW` / `SEND` /

`SEND_MULTIPLE` / NFC intents into `nav.newStack(...)` calls.

  • Foreground services (`CallForegroundService`, `NestForegroundService`, `PlaybackService`,

`NotificationRelayService`, ...) read their notification strings synchronously, which is the one case where a string may stay in Android `res/`. See `AGENTS.md` "Strings" and `amethyst/src/main/res/AGENTS.md`.

Intent / Context access from shared UI

Shared composables never touch `Context` directly. Use the existing shape: a small interface plus an `expect fun rememberX()` in `commonsUI` `commonMain`, with the Android actual in `commonsUI/src/androidMain`. Examples: `TextSharer` / `rememberTextSharer()` (the actual builds the `ACTION_SEND` chooser from `LocalContext`), `FileSharer`, `PlatformBackHandler`, `HideFromScreenshots`, `rememberCoarseLocationPermission(): PlatformPermission` (the actual wraps Accompanist's `rememberPermissionState`). When the behavior needs app-only state (an Activity class, a service, `Amethyst.instance`), put a member on `AppPlatform` / `AppServices` and implement it in `AndroidAppPlatform` / `AndroidAppServices`, as `openNestRoom` does with `NestActivity.launch`.

Flavors: play vs fdroid

`amethyst/build.gradle.kts` declares `flavorDimensions += "channel"` with `play` (default) and `fdroid`. They differ in `BuildConfig.IS_CASTING_AVAILABLE` and in their dependencies:

  • `play`: Firebase messaging, ML Kit (language id, translate, GenAI proofreading / rewriting /

prompt / image description), Play Services Cast, androidx AppFunctions.

  • `fdroid`: UnifiedPush and no Google services.

Classes that `main` calls exist in **both** `src/play/java/...` and `src/fdroid/java/...` with the same name and signature (`PushNotificationUtils`, `SelectNotificationProvider`, `LanguageTranslatorService`, `WritingAssistantFactory`, `ChromecastCaster`, `TermsGate`, ...). Add or change one side and the other flavor stops compiling. Build both when you touch them. Play-only code (`appfunctions/AmethystAppFunctions.kt`, `AmethystCastOptionsProvider`) needs no twin as long as `main` never references it. Build types: `debug` (`.debug` suffix), `release`, and `benchmark` (release config, debug-signed, profileable).

Gradle details beyond this (catalog, versions, KMP source sets) belong to `kotlin-multiplatform`.

Images (Coil)

`service/images/ImageLoaderSetup.kt` installs the singleton Coil loader. It is called from `AppModules.setImageLoader()` with a `callFactory` built from `roleBasedHttpClientBuilder.okHttpClientForImage`, so Tor routing and the "image" traffic tag apply. Cha

Read more
@vitorpamplona
Ships withamethyst

Nostr client for Android

Stars on GitHub

Momentum

stars in total
1.6k
Star history of vitorpamplona/amethyst on GitHub, a chart by star-history.com

Other skills on amethyst.

amy-expert

amy-expert

amy CLI in cli/: adding an `amy <verb>` command, Main.kt dispatch, Output.emit/--json contract, exit codes 0/1/2/124, ~/.amy…

auth-signers

auth-signers

NostrSigner in Amethyst: NostrSignerInternal (local key), NostrSignerRemote (NIP-46 bunker), NostrSignerExternal (NIP-55 Android…

compose-expert

compose-expert

Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back…

desktop-expert

desktop-expert

Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging.…

More skills from other plugins.

deep-code-review

deep-code-review

In-depth design-focused code review - understands codebase context before evaluating PR changes, posts structured feedback to…

expo1d53k
@expo@expoInstall Skill
modlens

modlens

Plug-in vision for text-only models. Hard rule: when a file path or URL with an image extension (.png, .jpg, .jpeg, .webp, .gif,…

modlens2w4.6k
@liustack@liustackInstall Skill
appllama-app-design-skill

appllama-app-design-sk…

Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile…

appllama2mo3.9k
@appllama@appllamaInstall Skill

Questions about android-expert.

Which coding agents can use android-expert?
android-expert installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.