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:
- First 401 triggers token refresh
- Subsequent 401s during refresh are queued via
Completer<void>list - All queued requests resolve when refresh completes
- If refresh fails, all queued requests receive the error
Router Auth Guard
GoRouter uses refreshListenable pattern:
_AuthChangeNotifierlistens toauthProviderstate changes- When auth state changes, notifies GoRouter to re-evaluate redirects
- 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)
Navigation Structure
/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 inl10n.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.isRtlgetter returnstruefor RTL locales- Material 3 widgets handle RTL layout automatically via
Directionality - The app sets
localeonMaterialApp.router, which propagates RTL direction
Adding a New String
- Add the key to
app_en.arb(with@keymetadata if it has placeholders) - Add translations to
app_de.arband other locale files - Run
flutter gen-l10nto regenerate - Use
AppLocalizations.of(context)!.newKeyin 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):
NavigationRailon the left side (extended at ≥1280px to show labels) - Tablet/Mobile (<1024px):
Draweraccessible 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 |