بنقرة واحدة
review-case
// Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
// Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including file structure, per-example storyboard creation, registration, and ARCHITECTURE.md updates.
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including dialog class structure, registration in APIExampleDlg, localization wiring, and ARCHITECTURE.md updates.
Add a new API example case or modify an existing one in the APIExample Android demo — creates or updates Fragment class, XML layout, string resources, and nav_graph registration. Use when: adding a new Agora RTC API demo screen, modifying an existing case's implementation or registration, implementing a new feature example in Java + XML layouts, registering a new case via @Example annotation, subclassing BaseFragment for a new demo screen, or updating an existing case's strings, layout, or nav entry. Keywords: add case, modify case, update case, new fragment, nav_graph, @Example, BaseFragment, APIExample, new screen, demo case, RTC API example.
Add a new audio API example case or modify an existing one in the APIExample-Audio Android demo — creates or updates Fragment class, XML layout, string resources, and nav_graph registration. Use when: adding a new Agora audio API demo screen, modifying an existing case's implementation or registration, implementing a new audio feature example in Java + XML layouts, registering a new case via @Example annotation, subclassing BaseFragment for a new audio demo screen, or updating an existing case's strings, layout, or nav entry. This project uses voice-sdk — no video APIs available. Keywords: add case, modify case, update case, new fragment, nav_graph, @Example, BaseFragment, APIExample-Audio, audio case, voice-sdk, new screen, audio demo, upsert case.
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.
| name | review-case |
| description | Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly. |
| compatibility | ["Cursor","Kiro","Windsurf","Claude","Copilot"] |
| license | MIT |
| metadata | {"author":"APIExample Team","version":"1.0.0","platform":"macOS"} |
Use this skill when you need to:
Check:
initializeAgoraEngine() or similarAgoraRtcEngineConfigleaveChannel() is called before destroy()destroy() is called in viewWillClose() or cleanup methodCorrect Pattern:
override func viewDidLoad() {
super.viewDidLoad()
initializeAgoraEngine() // Create once
}
override func viewWillClose() {
leaveChannel()
agoraKit.destroy()
super.viewWillClose()
}
func joinChannel() {
agoraKit.joinChannel(byToken: token, channelName: channel, info: nil, uid: 0)
}
func leaveChannel() {
agoraKit.leaveChannel(nil)
}
Incorrect Pattern:
See references/incorrect-lifecycle.swift for common mistakes.
Check:
DispatchQueue.main.asyncCorrect Pattern:
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
// Callback may arrive on background thread
DispatchQueue.main.async {
self.statusLabel.stringValue = "Joined channel"
}
}
Incorrect Pattern:
See references/incorrect-thread-safety.swift for common mistakes.
Check:
enableAudio()enableVideo()AVAudioSession.sharedInstance().requestRecordPermissionCorrect Pattern:
func initializeAgoraEngine() {
// Request permissions first
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted {
self.agoraKit.enableVideo()
}
}
AVCaptureDevice.requestAccess(for: .audio) { granted in
if granted {
self.agoraKit.enableAudio()
}
}
}
Check:
joinChannel() failures are handledCorrect Pattern:
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
DispatchQueue.main.async {
self.showError("Error: \(errorCode.rawValue)")
}
}
func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
// Refresh token before expiration
let newToken = KeyCenter.Token(channelName: self.channelName)
self.agoraKit.renewToken(newToken)
}
Check:
<ExampleName>MainBaseViewController@IBOutlet or var// MARK: sectionsCorrect Pattern:
class ScreenShareMain: BaseViewController {
var agoraKit: AgoraRtcEngineKit!
var remoteUid: UInt = 0
@IBOutlet weak var Container: AGEVideoContainer!
// MARK: - Lifecycle
override func viewDidLoad() { ... }
// MARK: - Agora Engine Setup
func initializeAgoraEngine() { ... }
// MARK: - Actions
@IBAction func joinButtonTapped(_ sender: Any) { ... }
}
Check:
Correct Pattern:
// Correct order: enable -> setup -> join
agoraKit.enableVideo()
agoraKit.setupLocalVideo(AgoraRtcVideoCanvas(uid: 0))
agoraKit.joinChannel(byToken: token, channelName: channel, info: nil, uid: 0)
Incorrect Pattern:
// ❌ Wrong order
agoraKit.joinChannel(...) // Join first
agoraKit.enableVideo() // Enable after join (too late)
Check:
Correct Pattern:
func leaveChannel() {
agoraKit.stopAudioMixing() // Stop audio
agoraKit.stopScreenCapture() // Stop screen share
agoraKit.leaveChannel(nil)
}
override func viewWillClose() {
leaveChannel()
agoraKit.destroy()
super.viewWillClose()
}
When reviewing, provide feedback in this format:
## Review Results
### ✅ Passed
- Engine lifecycle correctly managed
- Thread safety ensured with DispatchQueue.main.async
- Permissions requested before device access
### ⚠️ Issues Found
**[HIGH] Thread Safety Issue**
- File: `ScreenShare.swift`
- Line: 45
- Issue: UI update in delegate callback without DispatchQueue.main.async
- Suggestion: Wrap UI update with `DispatchQueue.main.async { ... }`
**[MEDIUM] Missing Error Handling**
- File: `ScreenShare.swift`
- Line: 78
- Issue: joinChannel() result not checked
- Suggestion: Implement `rtcEngine(_:didOccurError:)` delegate method
### 🔧 Recommendations
- Add logging for debugging
- Consider adding retry logic for network failures
Check:
Correct Pattern:
// macOS: Use Cocoa
import Cocoa
import AgoraRtcKit
class ExampleMain: BaseViewController {
@IBOutlet weak var Container: AGEVideoContainer!
// Cocoa-based UI
}
Incorrect Pattern:
// ❌ iOS patterns in macOS
import UIKit // Wrong framework
class ExampleMain: UIViewController { } // Wrong base class
Do NOT accept:
leaveChannel() before destroy()APIExample/Examples/[Basic|Advanced]/ structureUse this checklist when reviewing an example:
Lifecycle:
leaveChannel() called before destroy()destroy() called in cleanupThread Safety:
DispatchQueue.main.asyncPermissions:
Error Handling:
Code Quality:
API Usage:
Resources:
Platform:
Cause: destroy() called without leaveChannel() first
Fix: Always call leaveChannel() before destroy()
Cause: Direct UI update from background thread
Fix: Wrap with DispatchQueue.main.async { ... }
Cause: destroy() not called or engine recreated
Fix: Ensure destroy() in viewWillClose() and create engine once
Cause: No token refresh handling
Fix: Implement tokenPrivilegeWillExpire() delegate method
Cause: Permissions not requested
Fix: Request permissions before enableAudio() / enableVideo()
APIExample/Examples/Basic/JoinChannelVideo/ for referenceAPIExample/Common/ for base class implementation