> ## Documentation Index
> Fetch the complete documentation index at: https://docs.golansertifikasi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 04 Mobile Flutter Developer Guide

# Mobile Developer Guide — Golan Sertifikasi

**Stack:** Flutter\
**Repository:** `golan-mobile`\
**Platforms:** Android and iOS\
**Backend:** shared versioned REST/JSON public API through KrakenD Community Edition (`golan-backend`)

The mobile application is a consumer of the same backend used by the Angular web application. Business rules must remain consistent across both clients.

***

## 1. Mobile Scope

Flutter mobile is **student-facing only for MVP**.

MVP capabilities include:

* Google login/session handling and onboarding.
* Catalog/course detail.
* Midtrans top-up return/status, wallet balance and wallet-only cart/checkout.
* Order/top-up/ledger history and wallet-refund request/status.
* Permanent regular-course access, learning, progress, quiz, assignment, and regular certificate.
* BNSP registration/fulfillment status and secure LSP access display.
* In-app notifications and push notifications.

Mobile does **not** implement admin, instructor, finance, customer-service, or other backoffice functions for MVP. Those operational surfaces are handled by Angular web while consuming the same backend business rules.

Wallet, paid-course-completion referral reward visibility and wallet-only purchases are MVP mobile workflows; instructor withdrawal and platform payouts remain authorized web/backoffice workflows. Wishlist, discussions/community, advanced CMS, advanced analytics and growth features are non-blocking Phase 2 features.

The mobile app must not become an authority for authorization, payment result, tax, refund eligibility, assessment result, course completion, certificate issuance, BNSP delivery, or notification state.

## 1.1 Public Client Architecture

Flutter is a public API client. Production traffic follows:

```text theme={null}
Flutter app
→ HTTPS REST/JSON
→ KrakenD Community Edition
→ REST/HTTP edge of the owning Go service
```

Flutter must not:

* call internal gRPC endpoints;
* connect to RabbitMQ, PostgreSQL, Dragonfly, ClamAV, or internal service ports;
* discover microservices or choose service hosts;
* depend on protobuf or private database schemas;
* embed S3/SMTP/Midtrans server credentials;
* bypass KrakenD in production.

The canonical public API contract is OpenAPI under `/api/v1/...`. Internal gRPC/protobuf and RabbitMQ event contracts are backend-only. A backend service may be redeployed, replicated, split, or merged without changing the mobile architecture as long as the public contract remains compatible.

## 2. Recommended Project Structure

Feature-first structure:

```text theme={null}
lib/
  app/
    app.dart
    router.dart
    config/

  core/
    api/
    auth/
    storage/
    error/
    analytics/
    widgets/
    utils/

  features/
    auth/
    onboarding/
    catalog/
    course/
    wishlist/
    cart/
    checkout/
    orders/
    learning/
    quiz/
    assignment/
    certificates/
    bnsp/
    profile/
    notifications/
```

Within each feature:

```text theme={null}
data/
domain/
presentation/
```

Use this separation where it improves maintainability; do not generate excessive boilerplate for trivial features.

***

## 3. State Management

Choose one consistent state-management approach across the app.

Reasonable options include:

```text theme={null}
Riverpod
Bloc/Cubit
```

Do not mix multiple competing state-management systems without a clear reason.

Recommended state categories:

```text theme={null}
auth/session
current user
cart summary
feature-specific async state
```

Backend remains the business source of truth.

***

## 4. Networking

Use one centralized HTTP client whose base URL points to **KrakenD/public API**, not individual services.

Responsibilities:

* Base URL configuration.
* Auth/session headers or cookies according to the public contract.
* Token refresh coordination if applicable.
* Request/correlation ID propagation when supported.
* Standard success/error envelope decoding.
* Network timeout.
* Safe retry policy.
* Sensitive-data redaction in diagnostics.

Canonical success:

```json theme={null}
{ "data": {}, "meta": {} }
```

Canonical error:

```json theme={null}
{
  "error": {
    "code": "STABLE_MACHINE_CODE",
    "message": "Human-readable message",
    "fields": null,
    "request_id": "..."
  }
}
```

Rules:

* Branch on stable `error.code`, not message text.
* Preserve `request_id` in redacted diagnostics/support context.
* Treat timeouts/`502`/`503` as potentially unknown outcomes for mutations.
* Do not automatically retry non-idempotent checkout/payment/refund/submission mutations unless the public endpoint explicitly supports idempotency or provides a safe recovery contract.
* Never expose raw gateway payloads or internal stack traces to the user.
* Do not add a second network stack for direct microservice calls.

## 5. Authentication

Google login should use the appropriate platform-supported OAuth/Google Sign-In integration.

Flow:

```text theme={null}
Google Sign-In
→ receive supported Google credential/code
→ send to backend
→ backend validates
→ backend creates internal session/token
→ load /me
```

Do not trust local Google user data as the application user source of truth.

KrakenD may reject invalid access tokens at the edge, but the owning service still enforces authorization. Local role checks are UX only.

### Secure storage

Sensitive session/refresh credentials must use platform secure storage.

Do not store long-lived authentication tokens in plain shared preferences.

***

## 6. App Startup

Suggested bootstrap:

```text theme={null}
Load secure session
→ refresh/validate with backend
→ GET /me
→ route:
   unauthenticated → login
   onboarding incomplete → onboarding
   authenticated → home
```

Handle offline/network failures separately from invalid authentication.

***

## 7. Routing

Suggested routes:

```text theme={null}
/login
/onboarding

/home
/courses
/courses/:slug

/cart
/checkout
/orders
/orders/:id

/my-courses
/learn/:courseId
/lesson/:lessonId

/certificates
/certificates/:id

/bnsp
/bnsp/:registrationId

/profile
/notifications
```

Use deep links where useful, especially:

```text theme={null}
course detail
order detail
BNSP registration
certificate verification
```

***

## 8. Onboarding

Same business flow as web:

1. Profile.
2. Interests.
3. Acquisition source.

Requirements:

* Persist temporary form state only as needed.
* Backend determines completion.
* Use course category API for interests.
* Use acquisition source API for source options.
* Do not maintain a separate mobile-only taxonomy.

***

## 9. Catalog

Course cards must visibly distinguish:

```text theme={null}
Regular
BNSP
```

BNSP messaging must state that:

* Golan handles registration/payment.
* LSP handles the official certification process.
* BNSP certificate is not issued by Golan.

This message must not be hidden deep inside terms.

***

## 10. Course Detail

### Regular

Show:

```text theme={null}
course information
instructors[]
primary instructor when supplied
rating when review domain is enabled
price
modules
preview
certificate availability
```

### BNSP

Show:

```text theme={null}
program information
price
registration process
what happens after payment
LSP access handoff explanation
external certification disclaimer
```

***

## 11. Cart and Checkout

Backend is canonical for final:

```text theme={null}
subtotal
discount
PPN/tax
grand_total
```

Mobile may display estimated values while editing cart, but checkout must use backend-calculated order data.

***

## 12. Midtrans Top-up and Wallet Checkout

Implementation depends on the selected Midtrans mobile integration mode, but the business flow is fixed:

```text theme={null}
GET wallet balance / top-up intent if insufficient
→ obtain top-up-only Midtrans payment data
→ open Midtrans-supported top-up UI
→ on return show top-up verifying state and refetch backend top-up/wallet status
→ after credit, POST wallet checkout through Commerce
→ poll pending order while Finance capture is reconciled
→ continue only after backend reports order paid
```

Never treat return-to-app as verified top-up credit or course payment. Course checkout never creates a Midtrans transaction.

***

## 13. Deep Link / App Return Handling

When payment returns to the application:

1. Parse only expected navigation data.
2. Do not accept a client-side `paid=true`.
3. Open top-up status screen.
4. Refetch top-up and wallet from backend before offering wallet checkout.
5. Refetch any subsequent Commerce order independently and render canonical status.

Support:

```text theme={null}
pending
paid
expired
cancelled
refunded
```

***

## 14. Regular Learning

Course player should support:

```text theme={null}
video
text
pdf
file
quiz
assignment
```

Possible offline support can be added later, but it introduces DRM/storage/sync complexity and should not be assumed in MVP.

Progress updates must be sent to backend.

***

## 15. Video

For video lessons:

* Use a maintained player package.
* Support portrait/landscape.
* Resume position only if product requirements need it.
* Do not equate video playback progress alone with authoritative course completion unless backend rules explicitly define it.

***

## 16. Quiz

Requirements:

* Render supported question types, including essay.
* Preserve current attempt state reasonably without making local storage authoritative.
* Submit through backend with double-submit protection.
* Backend calculates/validates objective scores and owns attempt status.
* Do not ship correct-answer flags in pre-submit payloads.
* Essay-containing attempts may become `pending_review`; successful submission is not equivalent to passing.
* Final result after manual grading is fetched from backend.

Network interruption behavior must follow the reliability rules later in this guide.

## 17. Assignment

Support:

```text theme={null}
text submission
file selection/upload
submission history / attempt number
submission status
feedback
score
resubmission when backend permits
```

Request storage/media permission only when actually needed and according to platform policy.

Rules:

* MVP product default allows up to three submissions, but Flutter must not hardcode `3` as business authority.
* Render effective `max_submissions`, `submissions_used`, `can_resubmit`, and reason/deadline fields returned by backend.
* Each resubmission is a new server record; do not overwrite prior history locally.
* A changed policy between screen render and submit must be handled as a normal server rejection/reconciliation path.

## 17.1 Assignment/Media Upload Scan Contract

Uploaded assignment files are governed by Media Service and S3-compatible storage behind the public API. A completed byte upload is not proof that the file can be used.

Expected lifecycle:

```text theme={null}
upload authorization
→ transfer
→ pending_scan / scanning
→ clean | infected | scan_failed
```

Rules:

* Only `clean` media is eligible to become normal trusted assignment content/download.
* `pending_scan`/`scanning` should display processing state.
* `scan_failed` should remain unavailable and expose a retry/re-upload path defined by backend.
* `infected` must be rejected.
* Do not build or persist arbitrary S3 object keys.
* Private file access must use backend-authorized temporary access.
* Offline queues must not mark an assignment submitted until the authoritative submission endpoint confirms the record according to its contract.

## 18. Certificates

Only regular-course certificates are shown as Golan-issued certificates.

Certificate screen:

```text theme={null}
course
certificate number
issued date
status
view
verification
```

Current status:

```text theme={null}
active
revoked
```

Regular certificates are permanent and do not have an expiration date. Do not display expiry UI or compute one locally.

If opening a PDF or verification URL externally, use secure platform navigation.

Do not create any UI suggesting a Golan-issued BNSP certificate. Certificate availability/status must come from backend, not local course completion state.

## 19. BNSP Registration

Student menu:

```text theme={null}
BNSP Registrations
```

States:

```text theme={null}
pending_access
access_ready
access_delivered
cancelled
```

The backend should expose fields such as:

```text theme={null}
fulfillment_due_at
is_overdue
access_ready_at
access_delivered_at
```

The current product default is fulfillment within 1 × 24 hours according to the configured business-day calendar, but the app must not compute that deadline from a hardcoded `24` value.

Pending UX should explain that payment is confirmed and LSP access is being prepared. When access is available, display authorized fields:

```text theme={null}
LSP name
LSP website
account identifier
account secret
instructions
```

`access_delivered` is an authorized staff-recorded state. Student view/copy/open actions never change it. Any future student acknowledgement must be separate.

## 20. BNSP Credential Security

This is one of the most sensitive mobile screens.

Rules:

* Secret hidden by default.
* Explicit reveal action.
* Copy action should be deliberate.
* Do not take secret values into analytics.
* Do not log API body containing credentials.
* Do not persist credential responses to disk unless specifically designed and encrypted.
* Clear sensitive in-memory state when leaving screen where practical.
* Avoid screenshots if the product decides to use platform-level screenshot blocking for this screen.
* External LSP URL should require a valid HTTPS URL unless a known exception exists.

Mobile cannot guarantee secrecy after user copies credentials, so backend and LSP account policy should assume the user ultimately controls the credential once delivered.

***

## 21. Notifications

MVP mobile supports:

```text theme={null}
in-app notifications
push notifications
email as a backend delivery channel (not rendered by the app itself)
```

Potential events:

```text theme={null}
payment_success
course_enrollment
course_completed
quiz_pending_review
quiz_graded
assignment_graded
certificate_issued
bnsp_access_ready
bnsp_access_delivered
refund_processed
```

Rules:

* Backend notification/event state is canonical.
* Push payloads are navigation/refetch hints.
* Duplicate push delivery must be safe.
* Register/rotate/revoke device tokens safely.
* Logout or revoked session/device policy must stop inappropriate future push association.
* Opening an old notification must fetch current backend state before rendering the destination.

## 22. Wishlist — Phase 2

Wishlist is Phase 2 and must not block MVP mobile release.

If retained in the project structure for future compatibility, keep it isolated from core catalog/checkout dependencies. When enabled later, backend sync remains canonical and optimistic UI must roll back on definitive failure.

## 23. Reviews — Open Product Decision

The review/rating domain exists in the broader architecture, but the current product decision has not explicitly assigned reviews to MVP or Phase 2.

Do not make reviews a mobile release blocker until the SSOT assigns their phase. If enabled, backend determines review eligibility and moderation state; the app must not infer eligibility only from local purchase history.

## 24. Error Model

Map backend error codes to domain failures.

Examples:

```text theme={null}
authRequired
forbidden
validation
network
courseNotFound
orderNotPayable
paymentPending
bnspAccessNotReady
certificateNotEligible
unknown
```

Do not display raw server stack/error details.

***

## 25. Offline Strategy

MVP should use a conservative approach.

Reasonable offline behavior:

```text theme={null}
show cached public catalog where available
show last-known non-sensitive UI state
show explicit offline message
queue only operations proven safe to replay
```

Do not queue blindly:

```text theme={null}
checkout creation
payment requests
withdrawal
refund
quiz submit
credential retrieval
```

***

## 26. Caching

Safe candidates:

```text theme={null}
categories
public course summaries
app configuration
non-sensitive profile presentation
```

Avoid persistent cache for:

```text theme={null}
LSP account secret
Google tokens
payment secrets
bank account sensitive data
private documents
```

***

## 27. Analytics

Mobile can emit supported product events:

```text theme={null}
course_view
search
wishlist_add
cart_add
checkout_start
lesson_complete
bnsp_access_view
```

Never include secret values.

Use backend-supported event names to keep web/mobile analytics compatible.

***

## 28. Design Consistency

Mobile and web do not need pixel-identical layouts, but terminology and state naming must match.

Examples:

Use the same language for:

```text theme={null}
Pending LSP Access
Access Ready
Paid
Expired
Certificate Active
Certificate Revoked
```

Do not invent client-specific business statuses.

***

## 29. Environment and Runtime Configuration

Typical environments:

```text theme={null}
local
development
staging
production
```

Mobile runtime/build configuration may include:

```text theme={null}
API_BASE_URL              # KrakenD/public API origin
GOOGLE_CLIENT_ID / platform Google config
MIDTRANS_CLIENT_KEY where required by the approved client flow
MIDTRANS_ENV
APP_DEEP_LINK_BASE
PUSH_PUBLIC_CONFIG where applicable
```

Never embed backend/internal values such as:

```text theme={null}
IDENTITY_SERVICE_URL
LEARNING_SERVICE_URL
GRPC_*
RABBITMQ_URL
DATABASE_URL
DRAGONFLY_ADDR
S3_SECRET_KEY
SMTP_PASSWORD
MIDTRANS_SERVER_KEY
JWT_SIGNING_PRIVATE_KEY
BNSP_CREDENTIAL_ENCRYPTION_KEY
```

The app calls public KrakenD routes only.

### Business-policy rule

Do not ship authoritative literals such as:

```text theme={null}
PPN_RATE
REFUND_DAYS
BNSP_SLA_HOURS
MAX_ASSIGNMENT_SUBMISSIONS
```

Use backend-derived values/capabilities for presentation, including:

```text theme={null}
refund_request_deadline_at
can_request_refund
assignment.max_submissions
assignment.can_resubmit
bnsp.fulfillment_due_at
bnsp.is_overdue
price.tax
price.grand_total
```

The backend validates every mutation again.

## 29.1 Distributed Backend Workflow Semantics

The backend is distributed. A client-visible action can trigger asynchronous RabbitMQ consumers after one service commits its own transaction. Mobile UX must tolerate temporary convergence gaps.

Typical example:

```text theme={null}
Payment Service settles payment
→ event published reliably
→ Commerce updates order
→ Learning creates enrollment OR BNSP creates registration
→ Notification/Analytics react independently
```

Mobile rules:

* Midtrans app/browser return is not payment proof.
* Refetch canonical order/payment state after returning to the app.
* If payment is settled while entitlement/registration is still provisioning, show a neutral processing state and refetch with bounded backoff.
* A mutation timeout means outcome may be unknown; reconcile by resource/idempotency lookup before offering an unsafe repeat.
* Do not expose RabbitMQ event names/queue timing to the UI.
* Business state comes from public API status/capability fields only.

## 30. Security

Minimum mobile security expectations:

* Secure token storage.
* TLS-only API.
* No sensitive logs.
* No secrets in crash analytics.
* App link/deep-link validation.
* Input validation for URLs.
* Safe external browser opening.
* Certificate/LSP credential screens treated as sensitive.
* Obfuscation/minification for release where appropriate.
* Root/jailbreak checks are optional defense-in-depth, not a trust boundary.

Backend authorization remains mandatory.

***

## 31. Testing

### Unit

```text theme={null}
state notifiers/blocs
mappers
validators
error mapping
```

### Widget

```text theme={null}
onboarding
course card type
checkout summary
payment verification
BNSP credentials
certificate screen
```

### Integration

Critical flows:

```text theme={null}
auth bootstrap
onboarding
regular purchase
order verification
learning progress
BNSP registration status
BNSP credential retrieval
```

Use fake/sandbox credentials only.

***

## 32. Release Criteria

Before production release:

* Development and staging API separated.
* Google OAuth configured for production package/bundle IDs.
* Midtrans production configuration verified.
* Deep links/app links verified.
* Crash logs checked for sensitive payload leakage.
* ProGuard/R8/Flutter release build settings reviewed.
* iOS entitlements/release signing reviewed.
* Privacy disclosures reflect collected data.
* Production API base points only to KrakenD/public REST; no internal service/gRPC endpoints are bundled.
* Upload flows have been tested through pending scan, clean, infected/rejected, and scan-failure states where applicable.
* Payment and fulfillment screens tolerate temporary asynchronous convergence without reporting false success/failure.
* Push device registration/revocation behavior is verified if push remains in the MVP release.

***

## 33. Mobile Definition of Done

A feature is complete when:

1. It matches backend API and SSOT statuses.
2. Loading/error/offline states are covered.
3. Secrets are not logged or persisted carelessly.
4. Payment result waits for backend verification.
5. BNSP flow is clearly external-LSP based.
6. Regular certificates are the only Golan-issued certificates shown.
7. Android and iOS behavior is tested.
8. Core widget/unit tests are present.
9. Deep links and navigation edge cases are handled where relevant.
10. API traffic uses the public KrakenD/OpenAPI contract only.
11. Distributed workflow state is reconciled after ambiguous timeout/network outcomes.
12. Media-dependent flows wait for authoritative scan/availability state.
13. No internal service topology, gRPC contract, RabbitMQ queue, or private database schema leaks into the mobile domain model.

***

## 34. Mobile Engineering Baseline

The mobile repository should commit to one predictable application architecture.

Recommended baseline:

```text theme={null}
feature-first modules
one state-management approach
one routing approach
one HTTP client abstraction
immutable DTO/domain models where practical
central error normalization
secure storage abstraction
build flavors for development/staging/production
lint + format + tests in CI
```

Avoid mixing Riverpod, Bloc, Provider, GetX, and ad-hoc global singletons. Pick one primary state pattern and use exceptions only with documented justification.

Recommended dependency direction:

```text theme={null}
presentation
  ↓
domain/use-case where useful
  ↓
data/repository
  ↓
core API/storage abstractions
```

Simple read-only features do not need ceremonial layers, but payment, authentication, learning submissions, and BNSP credentials should have explicit boundaries.

***

## 35. Application State Model

Distinguish global lifecycle state from feature state.

Global app state may include:

```text theme={null}
bootstrap state
session/auth state
current user
onboarding status
connectivity hint
app configuration
cart summary count
```

Feature state belongs within the feature:

```text theme={null}
catalog query/result
course detail
learning session
quiz attempt
order/payment verification
BNSP registration detail
```

Do not create a single giant application state object that causes unrelated screens to rebuild and makes sensitive values long-lived in memory.

***

## 36. Session Lifecycle and Refresh Concurrency

Canonical states:

```text theme={null}
unknown
unauthenticated
authenticating
authenticated
refreshing
expired
```

Startup:

```text theme={null}
initialize app services
→ read secure session material
→ refresh/validate session with backend
→ GET /me
→ route based on auth + onboarding
```

Refresh requirements:

* Only one refresh operation should be active at a time.
* Requests waiting on refresh should resume consistently or fail consistently.
* Explicit invalid-session responses clear secure session material.
* Network timeout alone must not be interpreted as logout.
* Logout clears secure credentials and sensitive in-memory feature state.
* Never send auth tokens to analytics/crash breadcrumbs.

If the backend changes token/session strategy, mobile must follow the documented API contract rather than implementing its own assumptions.

***

## 37. API DTO and Domain Mapping

Keep backend enums canonical.

Recommended flow:

```text theme={null}
JSON
→ API DTO
→ mapper
→ domain/presentation model
```

Use strict parsing for required fields in sensitive flows so malformed data fails visibly during development/testing.

Unknown enum strategy must be explicit. For non-critical display enums, an `unknown` presentation state may be acceptable. For payment/security-sensitive state, unknown values should block consequential actions and trigger refetch/support behavior rather than guessing.

Examples of canonical enums:

```text theme={null}
CourseType.regular
CourseType.bnsp
OrderStatus.pending
OrderStatus.paid
BnspRegistrationStatus.pendingAccess
BnspRegistrationStatus.accessReady
```

***

## 38. Navigation and Deep-Link Contract

Deep links are untrusted navigation input.

For each supported link:

1. Parse only expected path/query fields.
2. Normalize/validate identifiers.
3. Route through normal authentication/onboarding guards.
4. Fetch canonical object state from backend.
5. Do not trust status or authorization encoded in the URL.

Examples:

```text theme={null}
/course/{slug}
/order/{id}
/bnsp/{registrationId}
/certificate/verify/{verificationCode}
```

Payment return links must carry only enough information to find/refetch the internal order/payment state. They must not carry trusted `paid`, `success`, price, entitlement, or credential flags.

***

## 39. Mobile Mutation Safety

Each mutation should define behavior for:

```text theme={null}
double tap
request timeout
app backgrounding
process death
network loss
response lost after server commit
user retry
```

For low-risk idempotent operations:

```text theme={null}
optimistic update when appropriate
→ request
→ rollback on definitive failure
```

For checkout/payment/refund/quiz submission/assignment submission:

* Do not blindly retry non-idempotent requests.
* Use backend idempotency keys where supported.
* On ambiguous timeout, reconcile by fetching canonical state.
* Disable repeated action while the initial request is actively in flight.
* Persist only the minimum recovery identifier needed to resume a safe status check.
* Do not infer completion simply because an HTTP request returned 2xx; inspect the returned business state and refetch when needed.

## 40. Detailed Payment Verification UX

Recommended screen states:

```text theme={null}
creatingOrder
awaitingPayment
paymentUiOpen
verifying
paid
pending
expired
cancelled
failedToVerify
```

After returning from Midtrans:

```text theme={null}
show Verifying Payment
→ GET order/payment state
→ paid?
   yes → show success destination based on purchased product
   no, pending → keep pending + manual refresh
   expired/cancelled → show final state
   network failure → show verification error without claiming payment failure
```

Important distinction:

```text theme={null}
cannot verify payment
≠
payment failed
```

If the app is terminated after payment and reopened, order history must still reconcile from backend without relying on previous local navigation state.

***

## 41. Learning Session and Progress Rules

Mobile should not assume every lesson has the same completion trigger.

Potential completion triggers must follow backend/product rules:

```text theme={null}
explicit Mark Complete
video threshold configured by backend
content opened + explicit completion
quiz passed
assignment passed
```

If only explicit completion is supported in MVP, keep the client model simple and do not infer completion from playback percentage.

Learning screen should tolerate backend content changes between sessions:

```text theme={null}
lesson removed/archived
module order changed
enrollment revoked
course completed
```

On conflict, refresh canonical course structure instead of trying to preserve stale local assumptions.

***

## 42. Quiz Attempt Reliability

An active quiz attempt needs an explicit recovery model.

Recommended behavior:

* Backend owns attempt number, start time, expiry, submission status, and grading state.
* Mobile may preserve unsent answers locally for UX if the data is non-sensitive and lifecycle rules permit it.
* On reopening, fetch current attempt state before submitting.
* Timer display is derived from backend attempt timing, not solely from a local countdown started when the widget mounted.
* Submission must be protected from double tap.
* A lost response after submission should trigger attempt-status reconciliation before retrying.
* Correct answers are displayed only according to backend review policy.
* Essay/manual grading must render an explicit `pending_review` state and later reconcile to the backend final grade/pass-fail result.
* A 2xx submit response is not itself proof of pass/completion.

## 43. Assignment Upload and Resubmission Contract

For file assignments:

```text theme={null}
select file
→ validate allowed type/size client-side for UX
→ obtain/upload through approved backend/storage flow
→ create submission referencing authorized uploaded object
→ show canonical submission state and attempt number
```

Requirements:

* Backend still validates type, size, ownership, authorization, and effective resubmission limit.
* Temporary upload failures should be distinguishable from final submission failures.
* Avoid requesting broad storage/media permissions when a platform document picker can provide scoped access.
* Do not retain private assignment files longer than needed in temporary app storage.
* Render backend `max_submissions`, `submissions_used`, and `can_resubmit`; never use a local literal as authority.
* Each successful resubmission produces a separate historical record.

## 44. BNSP Sensitive-Screen Lifecycle

Credential retrieval should be isolated from normal persisted repositories/caches.

Recommended flow:

```text theme={null}
load registration metadata
→ user taps View LSP Access
→ fetch sensitive endpoint
→ hold result in short-lived memory state
→ mask secret by default
→ explicit reveal/copy
→ clear on route disposal/logout/background policy as configured
```

Never place raw credentials in:

```text theme={null}
shared preferences
local database cache
analytics
crash reports
navigation arguments
push notification payloads
clipboard automatically
screenshots generated by test/reporting tools
```

If screenshot blocking is enabled, test both Android and iOS behavior because platform capabilities differ.

Clipboard behavior should be deliberate. If automatic clipboard clearing is introduced, it must be tested carefully and documented because mobile OS behavior varies.

***

## 45. Push and In-App Notification Architecture

The locked product SSOT requires push notification in MVP together with in-app and email. The current backend guide still labels FCM/push as a later Notification Service extension; backend documentation/implementation must be aligned before MVP release. Mobile must not create a separate notification authority to compensate.

Architecture rules:

* Notification Service/backend state is canonical.
* Push is a delivery hint; on open, navigate by safe reference and refetch the resource through KrakenD.
* Push payloads must not contain LSP passwords, auth tokens, Midtrans secrets, private signed URLs, or sensitive financial data.
* Register/rotate/revoke device tokens using public API endpoints owned by the notification architecture.
* On logout or revoked session, remove/revoke the device registration according to backend contract; do not assume local token deletion alone revokes server delivery.
* Duplicate push delivery must not duplicate business mutations.
* In-app notification read state is server-owned when the API exposes it.
* Do not require WebSocket for MVP correctness. Push + persisted in-app state + refetch is sufficient for awareness; live bidirectional sockets are not part of the initial backend architecture.

Push navigation flow:

```text theme={null}
receive push
→ validate local routing payload shape
→ user opens
→ route to target screen
→ GET current canonical resource
→ render current state
```

## 46. Offline and Local Persistence Matrix

Classify data before caching it.

### Safe or relatively safe to cache

```text theme={null}
public course summaries
categories
non-sensitive app configuration
last selected catalog filters
non-sensitive profile presentation
```

### Cache with caution

```text theme={null}
course structure for active enrollment
lesson text/PDF metadata
non-sensitive order summaries
notification list
```

These require stale-data handling and authorization revalidation after session changes.

### Do not persist by default

```text theme={null}
LSP account secret
raw OAuth/session secrets outside secure storage
payment credentials
bank account details
private assignment contents
admin-only data
```

Offline UI must clearly label stale data where incorrect freshness could mislead the user.

***

## 47. Connectivity and Error Recovery

Connectivity APIs are hints, not proof that a request will succeed.

Error categories should distinguish:

```text theme={null}
noNetwork
requestTimeout
serverUnavailable
unauthorized
forbidden
validation
notFound
conflict
rateLimited
domainError
unknown
```

UX rules:

* `noNetwork` → allow safe retry when connectivity may return.
* `401` after failed refresh → return to login.
* `403` → access denied; do not retry indefinitely.
* `422/validation` → preserve inputs and show field/domain errors.
* `409/conflict` → refetch canonical state where appropriate.
* `5xx` → show recoverable failure and correlation/request ID when support value exists.

Do not show raw stack traces or gateway payloads.

***

## 48. Mobile Observability and Sensitive-Data Redaction

Crash/error telemetry should include only data useful for diagnosis.

Safe examples:

```text theme={null}
app version/build
platform + OS version
feature/screen name
API error code
request/correlation ID
network error category
```

Redact or exclude:

```text theme={null}
Authorization headers
Google credentials
refresh tokens
LSP credentials
payment raw responses
bank account numbers
private file URLs with signed tokens
full user-generated assignment content
```

Logging interceptors must default to redacting headers/body fields in production.

***

## 49. Platform Lifecycle Handling

Test important flows when the application:

```text theme={null}
goes to background
returns from background
is killed by OS
is manually terminated
resumes from payment browser/SDK
opens from deep link
opens from push notification
```

On resume, refetch time-sensitive state when necessary:

```text theme={null}
payment verification
active quiz timing
BNSP access availability
order/refund state
```

Do not assume an in-memory state notifier survives OS process death.

***

## 50. Mobile Design-System Contract

Create shared widgets/tokens for:

```text theme={null}
buttons
text fields
select controls
loading/skeleton
empty/error views
status badges
price display
course type badge
confirmation dialogs
secure secret field
retry view
```

Accessibility requirements:

* Touch targets must be practical for mobile use.
* Text scaling should not break critical actions.
* Icons used as buttons need semantic labels.
* State cannot rely on color alone.
* Forms should use correct keyboard/input types where possible.

Terminology must match web/backend canonical statuses.

***

## 51. Expanded Mobile Test Matrix

### Authentication

```text theme={null}
fresh install logged out
successful Google auth
new user onboarding
existing user
session refresh
expired session
network failure during bootstrap
logout clears secure state and push association flow
```

### Commerce + refund

```text theme={null}
cart sync
tax-inclusive backend breakdown rendered without client PPN calculation
checkout double tap
payment return while pending
payment paid
payment verification network failure
app killed before/after payment return
order history reconciliation
refund capability/deadline fetched from backend
refund request success ≠ approval
refund final-state refresh
```

### Learning

```text theme={null}
permanent course access after valid purchase
lesson completion
stale course structure refresh
quiz resume/timer
quiz duplicate submit
essay submit → pending_review → graded result
assignment upload failure
assignment resubmit creates new history item
max submission limit driven by backend
course completed
certificate retrieval
certificate active/revoked only; no expiry UI
```

### BNSP

```text theme={null}
pending_access
access_ready
fulfillment_due_at displayed from API
overdue state driven by backend
credential endpoint forbidden/not ready
secret masked/revealed/copied
student view does not mark delivered
route disposal clears sensitive state
no secret in persistence/log mocks
external LSP URL validation
```

### Notifications

```text theme={null}
push foreground/background/cold-start handling
duplicate push safely refetches canonical state
old push opens current resource state
permission denied
invalid/rotated token recovery
logout/revoked device behavior
no sensitive data in push payload/log mocks
```

### Lifecycle

```text theme={null}
background/resume
deep-link cold start
push cold start
process death recovery
orientation where supported
```

### Platform

Run critical integration flows on both Android and iOS; emulator-only testing is not sufficient for Google sign-in, deep links, secure storage, payment returns, and notification behavior.

## 52. Build and Release Pipeline

Minimum CI stages:

```text theme={null}
resolve dependencies from lockfile
format check
static analysis
unit tests
widget tests
build development/staging artifact
critical integration tests where infrastructure allows
release build
```

Release configuration must separate:

```text theme={null}
API endpoint
Google OAuth platform configuration
Midtrans client-side configuration
app/deep-link domains
push configuration
```

Production secrets that belong on backend must never be embedded in Flutter assets, Dart constants, native resource files, or CI-generated application bundles.

***

## 53. Mobile Feature Acceptance Template

```text theme={null}
[ ] Backend/API contract identified
[ ] Auth/onboarding guard behavior defined
[ ] Backend-derived business limits/deadlines/capabilities identified
[ ] No PPN/refund/SLA/assignment-limit authority hardcoded in Dart
[ ] Loading state implemented
[ ] Empty state implemented where applicable
[ ] Offline/network failure state implemented
[ ] Domain errors mapped
[ ] Double-submit/retry behavior defined
[ ] Ambiguous HTTP/network outcome reconciles canonical state where needed
[ ] App background/resume behavior considered
[ ] Process-death recovery considered for critical flows
[ ] Push duplicate/token/logout lifecycle considered where applicable
[ ] Sensitive data persistence reviewed
[ ] Analytics/crash logging redaction reviewed
[ ] Android tested
[ ] iOS tested
[ ] Unit/widget tests added
[ ] Integration test updated for critical journey
[ ] No client-only business source of truth introduced
```

Payment, refund, BNSP credential, authentication, manual grading/assessment result, certificate, and notification screens should not pass release review with undefined recovery semantics.

## 54. Refund Workflow

Refund is MVP and is manually reviewed by admin/backoffice through the web application.

Student mobile flow:

```text theme={null}
order detail
→ fetch canonical refund capability
→ if allowed, submit refund request
→ show canonical request status
→ refresh/reconcile until final decision/processing state
```

Rules:

* Current default request window is seven days after payment, but mobile must not calculate `paid_at + 7 days` as business authority.
* Prefer backend fields `can_request_refund`, `refund_request_deadline_at`, and reason codes.
* Request submission does not mean approval or completed refund.
* Do not revoke local course access or hide BNSP data based solely on the presence of a refund request.
* Delivered BNSP access is non-refundable by default; show the backend decision/reason rather than duplicating the policy.
* Handle ambiguous network response by reconciling existing request/order state before retrying.

***

## 55. Config-Driven Business Policy

Flutter may cache non-sensitive, server-provided effective values for presentation, but it must not own business configuration.

Examples of backend-derived values:

```text theme={null}
price.subtotal / discount / tax / grand_total
refund_request_deadline_at
can_request_refund
assignment.max_submissions
assignment.submissions_used
assignment.can_resubmit
bnsp.fulfillment_due_at
bnsp.is_overdue
```

Values such as PPN rate, refund window, BNSP SLA, and assignment submission limit must not be duplicated as Dart constants, remote-config authority, or platform resource values. The backend validates all mutations again.

***

## 56. MVP Scope and Open Product Decisions

Flutter MVP remains student-only. Administrative/backoffice features must not leak into the mobile release scope.

Explicit Phase 2 items are non-blocking:

```text theme={null}
wishlist
discussions / community
advanced CMS
advanced analytics
growth / marketing
```

For unassigned behavior, follow the SSOT `Open Product Decision` list. Current examples include significant-course-usage refund threshold, partial-refund policy, BNSP business-calendar mechanics, review release phase, coupon release phase, optional student BNSP acknowledgement, and certificate reissue policy.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.