Home
Softono
flutter-bloc-advanced

flutter-bloc-advanced

Open source MIT JavaScript
136
Stars
43
Forks
0
Issues
2
Watchers
1 week
Last Commit

About flutter-bloc-advanced

Flutter Template : Feature-First Clean-Arch - BLoC Pattern, Environments, Configuration, Themes, Desktop, Mobile and Web

Platforms

Web Self-hosted

Languages

JavaScript

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://[email protected]/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.0 and Dart 3.12.0
  • 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.0
fvm use 3.44.0
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.0
Dart 3.12.0
State Management flutter_bloc 9.1.1, bloc_concurrency 0.3.0, stream_transform 2.1.1
Routing go_router 17.2.3
HTTP dio 5.9.2 (interceptor chain: connectivity, auth, token-refresh, resilience, mock, cache, dev-console, logging)
Forms flutter_form_builder 10.3.0+2
Localization intl 0.20.2, intl_utils 2.8.14
Storage shared_preferences 2.5.5, flutter_secure_storage 10.2.0
Charts fl_chart 1.2.0
Testing flutter_test, bloc_test, mocktail, test 1.31.0

Project Structure

lib/
  app/                 # Application foundation
    di/                # Dependency injection (manual, no get_it)
    router/            # Centralized routing with go_router
    shell/             # Responsive shell (sidebar, top bar, bottom nav, command palette)
    theme/             # Theme management
  core/                # Zero-dependency foundation
    errors/            # AppError sealed hierarchy + API exceptions
    result/            # Result<T> sealed type (Success / Failure)
    logging/           # Structured logging (AppLogger)
    security/          # JWT utilities
  features/            # Feature-based clean architecture modules
    <feature>/
      application/     # BLoCs, Use Cases
      data/            # Models, Repositories (concrete implementations)
      domain/          # Entities, Repository interfaces
      navigation/      # Feature-specific routes
      presentation/    # Pages and feature-specific widgets
  infrastructure/      # Cross-cutting technical concerns
    config/            # Environment, TemplateConfig
    http/              # Dio ApiClient + interceptors (auth, logging, mock)
    storage/           # Local storage (SharedPreferences)
  shared/              # Shared foundation across features
    design_system/     # Theme, typography, color tokens, 16 components
    dynamic_forms/     # Server-driven form engine (16 field types, bundle load, public withBloc helper)
    models/            # Cross-feature entities (UserEntity, PagedResult)
    widgets/           # Reusable components (buttons, forms, dialogs)
  generated/           # Localization generated files (do not edit)
  l10n/                # ARB translation files
  main/                # Entry points (main_local.dart, main_prod.dart)

test/                  # Mirrors lib/ structure
  architecture/        # Import guard tests (dependency rule enforcement)
  features/            # Feature tests (application, data, presentation)
  mocks/               # Mock classes and fake data

Customizing for Your Project

All template placeholders use __KEYWORD__ naming. Run a global search for __ across the project, replace each keyword with your own value, and you are done.

Keyword Description Example Where Used
__APP_NAME__ Full application name Acme Dashboard template_config.dart, manifest.json
__APP_SHORT_NAME__ Short name (PWA, mobile) Acme template_config.dart, manifest.json
__APP_DESCRIPTION__ One-line app description Team collaboration platform template_config.dart, manifest.json
__PROD_API_URL__ Production API endpoint https://api.acme.com/v1 template_config.dart, index.html
__WEB_BASE_URL__ Deployment URL (trailing /) https://app.acme.com/ template_config.dart, index.html, sitemap.xml
__GITHUB_REPO_URL__ GitHub repository URL https://github.com/acme/dashboard template_config.dart
__AUTHOR_NAME__ Developer or company name Acme Inc. template_config.dart, index.html, humans.txt
__AUTHOR_EMAIL__ Contact email [email protected] template_config.dart, CONTRIBUTING.md
__AUTHOR_URL__ Author website / profile https://github.com/acme template_config.dart, index.html, humans.txt
__AUTHOR_LOCATION__ Author location San Francisco, CA humans.txt

Also update manually:

  1. pubspec.yaml — Package name, version, description.
  2. web/index.html — Page <title>, meta descriptions, Open Graph / Twitter Card text.
  3. lib/l10n/intl_en.arb / intl_tr.arb — App-specific translations.

The central configuration file is lib/infrastructure/config/template_config.dart — Dart code throughout the app reads from this class.

Build, Test, and Quality

Build

# Android APK
fvm flutter build apk --target lib/main/main_prod.dart

# iOS
fvm flutter build ios --target lib/main/main_prod.dart

# Web
fvm flutter build web --target lib/main/main_prod.dart

Test

# Run all tests
fvm flutter test

# With coverage
fvm flutter test --coverage

# Useful for debugging order-dependent issues
fvm flutter test --concurrency=1 --test-randomize-ordering-seed=random

# Run only widget tests / everything except widget tests (fast unit loop)
fvm flutter test --tags widget
fvm flutter test --exclude-tags widget

Tags are declared in dart_test.yaml (widget, golden; integration is reserved for a possible future on-device suite). The end-to-end smoke (test/integration/app_smoke_test.dart) boots the whole app in mock mode and drives login → dashboard; it runs headless as part of the normal flutter test.

# Golden / visual-regression tests (alchemist, CI/Ahem-rendered)
fvm flutter test --tags golden                  # run goldens
fvm flutter test --tags golden --update-goldens # regenerate after intentional UI changes

Golden tests live next to the code they cover (feature-first, mirroring lib/; images in a sibling goldens/ci/ dir) and use alchemist's CI (Ahem-rendered) variant so they're identical across macOS/Linux/CI. Regenerate with --update-goldens after an intentional visual change.

See docs/testing-architecture.md for the test structure, global bootstrap, and how to write tests for each layer.

Analyze and Format

fvm dart analyze
fvm dart fix --apply
fvm dart format . --line-length=120

Git Hooks (Optional)

Install pre-commit (format + analyze) and pre-push (test) hooks:

bash scripts/setup_hooks.sh

Test Coverage Focus

Layer What is Tested
Use Cases Business logic delegation, input routing
Models / Entities fromJson, toJson, equality, copyWith
Mappers Entity ↔ Model transformations
Repositories API calls, Result type returns, error mapping
BLoCs State transitions, event handling, error states
Screens Widget rendering, user interactions, navigation
Architecture Import guard tests enforcing dependency rules

CI/CD

GitHub Actions workflows included in this repository:

  • build_and_test.yml for build and test automation
  • build-web.yml for web builds
  • sonar_scanner.yml for SonarQube analysis

To enable SonarQube, add the SONAR_TOKEN secret to your repository or organization.

Android Tooling

Component Version
Gradle 8.14
Android Gradle Plugin 8.11.1
Kotlin 2.2.20
Java Compatibility 17
NDK Dynamic (flutter.ndkVersion)
Build Config Kotlin DSL (.gradle.kts)

Adding a New Feature

  1. Create a new feature folder under lib/features/<feature>/.
  2. Define domain entities in domain/entities/ and repository interface in domain/repositories/.
  3. Implement data models in data/models/ and repository in data/repositories/ (return Result<T>).
  4. Add use cases in application/usecases/ (one class per operation).
  5. Create BLoC(s) in application/ using switch (result) { case Success: ... case Failure: ... }.
  6. Implement the UI in presentation/pages/.
  7. Define feature routes in navigation/.
  8. Register the feature routes in lib/app/router/app_router.dart.
  9. Register DI in lib/app/di/app_dependencies.dart (repository) and app_scope.dart (BLoC).
  10. Add tests in test/features/<feature>/ mirroring the feature structure.

Dependency rules (enforced by architecture guard tests):

  • core/ imports nothing from the project
  • shared/ imports only from core/
  • features/ imports from shared/, infrastructure/, core/ — never from other features
  • app/ can import from all layers

Contributing

Community contributions are welcome. If you want to improve the template, add features, polish documentation, or refine the UI, feel free to open an issue or submit a pull request.

  1. Fork the repository.
  2. Create a feature branch from your fork.
  3. Make your changes with tests or documentation updates when relevant.
  4. Run fvm dart analyze and fvm flutter test.
  5. Open a pull request with a clear summary of the change.

If you are unsure where to start, documentation improvements, screenshot refreshes, and test coverage enhancements are all valuable contributions.

Documentation

Document Description
Transformation Log What changed, why, before/after comparison
Architecture Migration Feature-first migration guide
Feature-First Boundaries Clean architecture design document
Upgrade Guide — Flutter 3.44.0 Flutter 3.41.8 → 3.44.0 upgrade notes (v0.21.0)
Upgrade Guide — Flutter 3.41.4 Historical: Flutter 3.41.4 upgrade notes (v0.18.0)

References

Ask DeepWiki

License

This project is licensed under the MIT License. See the LICENSE file for details.