Frontend Developer Guide — Membership One Flutter App

Architecture Overview

The Membership One app is built with Flutter using a feature-based architecture with Riverpod state management, GoRouter navigation, and Dio HTTP client. A single codebase serves both the consumer mobile app and the admin web app, with role-based routing determining which interface is shown.

Tech Stack

Component Version Purpose
Flutter 3.41.2 UI framework
Dart 3.11.0 Language
flutter_riverpod 2.6.1 State management
go_router 14.8.1 Declarative routing with auth guard
dio 5.7.0 HTTP client with interceptors
flutter_secure_storage 9.2.4 Encrypted token storage
qr_flutter 4.1.0 QR code rendering
screen_brightness 1.0.1 Auto-brightness for QR screen
shared_preferences 2.3.4 Locale persistence
image_picker 1.1.2 Profile photo upload
intl 0.20.2 Date/number formatting

Directory Structure

lib/
├── main.dart                          # App entry point (ProviderScope)
├── app.dart                           # MaterialApp.router with l10n + theme
├── core/
│   ├── api/
│   │   ├── api_client.dart            # Dio client with JWT interceptor + token refresh
│   │   └── endpoints.dart             # All REST API endpoint paths
│   ├── models/
│   │   ├── membership_template.dart   # Catalog item model
│   │   ├── contract_model.dart        # Contract/membership model
│   │   ├── transaction_model.dart     # Transaction + balance models
│   │   ├── bank_account_model.dart    # Bank account with IBAN masking
│   │   └── member_model.dart          # Member profile model
│   ├── providers/
│   │   ├── api_provider.dart          # ApiClient singleton provider
│   │   ├── auth_provider.dart         # Auth state (login/register/logout)
│   │   └── locale_provider.dart       # Locale state (32 languages + RTL)
│   ├── router/
│   │   └── app_router.dart            # GoRouter with auth redirect + shell route
│   └── theme/
│       └── app_theme.dart             # Material 3 theme (light + dark)
├── features/
│   ├── admin/                             # Admin web app features (Phase 4)
│   │   ├── dashboard/                     # KPI cards, action items
│   │   │   ├── providers/admin_dashboard_provider.dart
│   │   │   └── screens/admin_dashboard_screen.dart
│   │   ├── members/                       # DataTable, detail tabs, create wizard
│   │   │   ├── providers/admin_member_provider.dart
│   │   │   └── screens/
│   │   │       ├── member_list_screen.dart
│   │   │       ├── member_detail_screen.dart
│   │   │       └── member_create_screen.dart
│   │   ├── contracts/                     # Contract list, status management
│   │   │   ├── providers/admin_contract_provider.dart
│   │   │   └── screens/contract_list_screen.dart
│   │   ├── transactions/                  # Transaction list, storno
│   │   │   ├── providers/admin_transaction_provider.dart
│   │   │   └── screens/transaction_list_screen.dart
│   │   ├── templates/                     # Membership template CRUD
│   │   │   ├── providers/admin_template_provider.dart
│   │   │   └── screens/
│   │   │       ├── template_list_screen.dart
│   │   │       └── template_form_screen.dart
│   │   ├── billing/                       # Billing dashboard, manual trigger
│   │   │   ├── providers/admin_billing_provider.dart
│   │   │   └── screens/billing_dashboard_screen.dart
│   │   ├── reports/                       # Demographics, financial, retention, activity
│   │   │   ├── providers/admin_reports_provider.dart
│   │   │   └── screens/reports_screen.dart
│   │   ├── users/                         # User/role management
│   │   │   ├── providers/admin_user_provider.dart
│   │   │   └── screens/
│   │   │       ├── user_list_screen.dart
│   │   │       └── user_form_screen.dart
│   │   └── settings/                      # Organization configuration
│   │       ├── providers/admin_settings_provider.dart
│   │       └── screens/settings_screen.dart
│   ├── auth/screens/
│   │   ├── login_screen.dart          # Login with error handling
│   │   ├── register_screen.dart       # Registration with password strength
│   │   ├── forgot_password_screen.dart # 2-step password reset flow
│   │   └── verify_email_screen.dart   # Email verification deep link handler
│   ├── catalog/
│   │   ├── providers/catalog_provider.dart
│   │   └── screens/
│   │       ├── catalog_screen.dart    # Membership template list
│   │       └── membership_detail_screen.dart # Full template details + pricing
│   ├── purchase/
│   │   ├── providers/purchase_provider.dart
│   │   └── screens/purchase_wizard_screen.dart # 3-step purchase wizard
│   ├── qr/
│   │   ├── providers/qr_provider.dart
│   │   └── screens/qr_code_screen.dart # QR display with countdown + brightness
│   ├── history/
│   │   ├── providers/transaction_provider.dart
│   │   └── screens/transaction_history_screen.dart # Payment list with balance
│   └── profile/
│       ├── providers/profile_provider.dart
│       └── screens/profile_screen.dart # Profile, contracts, GDPR, language
├── shared/widgets/
│   ├── consumer_shell.dart            # Bottom navigation bar shell
│   ├── loading_indicator.dart         # Centered loading spinner
│   └── error_view.dart                # Error display with retry
└── l10n/
    ├── l10n.yaml                      # l10n configuration
    ├── app_en.arb                     # English (base, 148 keys)
    ├── app_de.arb                     # German (real translations)
    ├── app_{locale}.arb               # 30 more locale files
    └── generated/                     # Auto-generated (33 files)
        ├── app_localizations.dart     # Abstract class + delegate
        └── app_localizations_{locale}.dart

Key Patterns

Authentication Flow

App Start → AuthNotifier._checkAuth()
  ├── Token exists → AuthStatus.authenticated → Redirect to /catalog
  └── No token → AuthStatus.unauthenticated → Stay on /login

Login → AuthNotifier.login(email, password)
  ├── Success → Store tokens → AuthStatus.authenticated → Redirect to /catalog
  └── Failure → AuthStatus.unauthenticated + error message

401 Response → Dio Interceptor
  ├── Not /auth/ path → Refresh token → Retry original request
  └── Refresh fails → Clear tokens → Force logout

Token Refresh (Concurrent Safety)

The ApiClient handles concurrent 401 errors safely:

  1. First 401 triggers token refresh
  2. Subsequent 401s during refresh are queued via Completer<void> list
  3. All queued requests resolve when refresh completes
  4. If refresh fails, all queued requests receive the error

Router Auth Guard

GoRouter uses refreshListenable pattern:

  1. _AuthChangeNotifier listens to authProvider state changes
  2. When auth state changes, notifies GoRouter to re-evaluate redirects
  3. Redirect logic: - AuthStatus.initial → no redirect (startup check in progress) - Not authenticated + not on auth route → redirect to /login - Authenticated + on auth route → redirect to /catalog

State Management

All feature states follow the same pattern:

class FeatureState {
  final List<Model> items;
  final bool isLoading;
  final String? error;
  // ...
  FeatureState copyWith({...});
}

class FeatureNotifier extends StateNotifier<FeatureState> {
  final Ref _ref;
  FeatureNotifier(this._ref) : super(const FeatureState());

  Future<void> load() async {
    state = state.copyWith(isLoading: true, error: null);
    try {
      final api = _ref.read(apiClientProvider);
      final response = await api.get(Endpoints.feature);
      // Parse and update state
      state = state.copyWith(items: parsed, isLoading: false);
    } catch (e) {
      state = state.copyWith(isLoading: false, error: 'Failed to load');
    }
  }
}

final featureProvider = StateNotifierProvider<FeatureNotifier, FeatureState>((ref) {
  return FeatureNotifier(ref);
});

Data Models

All models use: - factory Model.fromJson(Map<String, dynamic> json) for deserialization - Map<String, dynamic> toJson() for serialization (where needed) - const constructors for immutability - Computed getters for derived properties (e.g., Member.fullName, BankAccount.maskedIban)

/login              → LoginScreen
/register           → RegisterScreen
/forgot-password    → ForgotPasswordScreen
/verify-email       → VerifyEmailScreen

ShellRoute (ConsumerShell with NavigationBar)
├── /catalog        → CatalogScreen
├── /qr             → QrCodeScreen
├── /history        → TransactionHistoryScreen
└── /profile        → ProfileScreen

/catalog/:id        → MembershipDetailScreen
/purchase/:templateId → PurchaseWizardScreen

Internationalization (i18n)

Setup

  • 32 ARB files in lib/l10n/ (24 EU + 8 non-EU languages)
  • English (app_en.arb) is the base template with 148 keys
  • German (app_de.arb) has real translations
  • Other locales have English placeholder values
  • Generated via flutter gen-l10n (config in l10n.yaml)

Usage

// Access translated strings
final l10n = AppLocalizations.of(context)!;
Text(l10n.login);
Text(l10n.welcomeBack('Max'));
Text(l10n.memberCount(42));

RTL Support

  • Arabic (ar) and Hebrew (he) are detected as RTL locales
  • LocaleNotifier.isRtl getter returns true for RTL locales
  • Material 3 widgets handle RTL layout automatically via Directionality
  • The app sets locale on MaterialApp.router, which propagates RTL direction

Adding a New String

  1. Add the key to app_en.arb (with @key metadata if it has placeholders)
  2. Add translations to app_de.arb and other locale files
  3. Run flutter gen-l10n to regenerate
  4. Use AppLocalizations.of(context)!.newKey in your widget

Theme

Colors

Name Hex Usage
Primary #2CC5CE Buttons, links, accents
Navy #0B3954 App bar, login background
Success #4CAF50 Paid status, check icons
Warning #FFC107 Pending status, offline badge
Error #E53935 Overdue status, error messages
Surface #F5F7FA Page backgrounds
OnSurface #1A1A2E Primary text
TextSecondary #6B7280 Secondary text, labels

Dark Mode

Dark mode is automatically supported via ThemeMode.system with colorSchemeSeed.

API Integration

Backend URL

Configured in endpoints.dart:

static const baseUrl = 'http://localhost:8081/api';

Key Endpoints

Method Path Screen
POST /auth/login Login
POST /auth/register Register
POST /auth/refresh Token refresh
POST /auth/verify-email Email verification
POST /auth/forgot-password Forgot password
POST /auth/reset-password Reset password
GET /membership-templates/catalog/{entityId} Catalog
POST /contracts Purchase
GET /members/{id} Profile
GET /transactions/member/{id} History
GET /transactions/member/{id}/balance Balance
GET /bank-accounts/member/{id} Profile

Error Handling

All API errors are caught and displayed as user-friendly messages. The DioException type is checked for structured error responses from the backend.

Development

Commands

# Analyze code
flutter analyze

# Run tests
flutter test

# Generate l10n
flutter gen-l10n

# Run app
flutter run

# Build APK
flutter build apk

# Build iOS
flutter build ios

File Count Summary

Category Files
Core (API, providers, router, theme, models) 10
Consumer feature screens 10
Consumer feature providers 5
Admin feature screens 14
Admin feature providers 8
Shared widgets 5
ARB locale files 32
Generated l10n 33
Config (main, app, pubspec, l10n.yaml) 4
Total Dart source files 52
Total with generated 85

Admin Web App Architecture (Phase 4)

Role-Based Routing

The app uses a single codebase with role-based routing. After login, the JWT payload includes a roles array. The router redirect logic checks roles:

Login → JWT decoded → user.roles[]
  ├── Contains ADMIN/OWNER/STAFF → redirect to /admin/dashboard
  └── Contains MEMBER only → redirect to /catalog (consumer)

AuthState tracks roles parsed from the login response. The isAdmin getter checks for admin-level roles (SYSTEM_ADMIN, ORG_OWNER, CLUB_ADMIN, STAFF, RECEPTIONIST). Members attempting to access /admin/* routes are redirected to /catalog.

Admin Shell (Responsive Layout)

The AdminShell widget provides the admin navigation:

  • Desktop (≥1024px): NavigationRail on the left side (extended at ≥1280px to show labels)
  • Tablet/Mobile (<1024px): Drawer accessible via hamburger menu

9 navigation destinations: Dashboard, Members, Contracts, Transactions, Templates, Billing, Reports, Users, Settings.

DataTableView Widget

A reusable generic DataTableView<T> widget provides: - Server-side pagination with configurable page size (25/50/100) - Column sorting (click headers) - Search bar with debounce - Responsive: full DataTable ≥768px, card list <768px - Customizable columns via DataTableColumn<T> with cellBuilder

Admin Navigation Structure

ShellRoute (AdminShell with NavigationRail/Drawer)
├── /admin/dashboard        → AdminDashboardScreen
├── /admin/members          → MemberListScreen
├── /admin/contracts        → ContractListScreen
├── /admin/transactions     → AdminTransactionListScreen
├── /admin/templates        → TemplateListScreen
├── /admin/billing          → BillingDashboardScreen
├── /admin/reports          → ReportsScreen
├── /admin/users            → UserListScreen
└── /admin/settings         → SettingsScreen

/admin/members/create       → MemberCreateScreen (4-step wizard)
/admin/members/:id          → MemberDetailScreen (7 tabs)
/admin/templates/create     → TemplateFormScreen
/admin/templates/:id        → TemplateFormScreen (edit mode)
/admin/users/create         → UserFormScreen
/admin/users/:id            → UserFormScreen (edit mode)

Admin API Endpoints

Method Path Screen
GET /admin/members?page&size&sort&search&status Member List
GET /admin/members/{id} Member Detail
POST /admin/members Create Member
PUT /admin/members/{id} Update Member
GET /admin/contracts?page&size&status Contract List
PUT /admin/contracts/{id}/cancel Cancel Contract
PUT /admin/contracts/{id}/suspend Suspend Contract
PUT /admin/contracts/{id}/resume Resume Contract
GET /admin/membership-templates Template List
POST /admin/membership-templates Create Template
PUT /admin/membership-templates/{id} Update Template
DELETE /admin/membership-templates/{id} Archive Template
GET /admin/transactions?page&size&status Transaction List
POST /admin/transactions/{id}/storno Storno Transaction
GET /admin/billing/summary Billing Dashboard
POST /admin/billing/trigger Manual Billing
GET /admin/dashboard/kpis Dashboard KPIs
GET /admin/reports/demographics Demographics
GET /admin/reports/financial Financial
GET /admin/reports/retention Retention
GET /admin/reports/activity Activity
GET /admin/users User List
POST /admin/users Create User
PUT /admin/users/{id} Update User
GET /admin/settings Settings
PUT /admin/settings Update Settings