# Loading and progress indicators follow platform conventions

- **Pattern:** `ab-001959` (`mobile-ux-patterns.touch-gestures.indicator-conventions`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001959
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001959)

## Why it matters

When async operations — API calls, uploads, data fetches — complete with no visual feedback, users perceive the app as frozen and repeat taps or abandon the action entirely. ISO 25010:2011 performance-efficiency attributes explicitly cover responsiveness perception, and loading indicators are the primary signal that work is in progress. Missing indicators also mask real failures: users cannot distinguish a slow network from a crashed operation, so they have no signal to wait, retry, or report a bug. Custom animations that diverge from platform conventions (native `ActivityIndicator` on iOS, material progress patterns on Android) erode platform coherence and increase cognitive load without adding value.

## Severity rationale

Low because the failure is perceptual rather than data-loss or security-impacting — users experience confusion and may repeat actions, but no state is corrupted.

## Remediation

Use React Native's native `ActivityIndicator` for indeterminate progress and `ProgressBar` for determinate progress. Gate rendering on the loading boolean from your async state; do not conditionally mount an indicator that is never visible due to broken logic.

```tsx
import { ActivityIndicator, View } from 'react-native';

export function DataScreen() {
  const { data, isLoading, error } = useFetch('/api/items');

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return <ItemList data={data} />;
}
```

For uploads or multi-step operations, prefer `ProgressBar` with a real `progress` value derived from bytes transferred or steps completed — avoid spinning indefinitely when determinate progress is available.

## Detection

- **ID:** `indicator-conventions`
- **Severity:** `low`
- **What to look for:** Count all async operations (data fetches, API calls, file uploads) and for each, check whether a loading indicator is displayed. Enumerate indicator types used: `ActivityIndicator`, `ProgressBar`, or custom.
- **Pass criteria:** At least 90% of async operations show a loading indicator during execution. Indicators use `ActivityIndicator` for indeterminate progress or `ProgressBar` for determinate progress. Color and size match platform conventions (subtle on iOS, material design on Android).
- **Fail criteria:** Any async operation has no loading indicator, or indicators use custom implementations that break platform conventions. Must not pass when indicators are present but never shown to users (e.g., conditional rendering logic is broken).
- **Skip (N/A) when:** The app has no async operations or loading states (purely static content).
- **Detail on fail:** Describe indicator deviations. Quote the actual component used. Example: `"Loading indicator uses custom animated SVG instead of native ActivityIndicator, breaking platform consistency."` or `"No loading indicator shown during data fetch �� users see frozen UI."`
- **Remediation:** Use native indicators from React Native or platform-specific UI libraries:

  ```tsx
  import { ActivityIndicator, ProgressBar, View } from 'react-native';

  // iOS-style spinner
  <ActivityIndicator size="large" color="#0000ff" />

  // Android-style progress bar
  <ProgressBar progress={0.5} color="#0000ff" />

  // Show during async operations
  {isLoading && <ActivityIndicator />}
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: user-experience, performance

HTML version: https://auditbuffet.com/patterns/ab-001959
