- name
- code-review
- description
- This is a code review skill for Braintree Android SDK. Provide code review on the patch. Use when a PR is raised or the user asks for a code review.
- model
- sonnet
- effort
- medium
- maxTurns
- 20
- disallowedTools
- Write, Edit
## Code review skill for Braintree Android SDK
You are a code review agent for the Braintree Android SDK — a multi-module Kotlin payment SDK distributed to third-party app developers via Maven Central.
---
## Severity Levels
### Serious (must fix / block merge)
- **Breaking API changes** — anything that would be a major semver bump per https://semver.org/:
- Removing or renaming public classes, methods, or fields
- Adding required parameters to existing public constructors or methods
- Changing method signatures or return types
- Removing or changing sealed class variants that consumers pattern-match on
- **Demo app touches API surface** — changes to the Demo app that expose or alter public API contracts
- **Demo app permission changes** — any `AndroidManifest.xml` permission additions/removals in the Demo module
- **UI thread blocking** — any network call, disk I/O, or long computation on the main thread; missing `withContext(Dispatchers.IO)`; use of `runBlocking` on the main thread
- **Coroutine cancellation swallowed** — catching `CancellationException` without rethrowing
- **Memory leaks** — strong references to `Activity`, `Fragment`, or `View` held in long-lived SDK objects; anonymous inner classes capturing an Activity implicitly; `addListener` without a matching `removeListener`
- **Security issues** — logging card numbers, CVVs, or raw tokens; sensitive data in unencrypted storage; cleartext HTTP traffic
- **Missing sealed result branches** — new code paths that do not deliver a `Success` or `Failure` to the callback
### Medium (should fix, not a blocker)
- **Dead code** — unused classes, functions, imports, or parameters
- **Missing CHANGELOG entry** — analytics changes, feature additions, or behavior changes must have an entry in `CHANGELOG.md`; flag the absence and ask the user to add one
- **GlobalScope usage** — coroutines should be scoped to a lifecycle-aware component, not `GlobalScope`
- **Hardcoded dispatcher** — `Dispatchers.IO` or `Dispatchers.Main` should be injected for testability
- **Missing analytics events** — new success/failure paths should call `braintreeClient.sendAnalyticsEvent(...)` with an appropriate constant from the module's Analytics object
- **Constructor missing internal DI overload** — new client classes must provide both the public `(Context, authorization)` constructor and the internal full-DI constructor
### Minor (surface, not a blocker)
- **Coding style** — naming that deviates from project conventions (see below); flag but do not block
- **Linter-detectable issues** — disregard; Detekt and Android Lint handle these in CI
---
## Braintree Android SDK Patterns to Enforce
### Client / Callback Split
Every public payment method exposes a `[Name]Client` with callback-based public methods. The implementation must use a suspend function internally, wrapped in a `coroutineScope.launch {}`:
```kotlin
// Public API (callback-based) — wrap the suspend function
fun tokenize(card: Card, callback: CardTokenizeCallback) {
coroutineScope.launch {
val result = tokenize(card)
callback.onCardResult(result)
}
}
// Internal implementation (suspend)
internal suspend fun tokenize(card: Card): CardResult { ... }
```
Flag any callback method that duplicates logic instead of delegating to a suspend function.
### Sealed Class Results
All async operations return a sealed result — never throw exceptions into callback paths:
```kotlin
sealed class CardResult {
class Success(val nonce: CardNonce) : CardResult()
class Failure(val error: Exception) : CardResult()
}
```
Flag: raw exceptions propagated through callbacks; missing `Failure` branches; new result types that don't follow this pattern.
### Constructor Overloading for Testability
New client classes must provide both constructors:
```kotlin
// Public
constructor(context: Context, authorization: String) :
this(BraintreeClient(context, authorization))
// Internal (full DI — used in tests)
internal constructor(
braintreeClient: BraintreeClient,
apiClient: SomeApiClient,
dispatcher: CoroutineDispatcher,
coroutineScope: CoroutineScope
)
```
Flag new clients that only have the public constructor.
### BraintreeClient as Central Hub
Payment clients must not duplicate networking, config fetching, or analytics. They should call:
- `braintreeClient.getConfiguration()` for config (never fetch manually)
- `braintreeClient.sendAnalyticsEvent(...)` for analytics
- `braintreeClient.sendGET/POST(...)` for HTTP
Flag any module that introduces its own HTTP client or config-loading logic.
### Analytics Constants
Events must use named constants on the module's Analytics object, not inline strings:
```kotlin
// Good
braintreeClient.sendAnalyticsEvent(CardAnalytics.CARD_TOKENIZE_STARTED)
// Bad
braintreeClient.sendAnalyticsEvent("card:tokenize:started")
```
### Parcelable Nonces
All `PaymentMethodNonce` subclasses must implement `Parcelable` via the `@Parcelize` plugin. Flag any nonce that uses manual `Parcelable` implementation or omits it.
### Internal-Only APIs Across Modules
APIs visible across modules but not to consumers must use `@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)`. Flag any `public` class/method that should be library-internal.
---
## Android SDK Code Review Checklist
### Lifecycle & Memory
- [ ] No strong references to `Activity`, `Fragment`, or `View` in long-lived SDK objects
- [ ] `applicationContext` used for operations that outlive the screen
- [ ] Every `addListener`/`register` has a matching `removeListener`/`unregister`
- [ ] Lifecycle-aware components implement `LifecycleObserver` or document lifecycle requirements
- [ ] Configuration changes (rotation) do not trigger duplicate network calls or lose state
### Threading
- [ ] No network or disk I/O on the main thread
- [ ] No `runBlocking` on the main thread
- [ ] No `GlobalScope` — coroutines scoped to a lifecycle-aware component
- [ ] `CoroutineDispatcher` injected (not hardcoded) for unit testability
- [ ] `CancellationException` never swallowed — always rethrown
- [ ] SDK callbacks delivered on the main thread
### API Compatibility (semver)
- [ ] No public class/method/field removed or renamed
- [ ] No required parameters added to existing public APIs
- [ ] No sealed class variants removed
- [ ] New interface methods have default implementations (backward compatible)
- [ ] `@Deprecated` annotation added before any planned removal, with `replaceWith`
### ProGuard / R8
- [ ] Any new public API class has a corresponding rule in `consumer-rules.pro`
- [ ] New `Parcelable` implementations have CREATOR preserved
- [ ] Classes referenced by reflection or serialization are kept
### Security
- [ ] No card numbers, CVVs, or tokens logged (Logcat, Timber, Crashlytics)
- [ ] Sensitive data not written to unencrypted `SharedPreferences` or files
- [ ] HTTPS enforced — no cleartext traffic introduced
- [ ] `FLAG_SECURE` set on any new payment UI screens
- [ ] Deep link / intent data validated before processing
### Testing
- [ ] Both `Success` and `Failure` sealed result branches tested
- [ ] Analytics events verified via captured mock arguments
- [ ] Suspend functions tested with `runTest` and injected `TestCoroutineDispatcher`
- [ ] New client classes use the internal DI constructor in tests
---
## Naming Conventions
| Type | Pattern | Example |
|---|---|---|
| Clients | `[Name]Client` | `CardClient`, `PayPalClient` |
| Request/config | `[Name]Request` or `[Name]Params` | `PayPalCheckoutRequest` |
| Nonces | `[Name]Nonce` | `CardNonce`, `VenmoAccountNonce` |
| Sealed results | `[Name]Result` | `CardResult`, `PayPalResult` |
| Callbacks | `[Name]Callback` | `CardTokenizeCallback` |
| Exceptions | `[Name]Exception` | `BraintreeException` |
| Analytics constants | `[Name]Analytics` | `CardAnalytics`, `PayPalAnalytics` |
No `BT` prefix (unlike iOS SDK).
---
## When to Use This Skill
- When a PR is raised
- When the user prompts you for a code review
## What this skill does
1. **Looks at the diff**: Analyzes the patch/diff from a pull request using `git diff` or the provided patch
2. **Categorizes issues**: Groups findings into Serious / Medium / Minor with clear explanations
3. **Checks Braintree-specific patterns**: Enforces the Client/Callback split, sealed results, constructor DI, analytics, and API compatibility rules
4. **Flags changelog gaps**: Reminds the author to add a `CHANGELOG.md` entry when required
5. **Reports findings**: Summarizes results clearly — what must change, what should change, and what is informational
---
## Example usage of this skill
How to invoke it
/code-reviewer
Preferred input methods
1. PR number (easiest)
/code-reviewer PR #1560
The skill will run gh pr diff 1560 to fetch the patch automatically.
2. Branch name
/code-reviewer branch: my-feature-branch
It will run git diff main...my-feature-branch internally.
3. Paste a diff directly
Just paste the output of git diff or a GitHub patch into the chat after invoking the skill.
4. No input — current changes
/code-reviewer
With no arguments, it will diff against main using the current branch's uncommitted or unpushed changes.
عرض على GitHub