blueprint_flutter_core 2.4.2 copy "blueprint_flutter_core: ^2.4.2" to clipboard
blueprint_flutter_core: ^2.4.2 copied to clipboard

A modular Flutter core package providing UI components, theming, and feature modules for scalable apps.

blueprint_flutter_core #

A modular Flutter core package that provides a complete foundation for scalable apps — covering theming, UI widgets, authentication flows, networking, push notifications, and profile management.


Features #

Module What it gives you
Core App configuration contract, root BlueprintFlutterCore widget, ProviderScope wiring, environment helpers, paginated models, JWT utilities, structured logging
Theme Abstract FxTheme / FxColors / FxSizes / FxTypography contracts, Material 3 ThemeData builder, persistent ThemeController (light / dark / system)
Widgets 30+ Fx-prefixed widgets: buttons, text fields, phone/PIN input, select & search fields, avatars, list tiles, bottom sheets, dialogs, snackbars, loaders, empty states, rich FxText with tappable spans
Network Pre-configured Dio client, TokenManager, AuthInterceptor, ErrorInterceptor, LogInterceptor, request-queuing on 401, base AppService class
Auth Full email (login / register / forgot-password) and phone OTP flows with built-in UI widgets (FxEmailAuth, FxPhoneAuth), Riverpod controllers, secure token storage
Notifications Firebase push notifications, device registration, NotificationController, FxNotificationTile
Profile ProfileController, ProfileService, FxProfileAvatar

Installation #

Add the package to your pubspec.yaml:

dependencies:
  blueprint_flutter_core: ^latest  # see badge above for current version

Then run:

flutter pub get

Getting Started #

1. Implement AppConfig #

Create your app-specific configuration by extending AppConfig:

// lib/config/app_config.dart
import 'package:blueprint_flutter_core/blueprint_flutter_core.dart';
import 'package:blueprint_flutter_core/blueprint_auth.dart';

class MyAppConfig extends AppConfig {
  @override
  String get appName => 'My App';

  @override
  String get apiBaseUrl => 'https://api.myapp.com';

  @override
  String get apiVersion => 'v1';

  @override
  AuthMethod get authMethod => AuthMethod.email; // or AuthMethod.phone

  @override
  bool get enableFirebase => true;

  @override
  bool get enableNotifications => true;
}

2. Bootstrap with BlueprintFlutterCore #

Wrap your app with BlueprintFlutterCore in main.dart. It sets up ProviderScope and makes appConfigProvider available throughout the tree:

import 'package:flutter/material.dart';
import 'package:blueprint_flutter_core/blueprint_flutter_core.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(
    BlueprintFlutterCore(
      config: MyAppConfig(),
      child: const MyApp(),
    ),
  );
}

3. Build the Theme #

Extend the four abstract theme classes to wire your brand into Material 3. Then pass the built ThemeData to MaterialApp:

// lib/theme/app_theme.dart
import 'package:blueprint_flutter_core/blueprint_theme.dart';

class AppTheme extends FxTheme {
  @override FxColors      get appColors     => AppColors();
  @override FxSizes       get appSizes      => AppSizes();
  @override FxTypography  get appTypography => AppTypography(colors: appColors, sizes: appSizes);
  @override FxThemeData   get appThemeData  => AppThemeData(
    colors: appColors, sizes: appSizes, typography: appTypography);
}
// lib/app.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:blueprint_flutter_core/blueprint_theme.dart';
import 'package:blueprint_flutter_core/blueprint_widgets.dart';

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeControllerProvider);
    final appTheme  = AppTheme();

    return MaterialApp.router(
      themeMode: themeMode,
      theme:     appTheme.build(Brightness.light),
      darkTheme: appTheme.build(Brightness.dark),
      routerConfig: appRouter,
      builder: (context, child) {
        // Initialise the responsive sizing utility once.
        ScreenUtil.init(context, designWidth: 390, designHeight: 844);
        return child ?? const SizedBox.shrink();
      },
    );
  }
}

Usage #

Barrel Imports #

Always import via the library barrels — never use deep src/ paths:

Library Use for
package:blueprint_flutter_core/blueprint_flutter_core.dart Config, environment, errors, pagination models, JWT, logger, BlueprintFlutterCore
package:blueprint_flutter_core/blueprint_theme.dart Theme tokens, ThemeController
package:blueprint_flutter_core/blueprint_widgets.dart Fx widgets, ScreenUtil, validators, formatters
package:blueprint_flutter_core/blueprint_network.dart ApiClient, services, Firebase client, interceptors
package:blueprint_flutter_core/blueprint_auth.dart Auth flows, controllers, email/phone widgets
package:blueprint_flutter_core/blueprint_notifications.dart Notifications
package:blueprint_flutter_core/blueprint_profile.dart Profile

Theme #

Implement the four abstract classes

// app_colors.dart
class AppColors extends FxColors {
  const AppColors({super.brightness});

  @override
  AppColors copyWith({Brightness? brightness}) =>
      AppColors(brightness: brightness ?? this.brightness);

  @override
  FxColorMode get primaryMode => const FxColorMode(
    light: Color(0xFF4F46E5),
    dark:  Color(0xFF818CF8),
  );

  @override FxColorMode get onPrimaryMode     => const FxColorMode(light: Colors.white,          dark: Color(0xFF1E1B4B));
  @override FxColorMode get secondaryMode     => const FxColorMode(light: Color(0xFF7C3AED),    dark: Color(0xFFA78BFA));
  @override FxColorMode get onSecondaryMode   => const FxColorMode(light: Colors.white,          dark: Color(0xFF2E1065));
  @override FxColorMode get errorMode         => const FxColorMode(light: Color(0xFFDC2626),    dark: Color(0xFFF87171));
  @override FxColorMode get onErrorMode       => const FxColorMode(light: Colors.white,          dark: Color(0xFF7F1D1D));
  @override FxColorMode get backgroundMode    => const FxColorMode(light: Color(0xFFF9FAFB),   dark: Color(0xFF0F172A));
  @override FxColorMode get onBackgroundMode  => const FxColorMode(light: Color(0xFF111827),   dark: Color(0xFFF1F5F9));
  @override FxColorMode get surfaceMode       => const FxColorMode(light: Colors.white,         dark: Color(0xFF1E293B));
  @override FxColorMode get onSurfaceMode     => const FxColorMode(light: Color(0xFF111827),   dark: Color(0xFFF1F5F9));
  @override FxColorMode get surfaceVariantMode=> const FxColorMode(light: Color(0xFFE0E7FF),   dark: Color(0xFF334155));
  @override FxColorMode get outlineMode       => const FxColorMode(light: Color(0xFFE2E8F0),   dark: Color(0xFF334155));
  @override FxColorMode get shadowMode        => const FxColorMode(light: Color(0x1A000000),   dark: Color(0x33000000));
  @override FxColorMode get successMode       => const FxColorMode(light: Color(0xFF16A34A),   dark: Color(0xFF4ADE80));
  @override FxColorMode get warningMode       => const FxColorMode(light: Color(0xFFD97706),   dark: Color(0xFFFBBF24));
  @override FxColorMode get infoMode          => const FxColorMode(light: Color(0xFF0284C7),   dark: Color(0xFF38BDF8));
  @override FxColorMode get textPrimaryMode   => const FxColorMode(light: Color(0xFF111827),   dark: Color(0xFFF1F5F9));
  @override FxColorMode get textSecondaryMode => const FxColorMode(light: Color(0xFF6B7280),   dark: Color(0xFF94A3B8));
  @override FxColorMode get textDisabledMode  => const FxColorMode(light: Color(0xFFD1D5DB),   dark: Color(0xFF475569));
  @override FxColorMode get textInverseMode   => const FxColorMode(light: Colors.white,         dark: Color(0xFF0F172A));
}
// app_sizes.dart — all values come from FxSizes defaults (ScreenUtil-scaled)
class AppSizes extends FxSizes {
  AppSizes();

  @override
  AppSizes copyWith() => AppSizes();
}
// app_typography.dart
class AppTypography extends FxTypography {
  const AppTypography({required super.colors, required super.sizes});

  @override String get headlineFontFamily => 'YourFont';
  @override String get bodyFontFamily     => 'YourFont';

  @override AppTypography copyWith({FxColors? colors, FxSizes? sizes}) =>
      AppTypography(colors: colors ?? this.colors, sizes: sizes ?? this.sizes);

  @override TextStyle get headlineLarge => TextStyle(fontSize: sizes.text4xl, fontWeight: FontWeight.w700, color: colors.textPrimary);
  @override TextStyle get bodyMedium    => TextStyle(fontSize: sizes.textMd,  fontWeight: FontWeight.w400, color: colors.textPrimary);
  // … implement all 16 TextStyle getters
}
// app_theme_data.dart — override sub-theme getters as needed
class AppThemeData extends FxThemeData {
  const AppThemeData({required super.colors, required super.sizes, required super.typography});

  @override
  AppThemeData copyWith({FxColors? colors, FxSizes? sizes, FxTypography? typography}) =>
      AppThemeData(
        colors:     colors     ?? this.colors,
        sizes:      sizes      ?? this.sizes,
        typography: typography ?? this.typography,
      );

  // Optional: override any sub-theme, e.g.:
  // @override ElevatedButtonThemeData get elevatedButtonTheme => ...
}

Toggle dark mode

// In any ConsumerWidget
ref.read(themeControllerProvider.notifier).toggleDark();
// or
ref.read(themeControllerProvider.notifier).setThemeMode(ThemeMode.dark);

Widgets #

Access theme tokens inside any widget via the FxUiToolkit mixin:

class MyWidget extends StatelessWidget with FxUiToolkit {
  MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    setToolkitContext(context); // call once at the top of build

    return Text('Hello', style: typography.headlineSmall);
  }
}

Available getters: theme, colorScheme, colors, sizes, typography, isDarkMode, screenWidth, screenHeight, isPhone, isTablet, plus navigation helpers (push, pop, pushReplace, pushAndClearStack) and overlay helpers (showFxBottomSheet, showFxDialog).

Buttons

// Primary (full-width by default)
FxButton(label: 'Continue', onPressed: () {}),

// Variants
FxButton(label: 'Cancel',  variant: FxButtonVariant.outline,    onPressed: () {}),
FxButton(label: 'Delete',  variant: FxButtonVariant.danger,     onPressed: () {}),
FxButton(label: 'Skip',    variant: FxButtonVariant.secondary,  onPressed: () {}),
FxButton(label: 'Link',    variant: FxButtonVariant.text,       onPressed: () {}),

// Loading state
FxButton(label: 'Saving…', isLoading: true, onPressed: () {}),

// With icon, not full-width
FxButton(
  label: 'Upload',
  isFullWidth: false,
  prefixIcon: const Icon(Icons.upload_outlined, size: 18),
  onPressed: () {},
),

Text Fields

FxTextField(
  label: 'Email',
  hint: 'you@example.com',
  keyboardType: TextInputType.emailAddress,
  prefixIcon: const Icon(Icons.mail_outline_rounded),
  validator: (v) => Validators.email(v),
),

FxTextField(
  label: 'Password',
  obscureText: true,                  // adds visibility toggle automatically
  validator: (v) => Validators.password(v),
),

FxSearchField(
  hint: 'Search…',
  debounceMs: 400,
  onChanged: (query) => search(query),
),

Select Field

FxSelectField<Country>(
  label: 'Country',
  selectedValue: _selectedCountry,
  data: FxSelectFieldData<Country>(
    items: countries,
    labelBuilder: (c) => c.name,
    leadingBuilder: (c, size) => FxCountryFlag(isoCode: c.code, size: size),
  ),
  onChanged: (country, _) => setState(() => _selectedCountry = country),
  onSearch: (query, items) => query == null
      ? items
      : items.where((c) => c.name.toLowerCase().contains(query.toLowerCase())).toList(),
),

Layout

FxScaffold(
  appBar: AppBar(title: const Text('My Screen')),
  body: Column(children: [...]),
  // padding defaults to sizes.md on all sides; override as needed
  padding: EdgeInsets.symmetric(horizontal: sizes.md),
),

Overlays

// Bottom sheet
showFxBottomSheet(
  data: FxOverlayData(
    title: 'Choose option',
    builder: (ctx) => MySheetContent(),
  ),
);

// Dialog
showFxDialog(
  data: FxOverlayData(
    title: 'Confirm',
    builder: (ctx) => MyDialogContent(),
  ),
);

// List-driven bottom sheet (built-in search + selection)
showFxBottomSheet<String>(
  data: FxOverlayData(
    title: 'Select a city',
    list: FxOverlayListData<String>(
      items: cities,
      titleTextBuilder: (c) => c,
      onSearch: (q, items) => q == null ? items : items.where((c) => c.contains(q)).toList(),
    ),
  ),
);

Snackbars

FxSnackbar.show(context, message: 'Saved!',        type: FxSnackbarType.success);
FxSnackbar.show(context, message: 'Network error', type: FxSnackbarType.error);
FxSnackbar.show(context, message: 'Heads up',      type: FxSnackbarType.warning);
FxSnackbar.show(context, message: 'FYI',           type: FxSnackbarType.info);

Display

// Avatar with initials fallback
FxAvatar(name: 'Alice Johnson', radius: 24),
FxAvatar(imageUrl: 'https://…/photo.jpg', radius: 20, showOnlineIndicator: true),

// List tile
FxListTile(
  leading: FxAvatar(name: user.fullName, radius: 20),
  title: user.fullName,
  subtitle: user.email,
  onTap: () => push('/profile/${user.id}'),
),

// Empty state
FxEmptyState(
  icon: const Icon(Icons.inbox_outlined),
  title: 'No messages yet',
  subtitle: 'When you receive a message, it will appear here.',
  actionLabel: 'Compose',
  onAction: () {},
),

// Rich text with tappable spans (wrap words in [ ])
FxText(
  'By continuing you agree to our [Terms] and [Privacy Policy].',
  onTap: (index, text) {
    if (index == 0) openTerms();
    if (index == 1) openPrivacyPolicy();
  },
),

Responsive sizing

// After ScreenUtil.init(context) in your MaterialApp builder:
SizedBox(width: 16.w, height: 48.h)
Text('Hello', style: TextStyle(fontSize: 14.sp))
BorderRadius.circular(8.r)

Network #

Define a service

import 'package:blueprint_flutter_core/blueprint_network.dart';

class PostsService extends AppService {
  PostsService(super.context);

  Future<List<Post>> getPosts() async {
    final response = await dio.get(endpoints.custom('posts'));
    return (response.data['data'] as List).map(Post.fromJson).toList();
  }
}

Provide the service with Riverpod

@riverpod
PostsService postsService(Ref ref) {
  return PostsService(ref.watch(serviceContextProvider));
}

The serviceContextProvider wires the authenticated Dio instance (with AuthInterceptor for automatic token refresh) and your ApiEndpoints into every service automatically.


Authentication #

Email flow

Drop FxEmailAuth anywhere in your widget tree. The controller and all state management are handled internally:

import 'package:blueprint_flutter_core/blueprint_auth.dart';
import 'package:blueprint_flutter_core/blueprint_widgets.dart';

FxEmailAuth(
  onAuthSuccess: () => context.go('/home'),
  theme: const FxEmailAuthTheme(
    loginConfig: LoginConfig(
      title: 'Welcome back',
      subtitle: 'Sign in to continue',
      buttonLabel: 'Sign In',
    ),
    registerConfig: RegisterConfig(
      title: 'Create account',
      subtitle: 'Fill in your details',
      buttonLabel: 'Create Account',
    ),
  ),
),

Steps handled automatically: Login → Register → Forgot Password (request PIN → verify PIN → reset password).

Phone OTP flow

FxPhoneAuth(
  onAuthSuccess: () => context.go('/home'),
  theme: const FxPhoneAuthTheme(
    phoneEntryConfig: PhoneEntryConfig(
      title: 'Enter your number',
      subtitle: "We'll send you a verification code",
      buttonLabel: 'Send Code',
    ),
    otpVerificationConfig: OtpVerificationConfig(
      title: 'Verify your number',
      subtitle: 'Enter the code sent to {{phone}}',
      otpLength: 6,
      resendCooldownSeconds: 60,
    ),
    userDetailsConfig: UserDetailsConfig(
      title: 'Complete your profile',
      requireEmail: false,
    ),
  ),
),

Steps handled automatically: Enter phone → OTP verification → User details (for new users).

Reading auth state

// In any ConsumerWidget
final authState = ref.watch(authControllerProvider);

if (authState.isAuthenticated) {
  print(authState.user?.fullName);
}

// Sign out
ref.read(authControllerProvider.notifier).signOut();

Notifications #

import 'package:blueprint_flutter_core/blueprint_notifications.dart';

// In a ConsumerWidget
final state = ref.watch(notificationControllerProvider);

// Load notifications
ref.read(notificationControllerProvider.notifier).loadNotifications();

// Mark as read
ref.read(notificationControllerProvider.notifier).markAsRead(notificationId);

// Built-in tile
FxNotificationTile(notification: state.notifications.first),

Profile #

import 'package:blueprint_flutter_core/blueprint_profile.dart';

// Access profile
final profile = ref.watch(profileControllerProvider).user;

// Update profile
await ref.read(profileControllerProvider.notifier).updateProfile(
  ProfileUpdate(firstName: 'Alice', lastName: 'Johnson'),
);

// Avatar widget
FxProfileAvatar(radius: 40),

Validators #

import 'package:blueprint_flutter_core/blueprint_widgets.dart';

TextFormField(
  validator: (v) => Validators.email(v),
)

// Available: required, email, phone, password, confirmPassword, minLength, maxLength
Validators.confirmPassword(confirmValue, originalValue)
Validators.minLength(value, 8)

Formatters #

import 'package:blueprint_flutter_core/blueprint_widgets.dart';

DateFormatter.format(DateTime.now(), pattern: 'dd MMM yyyy')  // → '19 Apr 2026'
DateFormatter.relative(DateTime.now().subtract(Duration(hours: 2))) // → '2 hours ago'

NumberFormatter.currency(1234.5, symbol: '\$')  // → '$1,234.50'
NumberFormatter.compact(1500000)                // → '1.5M'

TextFormatter.initials('Alice Johnson')  // → 'AJ'
TextFormatter.truncate('Long text…', 20)

Example App #

A full working example lives in the example/ directory. It demonstrates:

  • BlueprintFlutterCore bootstrap and AppConfig implementation
  • Complete FxTheme, FxColors, FxSizes, FxTypography, FxThemeData implementation
  • Light / dark mode toggle persisted across launches
  • Email and phone auth flows side-by-side
  • Comprehensive widget showcase (buttons, inputs, select, overlays, display, feedback)

Run it with:

cd example
flutter run

Architecture #

lib/
├── blueprint_flutter_core.dart
├── blueprint_theme.dart          # Theme tokens + ThemeController
├── blueprint_widgets.dart        # All Fx* widgets, ScreenUtil, validators, formatters
├── blueprint_network.dart        # Dio client, interceptors, AppService
├── blueprint_auth.dart           # Auth module (email + phone)
├── blueprint_notifications.dart  # Push notifications
└── blueprint_profile.dart        # User profile

Each module is independently importable — only add what your app needs.


Additional Information #

  • Issues & feature requests: GitHub Issues
  • Contributing: PRs are welcome. Please open an issue first for major changes.
  • License: See LICENSE