MCPcopy Index your code
hub / github.com/cevheri/flutter-bloc-advanced

github.com/cevheri/flutter-bloc-advanced @v0.22.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.22.0 ↗ · + Follow
88 symbols 145 edges 24 files 11 documented · 12%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Advanced Flutter BLoC Template

License: MIT Flutter Dart Platforms Open Source

A production-ready, community-friendly Flutter starter built with BLoC, repository pattern, responsive UI, role-based access control, internationalization, and multi-environment support. It is designed to help you move from prototype to maintainable product faster across mobile, web, and desktop.

Useful links: Wiki · Transformation Log · Report an Issue · Contributing

Why This Template?

  • Production-oriented structure with clear separation between presentation, business logic, and data access.
  • Ready-to-use authentication, role-based routing, user management, localization, theming, and dashboard flows.
  • Local mock mode for rapid development and production mode for real API integration.
  • Works as an open-source base project for teams, side projects, internal products, and community contributions.

Screenshots

The screenshots below are included to help contributors and adopters understand the current UX quickly before cloning or running the project.

Web Experience

Dark Login Light Login
Web login screen in dark theme Web login screen in light theme

Web user management list screen

Mobile Experience

Login Edit User
Mobile login screen Mobile edit user screen

What You Get Out of the Box

Authentication

  • Login with username and password
  • Registration flow
  • Forgot password flow
  • OTP send and verify flow
  • Token storage — JWT and refresh tokens are persisted via flutter_secure_storage (iOS Keychain, Android custom AES-GCM ciphers). Keychain accessibility is pinned to first_unlock_this_device so tokens stay on the device (no iCloud Keychain sync). Non-secret session fields (username, roles) remain in SharedPreferences for synchronous access. The first launch after upgrading from an older build runs a one-shot migration from the legacy plaintext keys into secure storage.

User Management

  • Create, update, delete, and list users
  • Account profile view and update
  • Change password screen

Access Control

  • Role-based routing for Admin and User roles
  • Public and private route separation
  • Protected admin-only pages

Crash Reporting (Sentry, opt-in)

The template ships with a Sentry adapter (SentryAnalyticsService) and a conservative PII / token scrubber, but no DSN is committed — public templates with a committed DSN bleed events from every fork into the original project's quota. Provide a DSN at build time:

fvm flutter run --target lib/main/main_prod.dart \
  --dart-define=SENTRY_DSN=https://YOUR_PUBLIC_KEY@oXXX.ingest.sentry.io/YOUR_PROJECT_ID
fvm flutter build apk --release --target lib/main/main_prod.dart \
  --dart-define=SENTRY_DSN=https://...
  • No DSN, or non-prod build: AppDependencies.createAnalyticsService() returns LogAnalyticsService. Errors land in AppLogger. No network egress.
  • DSN + prod build: bootstrap calls SentryFlutter.init(...) and the analytics interface switches to the Sentry-backed implementation. SentryNavigatorObserver registers automatically so route changes become breadcrumbs.

sentryBeforeSend (see lib/infrastructure/analytics/sentry_scrub.dart) drops the following before any event leaves the device:

  • Authorization / Cookie / Set-Cookie headers (case-insensitive)
  • Body keys whose names contain password, otp, token, refreshToken (case-insensitive substring)
  • JWT-shaped strings (3 base64url segments) in exception values + event message — replaced with [REDACTED_JWT]

The scrubber is unit-tested independently of the SDK; review it against your fork's PII surface before adopting in production. Adding new redaction rules is a single-file change.

Default sample rate: tracesSampleRate: 0.2. Adjust in AppBootstrap.run if your event budget needs different cadence.

Certificate Pinning (opt-in)

The HTTP client supports certificate pinning to defend against MITM via user-installed root CAs, rogue intermediates, and other forms of device-trust-store compromise. Default off — the template ships with an empty pin list because pinning the wrong cert bricks every install.

How it works:

  • ProfileConstants.certificatePins is a List<String> of base64 SHA-256 hashes. Empty list = pinning disabled (use system trust); non-empty list installs a custom Dio adapter that uses SecurityContext(withTrustedRoots: false) so every certificate falls through badCertificateCallback for pin checking. This is the only correct way to enforce pinning even against system-trusted but adversarial CAs.
  • Pin mismatch → DioExceptionType.badCertificate, which ResilienceInterceptor already treats as non-retryable. AppErrorCode.networkCertInvalid is available for repository-layer code paths that want to surface a typed error.
  • Web platform is a hard no-op. Browsers control TLS; from JS we cannot intercept the handshake. Use HSTS / CT pinning at the server side instead.

Extract pins from your live backend:

openssl s_client -servername api.example.com -connect api.example.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -outform DER \
  | openssl dgst -sha256 -binary \
  | openssl enc -base64

(v1 caveat — full-cert hash, not SPKI. The OWASP-recommended pin is SHA-256(SubjectPublicKeyInfo), which survives certificate rotation as long as the keypair is reused. Extracting SPKI from X.509 DER requires ASN.1 parsing; we ship full-cert hash for simplicity and auditability. Stored pin shape stays identical when upgrading — only the hash input changes. Track this gap in your fork before adopting in production.)

Key rotation procedure:

  1. Add the new (backup) pin to certificatePins before rotating server keys. Ship that app version.
  2. Rotate server certs to the new keypair.
  3. In a later release, remove the old pin.

This avoids a window where in-flight users with the old app version cannot reach the new cert.

Inactivity Auto-Logout

IdleTimeoutObserver (lib/core/security/) signs the user out after a configurable window of pointer inactivity. Default threshold is 15 minutes in production, disabled in dev/test (where hot-reload + mocked sessions would make a 15-minute kick out hostile).

  • Configured via ProfileConstants.idleTimeout. Override per environment in lib/infrastructure/config/environment.dart by editing the IDLE_TIMEOUT_SECONDS entry (an int in seconds) of the env map (e.g. _Config.prodConstants). Set to null to disable.
  • Background-aware: on iOS the Dart Timer pauses while the app is suspended, so the observer also captures wall-clock DateTime.now() on lifecycle transitions; resumes past the threshold fire immediately.
  • Surfacing: when the timer fires, the user sees a localized snackbar ("You were signed out due to inactivity.") and the router redirects to login. The reason is tagged SessionExpiredReason.idleTimeout so logs and UI can distinguish it from a manual sign-out.
  • Activity scope: pointer down / pointer move events on the MaterialApp.router subtree reset the timer. Scroll-only sessions (a long-form reading view) do extend, since scroll causes pointer move events.

Compliance baselines that expect inactivity timeouts: SOC2 CC6.1, ISO 27001 A.9.4.2, GDPR Art. 32.

Idempotent Mutations (opt-in)

POST / PUT / PATCH retries can create duplicate records when the client retries on a transient failure. The template ships an opt-in IdempotencyInterceptor that attaches an Idempotency-Key: <uuid-v4> header so a properly configured backend can deduplicate.

The header is a contract, not a guarantee. The server must be configured to deduplicate by this header (see Stripe's worked example, IETF draft idempotency-key-header). If the backend ignores it, this feature is theatre — the duplicate is still created server-side. Confirm support before opting in production call sites.

Opt in per call:

await ApiClient.post<User>('/admin/users', user, idempotent: true);
await ApiClient.put<User>('/admin/users', user, pathParams: '42', idempotent: true);
await ApiClient.patch<User>('/admin/users', user, pathParams: '42', idempotent: true);

Worked example: UserRepository.create opts in (see lib/features/users/data/repositories/user_repository.dart).

Retry stability: once generated, the key is stashed on RequestOptions.extra['_idempotency_key'] and reused on every subsequent pass — covers both ResilienceInterceptor retries (transient 5xx) and TokenRefreshInterceptor re-submission after a 401 + refresh.

GET, HEAD, OPTIONS, DELETE never get the header (safe / idempotent by HTTP semantics).

Screen Capture Protection (opt-in)

Mechanism for sensitive screens (credentials, OTP, payment, tokens) shipped disabled by default. The template's job is to provide the hook, not impose UX on every fork — many apps legitimately want users to screenshot QR codes, receipts, etc.

  • Helper: ScreenCaptureProtection.enable() / .disable() in lib/core/security/screen_capture_protection.dart.
  • Per-screen mixin: ScreenCaptureProtected<T> in lib/core/security/screen_capture_protected.dart — add to a State<T> and it auto-enables on initState, releases on dispose.

Platform behaviour (asymmetric — read this):

Platform What happens
Android FLAG_SECURE: screenshots and screen recording are blocked; recent-apps preview renders black.
iOS iOS provides no API to block screenshots (Apple policy). The task-switcher snapshot is blurred so on-screen secrets do not leak into the app preview. Screenshots while foregrounded still succeed.
Web / Desktop No-op.

Opt-in example (uncomment in LoginScreen):

import 'package:flutter_bloc_advance/core/security/screen_capture_protected.dart';

class _LoginScreenState extends State<LoginScreen>
    with ScreenCaptureProtected<LoginScreen>, SingleTickerProviderStateMixin {
  // ...
}

When to enable: credential entry, OTP screens, token display. When not to enable: screens with content the user is expected to capture (QR codes, receipts).

Server-Driven Dynamic Forms

  • 16 supported field types: text, email, password, number, phone, textarea, dropdown, multiSelect, date, datetime, toggle, checkbox, radio, slider, sectionHeader, divider
  • Schema-only loading (/dynamic-forms/sample — CRM lead demo) or bundled {schema, values} loading with per-instance pathParams (/user/:id/extended-info — user extended-info kitchen-sink covering all 16 types)
  • Reusable from any feature via DynamicFormsFeatureRoutes.withBloc(context, child) — the engine lives under shared/dynamic_forms/ and is feature-agnostic

UI and Developer Experience

  • Dark and light themes
  • Responsive layout support
  • English and Turkish localization
  • Design system foundation with reusable components
  • Multi-platform support for Android, iOS, Web, macOS, Linux, and Windows

Architecture

  • BLoC for state management
  • Feature-First Clean Architecture with domain layer (entities + repository interfaces)
  • Use Cases for business logic isolation
  • Repository pattern with Result<T> sealed type (no raw exceptions)
  • Typed error hierarchy (NetworkError, AuthError, ValidationError, ServerError, etc.)
  • Dio HTTP client with interceptor chain (auth, logging, mock)
  • Feature-based routing with clean boundaries
  • Manual JSON serialization for models
  • Environment-driven configuration for local and production modes
  • Architecture guard tests enforcing dependency rules

High-level architecture diagram

Quick Start

Prerequisites

  • Flutter 3.44.2 and Dart 3.12.2
  • FVM recommended for version consistency
  • Android SDK for Android builds
  • Xcode for iOS and macOS builds

Install FVM

# macOS / Linux
brew tap leoafarias/fvm
brew install fvm

# Windows
choco install fvm

Setup

git clone https://github.com/cevheri/flutter-bloc-advanced.git
cd flutter-bloc-advanced

fvm install 3.44.2
fvm use 3.44.2
fvm flutter pub get

Run Locally With Mock Data

All local requests use assets/mock/, so you can explore the app without standing up a backend first.

# Mobile
fvm flutter run --target lib/main/main_local.dart

# Web
fvm flutter run -d chrome --target lib/main/main_local.dart

# Web with a specific port
fvm flutter run -d chrome --web-port 3000 --target lib/main/main_local.dart

Demo Credentials

Role Username Password Access
Admin admin admin All pages
User user user Own profile and settings

Run Against the Real API

# Mobile
fvm flutter run --target lib/main/main_prod.dart

# Web
fvm flutter run -d chrome --target lib/main/main_prod.dart

The production environment is configured in lib/infrastructure/config/environment.dart.

Tech Stack

Category Technology
Flutter 3.44.2
Dart 3.12.2
State Manageme

Extension points exported contracts — how you extend this code

TestResult (Interface)
(no doc)
e2e/tests/all-features.spec.ts

Core symbols most depended-on inside this repo

l
called by 11
flutter_bootstrap.js
setTrustedTypesPolicy
called by 4
flutter_bootstrap.js
Scale
called by 4
windows/runner/win32_window.cpp
_
called by 2
flutter_bootstrap.js
C
called by 2
flutter_bootstrap.js
load
called by 2
flutter_bootstrap.js
_loadJSEntrypoint
called by 2
flutter_bootstrap.js
loadServiceWorker
called by 2
flutter_bootstrap.js

Shape

Method 41
Function 37
Class 9
Interface 1

Languages

C++57%
TypeScript42%
Kotlin1%

Modules by API surface

flutter_bootstrap.js29 symbols
windows/runner/win32_window.cpp23 symbols
linux/my_application.cc9 symbols
e2e/tests/all-features.spec.ts6 symbols
windows/runner/win32_window.h5 symbols
windows/runner/flutter_window.cpp5 symbols
windows/runner/utils.cpp3 symbols
flutter_service_worker.js2 symbols
windows/runner/main.cpp1 symbols
windows/runner/flutter_window.h1 symbols
windows/flutter/generated_plugin_registrant.cc1 symbols
linux/main.cc1 symbols

For agents

$ claude mcp add flutter-bloc-advanced \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page