starter_kit_flutter 1.0.0 copy "starter_kit_flutter: ^1.0.0" to clipboard
starter_kit_flutter: ^1.0.0 copied to clipboard

A batteries-included foundational Flutter package for networking, connectivity, storage, theming, app updates, and core utilities.

StarterKit #

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

  • networking (Dio + interceptors + result-based errors)
  • storage (preferences + secure storage)
  • theming (light/dark/system with persistence)
  • centralized design-system tokens
  • feature flags + conditional slots
  • safe JSON parsing against API key drift
  • debug-only in-app Dev Panel
  • connectivity monitoring
  • app update/maintenance checks
  • utility extensions and validators

The package is designed so teams can reuse the same setup across multiple projects with minimal wiring.

Installation #

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

Or if published:

dependencies:
  starter_kit_flutter: ^1.0.0

Then run:

flutter pub get

App Icon Generation (CLI Wrapper) #

You can generate launcher icons from a logo path using StarterKit's CLI wrapper around flutter_launcher_icons.

  1. In your app's pubspec.yaml, add:
dev_dependencies:
  flutter_launcher_icons: ^0.14.4
  1. Run:
flutter pub get
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png

Optional flags:

# Android only
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png --no-ios

# iOS only
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png --no-android

# Adaptive icon options (Android)
dart run starter_kit_flutter:starter_kit_icons --logo assets/logo/app_logo.png --adaptive-bg "#FFFFFF" --adaptive-fg assets/logo/fg.png

Notes:

  • Run the command from the consuming Flutter app root (where the app pubspec.yaml exists).
  • The command creates a temporary launcher config file and removes it after execution.
  • Generated platform icon files are the same output path behavior as flutter_launcher_icons.

Use StarterKitApp as the single integration point.

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(),
        );
      },
    ),
  );
}

Module Toggles #

StarterKitModulesConfig lets you explicitly control each module:

  • enabled: register real implementation
  • stub: register stub implementation
  • disabled: unregister module (accessor falls back to stub behavior if accessed)

Example:

const StarterKitModulesConfig(
  network: StarterKitModuleMode.enabled,
  storage: StarterKitModuleMode.enabled,
  theme: StarterKitModuleMode.enabled,
  connectivity: StarterKitModuleMode.enabled,
  updater: StarterKitModuleMode.disabled,
)

UI Controls #

StarterKitUiConfig centralizes global UI behavior:

const StarterKitUiConfig(
  showConnectivityBanner: true,
  showForceUpdateScreen: true,
  showMaintenanceScreen: true,
  enableGlobalLoader: true,
  enableDevPanel: true, // Only active in debug mode
  devPanelTrigger: DevPanelTrigger.floatingButton, // or .tripleTap / .both
)

These flags are applied by StarterKit.builder/StarterKitApp.

Safe JSON Parsing (API Breaking-Change Guardrails) #

Use SafeJsonReader / Map extensions to tolerate backend key renames:

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 via helper class:

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

Feature Flags #

Enable the module and seed flags from one place:

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

Access at runtime:

final enabled = StarterKit.I.featureFlags.isEnabled('new_checkout');
await StarterKit.I.featureFlags.setOverride('new_checkout', true);

Render conditionally with StarterKitSlot:

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

In-App Dev Panel (Debug Only) #

StarterKitDevPanel is mounted only when kDebugMode == true and enableDevPanel: true.

Tabs:

  1. Network — request logs, artificial delay, force 500/401/offline
  2. Storage — search/edit/delete SharedPreferences keys (+ secure read helper)
  3. Design — live theme mode + spacing/radius token tweaks
  4. Overlays — simulate offline/maintenance/force-update + toast
  5. Flags — toggle feature flags + base URL override

Open via floating debug FAB and/or triple-tap top-left corner depending on devPanelTrigger.

ui: const StarterKitUiConfig(
  enableDevPanel: true,
  devPanelTrigger: DevPanelTrigger.both,
)

Release safety: Dev Panel host and Dio debug interceptor are wrapped in kDebugMode checks so release builds do not activate debug tooling.

Centralized Design System #

DesignSystemConfig gives one-place control for visual consistency across projects:

  • spacing scale
  • corner radius scale
  • shadow/elevation tokens
  • typography and font scale
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, // Set custom family if available
    titleLargeSize: 24,
    bodyLargeSize: 16,
    bodyMediumSize: 14,
    labelMediumSize: 12,
  ),
)

Access tokens anywhere:

final spacing = context.dsSpacing;
final radius = context.dsRadius;
final shadows = context.dsShadow;
final typography = context.dsTypography;

Use reusable spacing widgets (no inline magic numbers):

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

Service Access #

Access all services through the facade:

final storage = StarterKit.I.storage;
final network = StarterKit.I.network;
final theme = StarterKit.I.theme;
final connectivity = StarterKit.I.connectivity;
final updater = StarterKit.I.updater;

Error Model #

All storage/network operations return Result<T, AppError>.

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

result.fold(
  (error) => debugPrint('Error: ${error.message}'),
  (data) => debugPrint('Success: $data'),
);

Behavior When Modules Are Not Enabled #

StarterKit is explicit and predictable:

  • Stub mode: calls succeed structurally but return controlled fallback behavior (e.g. NETWORK_NOT_INITIALIZED).
  • Disabled mode: module is not registered; accessor fallback still prevents hard crashes.
  • Enabled mode + missing config: init validation throws with a clear configuration error.

Manual/Advanced Path (Backward Compatible) #

You can still initialize manually:

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(),
    ),
  );
}

If you need to rebuild registrations at runtime:

await StarterKit.init(newConfig, true);

Common Examples #

Storage #

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

Theme #

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

Connectivity #

StarterKit.I.connectivity.connectionStream.listen((state) {
  debugPrint('Connectivity state: $state');
});

Updater #

final status = await StarterKit.I.updater.checkForUpdates();
debugPrint('Update status: $status');

Public API (Beginner First) #

Primary:

  • StarterKitApp
  • StarterKitConfig
  • StarterKitModulesConfig
  • StarterKitUiConfig
  • StarterKit.I.<service>

Advanced:

  • StarterKit.init(...)
  • StarterKit.builder(...)
  • low-level overlays/utilities/extensions

Requirements #

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

License #

MIT

StarterKit #

A batteries-included foundational Flutter package that handles boilerplate infrastructure for any Flutter app. StarterKit provides networking, connectivity monitoring, storage, theming, app updates, and core utilities through a simple, unified API.

Features #

  • 🌐 Networking: Dio-based HTTP client with interceptors, retry logic, and unified error handling
  • 📡 Connectivity Monitoring: Real-time internet connection state monitoring with automatic UI overlays
  • 💾 Storage: Unified storage interface supporting SharedPreferences and Secure Storage
  • 🎨 Theming: Light/Dark mode management with persistence
  • 🔄 App Updates: Force update and maintenance mode detection
  • 🛠️ Utilities: Extensions, validators, and helper functions
  • 🏗️ Architecture: Facade pattern with dependency injection (GetIt)
  • ✅ Error Handling: Sealed Result type ensuring no raw exceptions reach UI layer

Installation #

Add starter_kit_flutter to your pubspec.yaml:

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

Or if published to pub.dev:

dependencies:
  starter_kit_flutter: ^1.0.0

Then run:

flutter pub get

Quick Start #

1. Initialize StarterKit #

In your main.dart, initialize StarterKit before running the app:

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize StarterKit
  await StarterKit.init(
    StarterKitConfig(
      networkConfig: NetworkConfig(
        baseUrl: 'https://api.example.com',
        authTokenKey: 'auth_token', // Optional: for auto token injection
      ),
      storageConfig: const StorageConfig(),
      themeConfig: const ThemeConfig(
        defaultThemeMode: ThemeMode.system,
      ),
      updaterConfig: UpdaterConfig(
        versionCheckUrl: 'https://api.example.com/version',
        enableAutoCheck: true,
      ),
    ),
  );
  
  runApp(const MyApp());
}

2. Wrap Your MaterialApp #

Use StarterKit.builder to enable automatic overlays:

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: StarterKit.navigatorKey,
      builder: (context, child) => StarterKit.builder(
        child: child!,
      ),
      home: const HomePage(),
    );
  }
}

Usage Examples #

Networking #

All network operations return Result<T, AppError> to ensure safe error handling:

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

result.fold(
  (error) {
    // Handle error
    print('Error: ${error.message}');
  },
  (data) {
    // Handle success
    print('User data: $data');
  },
);

// POST request
final createResult = 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 #

Storage operations also return Result<T, AppError>:

// Save data
final saveResult = await StarterKit.I.storage.setString('username', 'john_doe');
saveResult.fold(
  (error) => print('Failed to save: ${error.message}'),
  (_) => print('Saved successfully'),
);

// Read data
final readResult = await StarterKit.I.storage.getString('username');
readResult.fold(
  (error) => print('Failed to read: ${error.message}'),
  (value) => print('Username: $value'),
);

// Other operations
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']);

// Note: Secure storage methods are available through StorageServiceImpl
// For tokens, use the authTokenKey in NetworkConfig for automatic injection

Theming #

Switch between light and dark themes:

// Get theme manager
final themeManager = StarterKit.I.theme;

// Toggle theme
await themeManager.toggleTheme();

// Set specific theme
await themeManager.setThemeMode(ThemeMode.dark);

// Access theme data
final lightTheme = themeManager.lightTheme;
final darkTheme = themeManager.darkTheme;

In your MaterialApp:

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

Connectivity Monitoring #

The connectivity banner appears automatically when connection is lost:

// Listen to connectivity changes
final connectivityService = StarterKit.I.connectivity;

connectivityService.connectionStream.listen((state) {
  switch (state) {
    case NetworkConnectionState.connected:
      print('Connected to internet');
      break;
    case NetworkConnectionState.disconnected:
      print('No internet connection');
      break;
    case NetworkConnectionState.unknown:
      print('Connection state unknown');
      break;
  }
});

// Check current state
final currentState = connectivityService.currentState;

App Updates #

Force update and maintenance mode are handled automatically:

// Check for updates manually
final updaterService = StarterKit.I.updater;
final status = await updaterService.checkForUpdates();

switch (status) {
  case UpdateStatus.upToDate:
    print('App is up to date');
    break;
  case UpdateStatus.updateAvailable:
    print('Update available');
    break;
  case UpdateStatus.forceUpdateRequired:
    print('Force update required');
    break;
  case UpdateStatus.maintenanceMode:
    print('App is in maintenance mode');
    break;
  case UpdateStatus.unknown:
    print('Update status unknown');
    break;
}

// Listen to update status changes
updaterService.updateStatusStream.listen((status) {
  // Handle status changes
});

Utilities #

String Extensions

// Email validation
final email = 'user@example.com';
if (email.isValidEmail) {
  print('Valid email');
}

// Capitalize
final name = 'john doe';
print(name.capitalize()); // "John doe"
print(name.capitalizeWords()); // "John Doe"

// Blank check
final text = '   ';
if (text.isBlank) {
  print('Text is blank');
}

Context Extensions

// Screen dimensions
final width = context.screenWidth;
final height = context.screenHeight;

// Show snackbars
context.showSnackBar('Hello!');
context.showSuccessSnackBar('Success!');
context.showErrorSnackBar('Error occurred!');

// Access theme
final theme = context.theme;
final textTheme = context.textTheme;

Responsive Sizing

// Height percentage
Container(
  height: 20.h(context), // 20% of screen height
  width: 50.w(context),  // 50% of screen width
  child: Text('Responsive'),
)

// Smaller dimension percentage
Container(
  size: 10.sp(context), // 10% of smaller dimension
)

Date Extensions

final date = DateTime.now();

// Readable formats
print(date.toReadable()); // "January 1, 2024"
print(date.toShortReadable()); // "Jan 1, 2024"
print(date.toReadableWithTime()); // "January 1, 2024 at 3:30 PM"

// Checks
if (date.isToday) print('Today');
if (date.isYesterday) print('Yesterday');
if (date.isPast) print('In the past');

Validators

Use validators in your FormFields:

TextFormField(
  decoration: const InputDecoration(labelText: 'Email'),
  validator: emailValidator(),
)

TextFormField(
  decoration: const InputDecoration(labelText: 'Password'),
  validator: combineValidators([
    requiredValidator('Password'),
    minLengthValidator(8, 'Password'),
  ]),
)

TextFormField(
  decoration: const InputDecoration(labelText: 'Phone'),
  validator: phoneValidator(),
)

Global Loader #

Show a global loading overlay:

// In your widget
GlobalLoader(
  isLoading: _isLoading,
  child: YourContent(),
)

Toast Overlay #

Show global toasts:

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

// Or using navigator key
final context = StarterKit.navigatorKey.currentContext;
if (context != null) {
  ToastOverlay.showSuccess(context, 'Operation completed!');
}

Configuration #

Network Configuration #

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

Storage Configuration #

StorageConfig(
  keyPrefix: 'my_app_', // Optional: prefix for all keys
  enableSecureStorage: true,
  enablePreferencesStorage: true,
)

Theme Configuration #

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

Updater Configuration #

UpdaterConfig(
  versionCheckUrl: 'https://api.example.com/version',
  currentVersion: '1.0.0', // Optional: auto-detected from package_info
  checkInterval: 3600, // seconds (1 hour)
  enableAutoCheck: true,
  enableMaintenanceMode: true,
  headers: {
    'Authorization': 'Bearer token',
  },
)

Error Handling #

All operations return Result<T, AppError>. Handle errors safely:

final result = await StarterKit.I.network.get('/data');

result.fold(
  (error) {
    // Handle different error types
    switch (error) {
      case NetworkError():
        print('Network error: ${error.message}');
        print('Status code: ${error.statusCode}');
        break;
      case ConnectionError():
        print('Connection error: ${error.message}');
        break;
      case StorageError():
        print('Storage error: ${error.message}');
        break;
      case ValidationError():
        print('Validation error: ${error.message}');
        break;
      default:
        print('Unknown error: ${error.message}');
    }
  },
  (data) {
    // Handle success
    print('Data: $data');
  },
);

Architecture #

StarterKit follows a strict facade pattern:

  • Single Entry Point: StarterKit.init() and StarterKit.builder
  • Service Access: All services via StarterKit.I.<service>
  • Dependency Injection: Uses GetIt internally
  • Interface-Based: Host app only interacts with interfaces
  • Error Safety: All operations return Result<T, AppError>

Directory Structure #

lib/
├── src/
│   ├── core/
│   │   ├── data/              # Network & Storage implementations
│   │   ├── services/          # Connectivity & Updater services
│   │   ├── design_system/     # Theme & Responsive sizing
│   │   ├── widgets/           # UI Components & Overlays
│   │   ├── models/            # Configs & Response models
│   │   ├── exceptions/        # Result type & Error handling
│   │   ├── utils/             # Extensions & Validators
│   │   └── helpers/           # Service locator & Logger
│   └── facade/
│       └── starter_kit.dart   # Main facade
└── starter_kit_flutter.dart   # Public export

API Reference #

StarterKit #

  • static Future<void> init(StarterKitConfig config) - Initialize the package
  • static Widget builder({required Widget child, ...}) - Builder widget for overlays
  • static GlobalKey<NavigatorState> navigatorKey - Global navigation key
  • static ServiceAccessor I - Service accessor

ServiceAccessor #

  • IStorageService storage - Storage service
  • INetworkClient network - Network client
  • ThemeManager theme - Theme manager
  • IConnectivityService connectivity - Connectivity service
  • IUpdaterService updater - Updater service

Requirements #

  • Flutter SDK: >=3.0.0
  • Dart SDK: >=3.0.0

Dependencies #

  • dio: ^5.4.0 - HTTP client
  • get_it: ^7.6.4 - Dependency injection
  • shared_preferences: ^2.2.2 - Local storage
  • flutter_secure_storage: ^9.0.0 - Secure storage
  • connectivity_plus: ^5.0.2 - Connectivity monitoring
  • package_info_plus: ^5.0.1 - App version info

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Merge Request

License #

This project is licensed under the MIT License.

Support #

For issues, questions, or contributions, please open an issue on GitLab.

Authors #

  • Moinuddin - SDE-III - Quantana India

Made with ❤️ for the Flutter community

4
likes
0
points
227
downloads

Publisher

unverified uploader

Weekly Downloads

A batteries-included foundational Flutter package for networking, connectivity, storage, theming, app updates, and core utilities.

Repository (GitLab)
View/report issues

License

unknown (license)

Dependencies

connectivity_plus, dio, flutter, flutter_secure_storage, get_it, package_info_plus, shared_preferences

More

Packages that depend on starter_kit_flutter