🚀 starter_kit_flutter

A batteries-included Flutter package that sets up common app foundations from one place.

starter_kit_flutter hero

Ship features faster — stop re-wiring the same boilerplate in every project.

🌐 Networking Dio + interceptors + sealed Result / AppError
💾 Storage SharedPreferences + Flutter Secure Storage
🎨 Theming Light / dark / system with persistence
🧱 Design system Spacing, radius, shadows, typography tokens
🚩 Feature flags Runtime flags + conditional UI (StarterKitSlot)
🛡️ Safe JSON Crash-proof parsing against API key drift
🛠️ Dev Panel Debug-only in-app inspector (kDebugMode)
📡 Connectivity Monitoring with optional offline banner
🔄 Updater Force-update & maintenance screens
✨ Utilities Extensions, validators, loader & toast overlays
🖼️ App icons CLI wrapper around flutter_launcher_icons

Package: starter_kit_flutter on pub.dev
Import: package:starter_kit_flutter/starter_kit_flutter.dart
Compat export: package:starter_kit_flutter/starter_kit.dart


📦 Installation

dependencies:
  starter_kit_flutter:
    git:
      url: https://gitlab.com/moin-quantana/starter_kit.git
      ref: main

Or from pub.dev:

dependencies:
  starter_kit_flutter: ^1.0.5
flutter pub get

Use StarterKitApp as the single integration point. It initializes modules and wraps overlays for you.

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

void main() {
  runApp(
    StarterKitApp(
      config: StarterKitConfig(
        networkConfig: const NetworkConfig(
          baseUrl: 'https://api.example.com',
          authTokenKey: 'auth_token',
        ),
        storageConfig: const StorageConfig(),
        themeConfig: const ThemeConfig(
          defaultThemeMode: ThemeMode.system,
        ),
        updaterConfig: const UpdaterConfig(
          versionCheckUrl: 'https://api.example.com/version',
          enableAutoCheck: true,
          enableMaintenanceMode: true,
        ),
        modules: const StarterKitModulesConfig(
          network: StarterKitModuleMode.enabled,
          storage: StarterKitModuleMode.enabled,
          theme: StarterKitModuleMode.enabled,
          connectivity: StarterKitModuleMode.enabled,
          updater: StarterKitModuleMode.enabled,
          featureFlags: StarterKitModuleMode.enabled,
        ),
        featureFlagConfig: const FeatureFlagConfig(
          initialFlags: {'promo_banner': true},
        ),
        ui: const StarterKitUiConfig(
          showConnectivityBanner: true,
          showForceUpdateScreen: true,
          showMaintenanceScreen: true,
          enableGlobalLoader: true,
          enableDevPanel: true,
          devPanelTrigger: DevPanelTrigger.floatingButton,
        ),
        designSystemConfig: const DesignSystemConfig(
          spacing: DesignSpacingConfig(
            xxs: 4,
            xs: 8,
            sm: 12,
            md: 16,
            lg: 20,
            xl: 24,
            xxl: 32,
            section: 40,
          ),
          radius: DesignRadiusConfig(
            sm: 8,
            md: 12,
            lg: 16,
            xl: 24,
          ),
          typography: DesignTypographyConfig(
            titleLargeSize: 24,
            bodyLargeSize: 16,
            bodyMediumSize: 14,
          ),
        ),
      ),
      appBuilder: (context, data) {
        return MaterialApp(
          navigatorKey: data.navigatorKey,
          theme: data.lightTheme,
          darkTheme: data.darkTheme,
          themeMode: data.themeMode,
          home: const HomePage(),
        );
      },
    ),
  );
}

Access services anywhere after init:

StarterKit.I.storage
StarterKit.I.network
StarterKit.I.theme
StarterKit.I.connectivity
StarterKit.I.updater
StarterKit.I.featureFlags

🎛️ Module Toggles

StarterKitModulesConfig controls each module explicitly:

Mode Behavior
enabled Register the real implementation
stub Register a stub (safe no-ops / controlled fallbacks)
disabled Unregister; accessors still fall back to stubs if called
const StarterKitModulesConfig(
  network: StarterKitModuleMode.enabled,
  storage: StarterKitModuleMode.enabled,
  theme: StarterKitModuleMode.enabled,
  connectivity: StarterKitModuleMode.enabled,
  updater: StarterKitModuleMode.disabled,
  featureFlags: StarterKitModuleMode.enabled,
)

Predictable behavior:

  • Stub — calls succeed structurally with controlled fallbacks (e.g. NETWORK_NOT_INITIALIZED)
  • Disabled — module not registered; accessor stubs prevent hard crashes
  • Enabled + missing config — init validation throws a clear configuration error

🖼️ UI Controls

StarterKitUiConfig centralizes global UI behavior applied by StarterKitApp / StarterKit.builder:

const StarterKitUiConfig(
  showConnectivityBanner: true,
  showForceUpdateScreen: true,
  showMaintenanceScreen: true,
  enableGlobalLoader: true,
  enableDevPanel: true, // only active when kDebugMode == true
  devPanelTrigger: DevPanelTrigger.floatingButton, // or .tripleTap / .both
)

🚩 Feature Flags & Conditional UI

Seed flags from config:

StarterKitConfig(
  featureFlagConfig: const FeatureFlagConfig(
    initialFlags: {
      'new_checkout': false,
      'promo_banner': true,
    },
  ),
  modules: const StarterKitModulesConfig(
    featureFlags: StarterKitModuleMode.enabled,
  ),
)

Runtime access:

final enabled = StarterKit.I.featureFlags.isEnabled('new_checkout');
await StarterKit.I.featureFlags.setOverride('new_checkout', true);
await StarterKit.I.featureFlags.clearOverride('new_checkout');
await StarterKit.I.featureFlags.applyRemoteValues({'promo_banner': true});

Conditional rendering with StarterKitSlot:

StarterKitSlot(
  featureFlag: 'promo_banner',
  flaggedChild: const PromoBanner(),
  fallback: const SizedBox.shrink(),
  child: const SizedBox.shrink(),
)

🛡️ Safe JSON Parsing

Tolerate backend key renames without crashing:

final json = response as Map<String, dynamic>;

final id = json.asString(['account_id', 'user_id', 'id'], fallback: '');
final total = json.asInt(['total', 'count'], fallback: 0);
final active = json.asBool(['is_active', 'active', 'enabled'], fallback: false);
final nested = json.asMap(['data', 'payload'], fallback: {});

// Or:
SafeJsonReader.asString(json, ['account_id', 'user_id', 'id']);

🛠️ In-App Dev Panel (Debug Only)

Mounted only when kDebugMode == true and enableDevPanel: true.

Tab What it does
Network Request logs, artificial delay, force 500 / 401 / offline
Storage Search / edit / delete SharedPreferences (+ secure read helper)
Design Live theme mode + spacing / radius token tweaks
Overlays Simulate offline / maintenance / force-update + toast
Flags Toggle feature flags + base URL / key remapper
ui: const StarterKitUiConfig(
  enableDevPanel: true,
  devPanelTrigger: DevPanelTrigger.both, // FAB and/or triple-tap top-left
)

Release safety: Dev Panel host and Dio debug interceptor are gated by kDebugMode.


🎨 Centralized Design System

DesignSystemConfig provides one-place visual tokens:

const DesignSystemConfig(
  spacing: DesignSpacingConfig(
    xxs: 4, xs: 8, sm: 12, md: 16,
    lg: 20, xl: 24, xxl: 32, section: 40,
  ),
  radius: DesignRadiusConfig(sm: 8, md: 12, lg: 16, xl: 24),
  shadow: DesignShadowConfig(cardElevation: 2),
  typography: DesignTypographyConfig(
    fontFamily: null,
    titleLargeSize: 24,
    bodyLargeSize: 16,
    bodyMediumSize: 14,
    labelMediumSize: 12,
  ),
)
final spacing = context.dsSpacing;
final radius = context.dsRadius;
final shadows = context.dsShadow;
final typography = context.dsTypography;

const VSpace(DesignSpacingSize.md);
const HSpace(DesignSpacingSize.sm);
SKSizedBox.h16;
SKSizedBox.w8;

⚠️ Error Model

Storage and network operations return sealed Result / AppError — no raw exceptions to the UI.

final result = await StarterKit.I.network.get<Map<String, dynamic>>(
  '/users/1',
  fromJson: (json) => json as Map<String, dynamic>,
);

result.fold(
  (error) {
    switch (error) {
      case NetworkError(:final statusCode):
        debugPrint('Network $statusCode: ${error.message}');
      case ConnectionError():
        debugPrint('Offline: ${error.message}');
      case StorageError():
        debugPrint('Storage: ${error.message}');
      case ValidationError():
        debugPrint('Validation: ${error.message}');
      case ConfigurationError():
        debugPrint('Config: ${error.message}');
      case UnknownError():
        debugPrint('Unknown: ${error.message}');
    }
  },
  (data) => debugPrint('Success: $data'),
);

// Helpers: result.isSuccess, result.dataOrNull, result.errorOrNull

📚 Usage Examples

🌐 Networking

final getResult = await StarterKit.I.network.get<Map<String, dynamic>>(
  '/users/123',
  fromJson: (json) => json as Map<String, dynamic>,
);

final postResult = await StarterKit.I.network.post<Map<String, dynamic>>(
  '/users',
  data: {'name': 'John Doe', 'email': 'john@example.com'},
  fromJson: (json) => json as Map<String, dynamic>,
);

// PUT, PATCH, DELETE are also available

💾 Storage

await StarterKit.I.storage.setString('username', 'john_doe');
final username = await StarterKit.I.storage.getString('username');

await StarterKit.I.storage.setInt('count', 42);
await StarterKit.I.storage.setBool('isLoggedIn', true);
await StarterKit.I.storage.setDouble('rating', 4.5);
await StarterKit.I.storage.setStringList('tags', ['flutter', 'dart']);

🎨 Theme

await StarterKit.I.theme.setThemeMode(ThemeMode.dark);
await StarterKit.I.theme.toggleTheme();

MaterialApp(
  theme: StarterKit.I.theme.lightTheme,
  darkTheme: StarterKit.I.theme.darkTheme,
  themeMode: StarterKit.I.theme.currentThemeMode,
);

📡 Connectivity

StarterKit.I.connectivity.connectionStream.listen((state) {
  switch (state) {
    case NetworkConnectionState.connected:
      debugPrint('Online');
    case NetworkConnectionState.disconnected:
      debugPrint('Offline');
    case NetworkConnectionState.unknown:
      debugPrint('Unknown');
  }
});

final current = StarterKit.I.connectivity.currentState;

🔄 Updater

final status = await StarterKit.I.updater.checkForUpdates();

switch (status) {
  case UpdateStatus.upToDate:
  case UpdateStatus.updateAvailable:
  case UpdateStatus.forceUpdateRequired:
  case UpdateStatus.maintenanceMode:
  case UpdateStatus.unknown:
    debugPrint('$status');
}

StarterKit.I.updater.updateStatusStream.listen((status) {
  // react to status changes
});

💬 Overlays

GlobalLoader(
  isLoading: _isLoading,
  child: YourContent(),
);

ToastOverlay.show(context, 'Hello!');
ToastOverlay.showSuccess(context, 'Success!');
ToastOverlay.showError(context, 'Error!');
ToastOverlay.showWarning(context, 'Warning!');
ToastOverlay.showInfo(context, 'Info!');

final ctx = StarterKit.navigatorKey.currentContext;
if (ctx != null) {
  ToastOverlay.showSuccess(ctx, 'Done');
}

✨ Utilities

// Strings
'user@example.com'.isValidEmail;
'john doe'.capitalize();
'john doe'.capitalizeWords();
'   '.isBlank;

// Context
context.screenWidth;
context.screenHeight;
context.showSnackBar('Hello!');
context.showSuccessSnackBar('OK');
context.showErrorSnackBar('Failed');

// Responsive
20.h(context); // % of height
50.w(context); // % of width
10.sp(context); // % of smaller dimension

// Dates
DateTime.now().toReadable();
DateTime.now().toShortReadable();
DateTime.now().isToday;

// Form validators
TextFormField(validator: emailValidator());
TextFormField(
  validator: combineValidators([
    requiredValidator('Password'),
    minLengthValidator(8, 'Password'),
  ]),
);
TextFormField(validator: phoneValidator());

⚙️ Configuration Reference

NetworkConfig

NetworkConfig(
  baseUrl: 'https://api.example.com',
  connectTimeout: 30,
  receiveTimeout: 30,
  sendTimeout: 30,
  enableLogging: true,
  enableRetry: true,
  maxRetries: 3,
  authTokenKey: 'auth_token',
  headers: {'X-API-Key': 'your-api-key'},
)

StorageConfig

StorageConfig(
  keyPrefix: 'my_app_',
  enableSecureStorage: true,
  enablePreferencesStorage: true,
)

ThemeConfig

ThemeConfig(
  defaultThemeMode: ThemeMode.system,
  lightTheme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  themeStorageKey: 'theme_mode',
)

UpdaterConfig

UpdaterConfig(
  versionCheckUrl: 'https://api.example.com/version',
  currentVersion: '1.0.0', // optional; auto from package_info if omitted
  checkInterval: 3600,
  enableAutoCheck: true,
  enableMaintenanceMode: true,
  headers: {'Authorization': 'Bearer token'},
)

FeatureFlagConfig

FeatureFlagConfig(
  initialFlags: {'promo_banner': true},
  storageKey: 'starter_kit_feature_flags',
)

🧩 Manual / Advanced Path

If you need explicit control over init order:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await StarterKit.init(
    StarterKitConfig(
      networkConfig: const NetworkConfig(baseUrl: 'https://api.example.com'),
      storageConfig: const StorageConfig(),
      themeConfig: const ThemeConfig(),
    ),
  );

  runApp(
    MaterialApp(
      navigatorKey: StarterKit.navigatorKey,
      builder: (context, child) => StarterKit.builder(child: child!),
      home: const HomePage(),
    ),
  );
}

Re-initialize at runtime:

await StarterKit.init(newConfig, true);

🖼️ App Icon Generation (CLI)

Wrapper around flutter_launcher_icons. Run from the consuming app root.

  1. Add to the app pubspec.yaml:
dev_dependencies:
  flutter_launcher_icons: ^0.14.4
  1. Generate:
flutter pub get
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png

Optional flags:

dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png --no-ios
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png --no-android
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png \
  --adaptive-bg "#FFFFFF" --adaptive-fg assets/logo/fg.png

A temporary launcher config is created and removed after the run.


🔑 Public API (Beginner First)

Primary

  • StarterKitApp
  • StarterKitConfig / StarterKitModulesConfig / StarterKitUiConfig
  • DesignSystemConfig / FeatureFlagConfig
  • StarterKit.I services (storage, network, theme, connectivity, updater, featureFlags)
  • StarterKitSlot
  • SafeJsonReader + Map key-chain extensions
  • Sealed Result / AppError

Advanced

  • StarterKit.init(...) / StarterKit.builder(...) / StarterKit.navigatorKey
  • Overlays: GlobalLoader, ToastOverlay, connectivity / force-update / maintenance screens
  • Debug: StarterKitDevPanel, DevPanelTrigger, StarterKitDebugController
  • Extensions, validators, ResponsiveSizer

🏗️ Architecture

  • Facade — host apps talk to StarterKit / StarterKit.I only
  • Modules — enabled / stub / disabled registration via GetIt
  • Interfaces — INetworkClient, IStorageService, IConnectivityService, IUpdaterService, IFeatureFlagService
  • Errors — sealed Result + sealed AppError hierarchy
lib/
├── src/
│   ├── core/
│   │   ├── data/              # Network & storage implementations
│   │   ├── services/          # Connectivity, updater, feature flags
│   │   ├── design_system/     # Theme manager, spacing, responsive
│   │   ├── widgets/           # App, builder, slot, overlays, Dev Panel
│   │   ├── models/            # Configs & responses
│   │   ├── exceptions/        # Result & AppError
│   │   ├── utils/             # SafeJson, extensions, validators
│   │   ├── debug/             # Dev Panel controller
│   │   └── helpers/           # Service locator, logger, routing keys
│   └── facade/
│       └── starter_kit.dart
├── starter_kit_flutter.dart   # Public export
└── starter_kit.dart           # Compatibility re-export

✅ Requirements

  • Flutter SDK >= 3.0.0
  • Dart SDK >= 3.0.0 < 4.0.0

📌 Dependencies

Package Constraint
dio ^5.11.1
get_it ^9.2.0
shared_preferences ^2.5.3
flutter_secure_storage >=10.0.0 <12.0.0
connectivity_plus >=6.1.5 <8.0.0
package_info_plus >=9.0.0 <11.0.0

📄 License

MIT — see LICENSE.

💬 Support

Issues and contributions: GitLab.

👤 Authors

Libraries

starter_kit
Compatibility export for the previous library path.
starter_kit_flutter
StarterKit Flutter - A batteries-included foundational Flutter package.