Android/APIExample-Compose/.agent/skills/upsert-case/SKILL.md
Add a new API example case or modify an existing one in the APIExample-Compose Android demo — creates or updates a Kotlin Composable file, registers or updates it in Examples.kt, and manages string resources. Use when: adding a new Agora RTC API demo screen in Jetpack Compose, modifying an existing case's implementation or registration, porting an existing APIExample case to Compose, implementing a new feature example in Kotlin + Compose UI, registering a new entry in BasicExampleList or AdvanceExampleList, or updating an existing case's strings or Examples.kt entry. Kotlin only — no XML layouts, no Fragments. Keywords: add case, modify case, update case, new composable, Examples.kt, BasicExampleList, AdvanceExampleList, APIExample-Compose, Compose case, new screen, Jetpack Compose, RTC API example, upsert case.
npx skillsauth add agoraio/api-examples upsert-caseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Touch exactly 3 files (all paths relative to app/src/main/):
| File | What to add |
|---|---|
| java/.../compose/samples/YourCaseName.kt | Composable file |
| java/.../compose/model/Examples.kt | 1 list entry |
| res/values/strings.xml | 1 string |
No nav_graph.xml changes — navigation routes by list position automatically.
Before writing a single line, ask:
JoinChannelVideo.kt for video, JoinChannelAudio.kt for audio)CAMERA + RECORD_AUDIO vs RECORD_AUDIO only), whether enableVideo() and VideoGrid are neededquery-cases skill to see current entries; list order is display orderMANDATORY — READ ENTIRE FILE before writing any code:
references/composable-template.kt
Do NOT skip — the SettingPreferences.getArea(), DisposableEffect key, rememberSaveable vs remember rules, and @Preview placement are only fully shown there and are required in every case.
Do NOT load any other reference files for this task.
Non-obvious points the template highlights:
mAreaCode = SettingPreferences.getArea() — required, do not hardcode or omitDisposableEffect(lifecycleOwner) — key must be lifecycleOwner, not Unit; wrong key means cleanup never fires on back navigationrememberSaveable for channelName, isJoined, uid, videoIdList — survives rotationremember for RtcEngine — must NOT be rememberSaveable (engine is not serializable)IRtcEngineEventHandler callbacks can mutate Compose state directly — snapshot system is thread-safe, no runOnUIThread() neededToast/Dialog/AlertDialog inside callbacks still need main thread — use coroutineScope.launch(Dispatchers.Main) { }@Preview goes on the private *View function only — never on the public stateful entryFile: app/src/main/java/io/agora/api/example/compose/model/Examples.kt
val AdvanceExampleList = listOf(
// … existing entries …
Example(R.string.example_your_case_name) { YourCaseName() }
)
List order is display order — position determines where the case appears in the UI.
File: app/src/main/res/values/strings.xml
<string name="example_your_case_name">Your Case Name</string>
String key must use the example_ prefix. No separate tips string needed (unlike APIExample).
Add one line to the case list in ARCHITECTURE.md under the correct directory section:
├── YourCaseName.kt # "Display Name" — key API description
Keep the format consistent with existing entries. This file is the fast-lookup index used by query-cases — keeping it current avoids full directory scans.
When modifying an existing case rather than creating a new one, identify which files need changes based on what you are updating:
| What changed | Files to touch |
|---|---|
| Implementation logic (API calls, event handling, Compose state) | java/.../compose/samples/CaseName.kt |
| Display name | res/values/strings.xml |
| List group (Basic ↔ Advance) or position | java/.../compose/model/Examples.kt (move entry between lists or reorder) |
| Composable function rename | CaseName.kt (file + function name), Examples.kt (lambda reference), ARCHITECTURE.md |
After making changes:
Examples.kt entry consistency — ensure the string resource reference, composable lambda, and list placement (BasicExampleList or AdvanceExampleList) still match the actual case. A mismatch causes the case to silently disappear from the list or render the wrong screen.res/values/strings.xml if the display name changed.ARCHITECTURE.md — update the Directory Layout entry and the Case Index table row to reflect any changes to the case name, path, Key APIs, or description../gradlew assembleDebug
isJoined flips to trueRtcEngine.destroy within ~2 seconds; if missing, DisposableEffect key is wrong or onDispose is incompletechannelName and isJoined survive (rememberSaveable working)ARCHITECTURE.md Case Index table is updated — row added (new case) or row updated (modified case) with correct Case, Path, Key APIs, and DescriptionExamples.kt entry is consistent — string resource, composable lambda, and list placement match the actual caseIf the case meets any of the following criteria, create a Spec rather than using this skill directly:
If none apply → use this skill directly; no Spec needed.
APIExample-Composeremember vs rememberSaveable boundaries)Examples.kt list entry, strings.xml key (example_ prefix) — finalize during design to avoid conflictsARCHITECTURE.md or use the query-cases skill to check existing entriesDisposableEffect(lifecycleOwner), rememberSaveable vs remember, main-thread dispatch for Toast/Dialogupsert-case skill, and provide skill input parametersFragment, or ViewBinding — Compose only.remember for channelName, isJoined, or uid — they must be rememberSaveable to survive rotation.rememberSaveable for RtcEngine — it is not serializable and will crash on rotation.Unit as the DisposableEffect key — it fires only once and won't clean up on back navigation. Always use lifecycleOwner.@Preview on the public stateful function — it will crash because LocalContext and LocalLifecycleOwner are unavailable in preview. Only preview the private *View function.Toast/Dialog/AlertDialog directly inside IRtcEngineEventHandler callbacks — they require the main thread. Use coroutineScope.launch(Dispatchers.Main) { }.mAreaCode — always use SettingPreferences.getArea().development
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including dialog class structure, message map registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
development
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including file structure, registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.