jc-mobile-debug
Use when reviewing or debugging the JapaConnect Flutter mobile app (apps/mobile). Diagnoses widget rebuild loops, Riverpod state scoping, API client drift, JWT refresh loops, secure-storage token issu
Files1
View on GitHubSKILL.md.claude/skills/jc-mobile-debug/SKILL.md
GitHubJapaConnect Mobile App — Debug Skill
You are debugging the Flutter mobile client. The app is a thin /api/v1 client in apps/mobile/ with a shared packages/japaconnect_core/ package. Most bugs are in one of three layers: UI/rendering, state/HTTP, or toolchain/runtime. Isolate the layer first, then apply the matching diagnostic path.
Step 0 — Confirm the Environment
flutter doctormust pass for the platform you are debugging (Android SDK / Xcode / CocoaPods).- The backend must be running (
go run ./...ormake dev) at the URL the mobile client points to. - The mobile app must build and run:
- Android:
flutter run --flavor devorflutter runfromapps/mobile/ - iOS:
flutter run -d ios(macOS + Xcode required; Linux agents cannot build iOS)
- Android:
- If
apps/mobile/does not exist, the mobile app is not yet implemented — stop and report that, do not fabricate a fix.
1. UI / Rendering Bugs
1.1 Layout overflow (A RenderFlex overflowed by ... pixels)
- Wrap the overflowing widget in
Expanded/Flexibleinside aColumnorRow. - For horizontal lists, use
ListViewwithscrollDirection: Axis.horizontaland bounded item widths. - For long text, wrap in
ExpandedwithText(maxLines: ..., overflow: TextOverflow.ellipsis). - Check
SafeAreausage at screen edges and notches. - Test on the smallest Android device the audience uses (low-end screen height ~640dp).
1.2 Unbounded constraints (BoxConstraints forces an infinite width/height)
- Common in
ListViewinsideColumnwithoutExpanded. - Common in
Row/Columncontaining aListViewdirectly. - Fix: give the scrollable a bounded size (e.g.,
Expanded(child: ListView(...))).
1.3 Widget rebuild loops
- Check for
setStateinsidebuildor inside anasynccallback that fires repeatedly. - With Riverpod: ensure providers are not being watched inside a
buildmethod in a way that triggers self-mutation. - Use
constconstructors for static subtrees. - Use
RepaintBoundaryaround heavy animated content if needed.
1.4 Images not loading / wrong aspect
- Verify
CachedNetworkImagehasplaceholderanderrorWidget. - Verify the image URL is absolute and the backend serves it (not a relative web path).
- Verify the container constrains width or height;
CachedNetworkImagewithout bounds is a common source of layout overflow.
2. State / HTTP Bugs
2.1 API calls fail with 401 in a loop
- Root cause 1: access token expired and refresh token is invalid/missing.
- Check
flutter_secure_storagecontains the refresh token. - Check
AuthService.refresh()is called and the new access token is stored in memory (not secure storage).
- Check
- Root cause 2:
X-Device-Idheader is missing.- Every authenticated request must include the device ID from
flutter_secure_storage.
- Every authenticated request must include the device ID from
- Root cause 3: backend
/api/v1/auth/refreshroute is not wired or returns a non-standard error.- Probe with
curl -v -H "Authorization: Bearer <token>" -H "X-Device-Id: <id>" http://localhost:8080/api/v1/auth/refresh.
- Probe with
2.2 Hardcoded data / Rule 16 violations
- Search for lists, maps, or mock user objects in
apps/mobile/lib/features/. - Any
constlist of countries, currencies, languages, nav items, or options is a Rule 16 violation unless it is a pure client-side constant (e.g., static animation durations). - Replace with API calls through
japaconnect_core.
2.3 japaconnect_core API client drift
- The OpenAPI spec (§47) is the source of truth.
- Regenerate the Dart client after backend route changes:
dart run build_runner build --delete-conflicting-outputs(or the documented generator command inpackages/japaconnect_core/). - Verify the method names and request shapes match the live backend.
- Do not call
http.getordiodirectly in feature code.
2.4 Riverpod state not updating
- Verify the provider is
Scopedat the right level. - Verify
ref.watch(rebuild) vsref.read(one-shot) usage. - Verify
AsyncNotifier/StateNotifiermethods callstate = ...(orstate = AsyncValue.data(...)) after the async operation. - Verify no widget is rebuilding because a parent provider is emitting
AsyncLoadingrepeatedly.
2.5 Push notifications not received
- Check FCM/APNs credentials are seeded in
system_settings(push.fcm_server_key,push.apns_*,push.enabled = true). - Check
POST /api/v1/me/devicesis called and returns 200 with the token stored. - Check
device_tokenstable hasis_active = truefor the user's device. - Check the backend
notification:send_pushAsynq task is enqueued when a notification is generated for the user. - Check the app requests notification permission on first open.
- Check the app handles
onMessage(foreground),onBackgroundMessage, andonMessageOpenedAppcorrectly.
2.6 Secure-storage issues
flutter_secure_storagereads are async and can fail silently on some Android devices if the keystore is locked.- On iOS, the keychain is not shared across app groups unless configured.
- Never store the access token in secure storage — it must live in memory only.
- On logout, delete the refresh token, device ID, and any persisted drafts.
3. Toolchain / Runtime Bugs
3.1 Android build fails
- Run
flutter cleanthenflutter pub get. - Check
android/app/build.gradlecompileSdk,minSdk, andtargetSdk. - Check
pubspec.lockfor plugin version conflicts. - If Gradle daemon is stuck:
pkill -f "gradle"and retry. - Common on agents:
JAVA_HOMEmust point to a JDK 17+ (not JRE).
3.2 iOS build fails (macOS only)
cd ios && pod installwhenPodfile.lockis out of date.flutter cleanand rebuild.- Xcode signing / provisioning is a manual step; the agent can build for the simulator without signing, not for a device.
CocoaPodsmust be installed andpod setupmust have run.
3.3 Simulator / emulator not launching
- List devices:
flutter devices. - Start an emulator:
flutter emulators --launch <id>. - For iOS simulator, the host must be macOS.
3.4 flutter analyze reports issues
- Run
flutter analyzeinapps/mobile/andpackages/japaconnect_core/. - Treat all
error-level issues as blockers. infoandwarninglevel issues should be cleaned up before committing; do not leave known warnings unaddressed.
4. Backend vs Frontend Isolation
When a mobile screen fails, determine which side is broken:
- Frontend bug: UI renders but shows wrong data, crashes on tap, overflow, or wrong state.
- Backend bug: API returns 404/500/401, wrong JSON shape, missing field, or missing route.
- Contract bug: the API works in isolation, but the generated
japaconnect_coreclient expects a different shape. This is a drift bug — fix the client generation or the backend contract, then regenerate.
For backend contract bugs, switch to jc-backend-debug and fix the backend. Do not paper over contract drift in the Flutter client.
5. Debug Checklist
-
flutter doctorpasses for the target platform - App builds and runs on at least one emulator / simulator
- Backend is running and reachable
-
flutter analyzereturns zero errors -
flutter testpasses for the affected feature - Network requests include
Authorization: Bearer <token>andX-Device-Id: <id> - The access token is not written to
flutter_secure_storageor logs - Push token registration/unregistration is wired (where applicable)
- No hardcoded data or mock user objects in feature code
- UI tested at 320dp and 720dp widths