FKernal πŸš€

Pub Version License: MIT Flutter Dart Style: Very Good Analysis

The Configuration-Driven Flutter Kernel β€” Build production-grade Flutter apps in a fraction of the time.

FKernal eliminates boilerplate by providing a centralized orchestration layer for Networking, State Management, Persistence, Error Recovery, and Design Systems. Define your endpoints and theme tokens once β€” FKernal handles everything else automatically.

πŸ’‘ Philosophy: Configuration over Implementation. If something can be declared in a config object, it should be.


πŸ“‘ Table of Contents

πŸ“š Documentation

Deep dive into specific topics:


✨ Features

Feature Description
🌐 Declarative Networking Define endpoints as constants. No more repositories, clients, or interceptors. Supports REST out of the box with extension points for GraphQL/gRPC.
πŸ”„ Universal State Management Pluggable architecture supporting Riverpod (default), BLoC, GetX, MobX, Signals, or custom adapters. Every endpoint gets a unified ResourceState.
πŸ’Ύ Smart Caching TTL-based caching with automatic invalidation on mutations. Supports Stale-While-Revalidate pattern. Backed by Hive for binary storage.
🎨 Theming System Define theme tokens once, apply everywhere with dynamic light/dark switching. Full Material 3 support with automatic persistence.
⚠️ Unified Error Handling All errors normalized into typed FKernalError objects with automatic retry logic, exponential backoff, and environment-aware logging.
πŸ“¦ Local State Slices Manage UI state with Value, Toggle, Counter, List, and Map slices. Includes undo/redo support with history tracking.
πŸ”Œ Extensible Architecture Override network client, storage providers, and add observers. Implement INetworkClient, IStorageProvider, or ISecureStorageProvider for full customization.
πŸ” Observability Built-in KernelObserver and KernelEvent systems for structured runtime monitoring, debugging, and analytics integration.
πŸ”₯ Optional Firebase Separated Firebase module for Firestore/Auth/Storage. Keeps core package light while providing deep integration when needed.
πŸ” Type-Safe Resources Use ResourceKey<T> for compile-time type safety when accessing state. Catch typos at build time.
πŸ”„ Token Refresh & Auth Opt-in 401 token refresh, dynamic token providers, and runtime auth controls.
πŸ“‘ Pagination Support Built-in FKernalPaginatedBuilder for effortless infinite scrolling and list management.
πŸ§ͺ First-Class Testing Comprehensive mocks for networking and storage included.
πŸš€ High Performance Automatic request deduplication and per-widget cancellation to save bandwidth and battery.

πŸ“¦ Installation

Add FKernal to your pubspec.yaml:

dependencies:
  fkernal: ^1.3.0
  flutter_riverpod: ^2.4.9

Then run:

flutter pub get

Requirements

Requirement Minimum Version
Flutter 3.10.0+
Dart 3.0.0+
Platforms iOS, Android, Web, macOS, Windows, Linux

Optional Dependencies

For secure storage on mobile platforms (recommended for auth tokens):

dependencies:
  flutter_secure_storage: ^9.0.0

For Firebase/Firestore integration, use the optional module:

import 'package:fkernal/fkernal_firebase.dart';

Then add these to your pubspec.yaml:

dependencies:
  cloud_firestore: ^5.0.0
  firebase_auth: ^5.0.0
  firebase_storage: ^12.0.0

πŸš€ Quick Start

1. Initialize FKernal

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

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

  await FKernal.init(
    config: const FKernalConfig(
      baseUrl: 'https://api.example.com',
      environment: Environment.development,
      theme: ThemeConfig(
        primaryColor: Color(0xFF6366F1),
        useMaterial3: true,
      ),
    ),
    endpoints: [
      Endpoint(
        id: 'getUsers',
        path: '/users',
        parser: (json) => (json as List)
            .map((u) => User.fromJson(u))
            .toList(),
      ),
      Endpoint(
        id: 'createUser',
        path: '/users',
        method: HttpMethod.post,
        invalidates: ['getUsers'], // Auto-refresh users list
      ),
    ],
  );

  runApp(const MyApp());
}

2. Wrap Your App

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

  @override
  Widget build(BuildContext context) {
    return FKernalApp(
      child: Builder(
        builder: (context) {
          final themeManager = context.themeManager;
          return ListenableBuilder(
            listenable: themeManager,
            builder: (context, _) => MaterialApp(
              theme: themeManager.lightTheme,
              darkTheme: themeManager.darkTheme,
              themeMode: themeManager.themeMode,
              home: const HomeScreen(),
            ),
          );
        },
      ),
    );
  }
}

3. Consume Data

class UsersScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FKernalBuilder<List<User>>(
        resource: 'getUsers',
        builder: (context, users) => ListView.builder(
          itemCount: users.length,
          itemBuilder: (_, i) => ListTile(title: Text(users[i].name)),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.performAction('createUser', 
          payload: {'name': 'New User'}),
        child: Icon(Icons.add),
      ),
    );
  }
}

That's it! No BLoCs, no repositories, no API client setup.


πŸ“– Core Concepts

πŸ“˜ See Architecture Deep Dive for full details.

Configuration

FKernalConfig is the central hub for all framework behavior:

const config = FKernalConfig(
  baseUrl: 'https://api.example.com',
  environment: Environment.production,
  
  features: FeatureFlags(
    enableCache: true,
    enableAutoRetry: true,
    maxRetryAttempts: 3,
    enableLogging: false,
  ),
  
  defaultCacheConfig: CacheConfig(
    duration: Duration(minutes: 5),
  ),
  
  connectTimeout: 30000,
  receiveTimeout: 30000,
);

Endpoints

πŸ“˜ See Networking Deep Dive for full details.

Endpoints are immutable blueprints for your API layer:

Endpoint(
  id: 'getUser',              // Unique identifier
  path: '/users/{id}',        // Path with parameters
  method: HttpMethod.get,     // HTTP method
  cacheConfig: CacheConfig.medium,  // 5-minute TTL
  requiresAuth: true,         // Add auth headers
  invalidates: ['getUsers'],  // Clear on success
  parser: (json) => User.fromJson(json),
  description: 'Fetches a user by ID',
)

Cache Presets:

  • CacheConfig.none - Always fetch fresh
  • CacheConfig.short - 1 minute
  • CacheConfig.medium - 5 minutes
  • CacheConfig.long - 1 hour
  • CacheConfig.persistent - 24 hours

State Management

πŸ“˜ See State Management Deep Dive for full details on adapters and local state.

Every request is tracked as a ResourceState<T>:

// Pattern matching (recommended)
switch (state) {
  ResourceLoading() => CircularProgressIndicator(),
  ResourceData(:final data) => Text(data.name),
  ResourceError(:final error) => Text(error.message),
  _ => SizedBox(),
}

🧩 Widgets Reference

FKernalBuilder

The primary widget for consuming API data:

FKernalBuilder<List<User>>(
  resource: 'getUsers',
  params: {'limit': 10},           // Query parameters
  pathParams: {'orgId': '123'},    // Path parameters
  autoFetch: true,                 // Fetch on mount
  
  builder: (context, users) => ListView(...),
  
  loadingWidget: ShimmerLoading(), // Custom loading
  errorBuilder: (ctx, err, retry) => RetryButton(err, retry),
  emptyWidget: EmptyState(),
  
  onData: (users) => print('Got ${users.length} users'),
  onError: (error) => analytics.log(error),
)

Local State Builders

For non-API state (forms, UI toggles, etc.):

// Complex state with history
FKernalLocalBuilder<CalculatorState>(
  slice: 'calculator',
  create: () => LocalSlice(initialState: CalculatorState()),
  builder: (context, state, update) => Column(
    children: [
      Text(state.display),
      ElevatedButton(
        onPressed: () => update((s) => s.copyWith(display: '0')),
        child: Text('Clear'),
      ),
    ],
  ),
)

// Simple toggle
FKernalToggleBuilder(
  slice: 'darkMode',
  create: () => ToggleSlice(false),
  builder: (context, value, toggle) => Switch(
    value: value,
    onChanged: (_) => toggle.toggle(),
  ),
)

// Counter with bounds
FKernalCounterBuilder(
  slice: 'quantity',
  create: () => CounterSlice(initial: 1, min: 1, max: 99),
  builder: (context, value, counter) => Row(
    children: [
      IconButton(onPressed: counter.decrement, icon: Icon(Icons.remove)),
      Text('$value'),
      IconButton(onPressed: counter.increment, icon: Icon(Icons.add)),
    ],
  ),
)

// List management
FKernalListBuilder<String>(
  slice: 'tags',
  create: () => ListSlice(['flutter', 'dart']),
  builder: (context, items, slice) => Wrap(
    children: [
      ...items.map((tag) => Chip(
        label: Text(tag),
        onDeleted: () => slice.remove(tag),
      )),
      ActionChip(
        label: Text('Add'),
        onPressed: () => slice.add('new-tag'),
      ),
    ],
  ),
)

Built-in UI Widgets

// Loading indicator
AutoLoadingWidget(
  size: 40,
  message: 'Loading users...',
)

// Error with retry
AutoErrorWidget(
  error: FKernalError.network('Connection failed'),
  onRetry: () => context.refreshResource('getUsers'),
  compact: false,
)

// Empty state
AutoEmptyWidget(
  title: 'No Users',
  subtitle: 'Add your first user to get started',
  icon: Icons.people_outline,
  actionText: 'Add User',
  onAction: () => showAddUserDialog(context),
)

πŸ”§ Context Extensions

Access FKernal services from any widget:

// State Management
context.useResource<List<User>>('getUsers');      // Reactive state
context.fetchResource<List<User>>('getUsers');    // Imperative fetch
context.refreshResource<List<User>>('getUsers');  // Force refresh
context.performAction<User>('createUser', payload: user);

// Managers
context.stateManager;    // StateManager instance
context.storageManager;  // StorageManager instance
context.themeManager;    // ThemeManager instance
context.errorHandler;    // ErrorHandler instance

// Theme
// Theme
// Use ListenableBuilder or AnimatedBuilder to listen to changes
context.themeManager;    // ThemeManager instance (ChangeNotifier)
context.themeManager.toggleTheme();  // Switch light/dark

// Local State
context.localState<int>('counter');     // Get value
context.localSlice<int>('counter');     // Get slice
context.updateLocal<int>('counter', (v) => v + 1);

🎨 Theming

πŸ“˜ See Theming Deep Dive for full details.

Define your design system tokens:

const theme = ThemeConfig(
  primaryColor: Color(0xFF6366F1),
  secondaryColor: Color(0xFF8B5CF6),
  
  useMaterial3: true,
  defaultThemeMode: ThemeMode.system,
  
  borderRadius: 12.0,
  defaultPadding: 16.0,
  cardElevation: 2.0,
  
  fontFamily: 'Inter',
);

Toggle theme from anywhere:

IconButton(
  icon: Icon(Icons.dark_mode),
  onPressed: () => context.themeManager.toggleTheme(),
)

πŸ›‘οΈ Error Handling

All errors are normalized to FKernalError:

const error = FKernalError(
  type: FKernalErrorType.network,
  message: 'Connection failed',
  statusCode: null,
  originalError: SocketException('...'),
);

// Error types
FKernalErrorType.network      // Connection issues
FKernalErrorType.server       // 5xx responses
FKernalErrorType.unauthorized // 401
FKernalErrorType.forbidden    // 403
FKernalErrorType.notFound     // 404
FKernalErrorType.validation   // Invalid data
FKernalErrorType.rateLimited  // 429
FKernalErrorType.timeout      // Request timeout
FKernalErrorType.unknown      // Unexpected errors
FKernalErrorType.cancelled    // Request manually cancelled

---

## πŸ—οΈ Models

Implement `FKernalModel` for type-safe API handling:

```dart
class User implements FKernalModel {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'],
    name: json['name'],
    email: json['email'],
  );

  @override
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
  };

  @override
  void validate() {
    if (name.isEmpty) {
      throw FKernalError(
        type: FKernalErrorType.validation,
        message: 'Name is required',
      );
    }
  }
}

πŸ”Œ Extensibility

Custom Network Client

class MyNetworkClient implements INetworkClient {
  @override
  Future<T> request<T>(Endpoint endpoint, {...}) async {
    // Your implementation
  }
}

await FKernal.init(
  config: config.copyWith(
    networkClientOverride: MyNetworkClient(),
  ),
  endpoints: endpoints,
);

Custom Storage Providers

await FKernal.init(
  config: config.copyWith(
    cacheProviderOverride: MyCustomCacheProvider(),
    secureProviderOverride: MySecureStorageProvider(),
  ),
  endpoints: endpoints,
);

Observers

class AnalyticsObserver extends KernelObserver {
  @override
  void onEvent(KernelEvent event) {
    analytics.track(event.name, event.data);
  }
}

await FKernal.init(
  config: config,
  endpoints: endpoints,
  observers: [AnalyticsObserver()],
);

πŸ“‹ Best Practices

1. Centralize Endpoints

Keep all endpoints in a single configuration file for maintainability:

// lib/config/endpoints.dart
final appEndpoints = <Endpoint>[
  // Auth
  Endpoint(id: 'login', path: '/auth/login', method: HttpMethod.post),
  // Users
  Endpoint(id: 'getUsers', path: '/users', ...),
];

2. Use Parsers for Type Safety

Always provide a parser function to ensure type-safe data handling:

parser: (json) => (json as List)
    .map((u) => User.fromJson(Map<String, dynamic>.from(u)))
    .toList(),

3. Leverage Cache Invalidation

See Caching Deep Dive for strategy details. Use invalidates to keep UI consistent without manual refreshes:

Endpoint(
  id: 'createPost',
  invalidates: ['getPosts', 'getUserPosts', 'getPostStats'],
),

4. Use Appropriate Cache TTLs

Match cache duration to data volatility:

Data Type Recommended TTL
User session/auth No cache
Notifications 1 minute
Feed/Timeline 2-5 minutes
User profile 5-15 minutes
Static content 24 hours

5. Handle Empty States

Always provide feedback for empty data:

emptyWidget: const AutoEmptyWidget(
  title: 'No Users',
  subtitle: 'Invite team members to get started',
  icon: Icons.group_add,
),

6. Validate Models Before Mutations

Always validate data before sending to the API to provide immediate feedback.


❓ FAQ

How does FKernal compare to BLoC/Riverpod/Provider?

FKernal is not a replacement for state management libraries β€” it's a higher-level abstraction that handles the common patterns (API calls, caching, loading states) that these libraries require you to implement manually. Typical code reduction is 80-90%.

Can I use FKernal with an existing app?

Yes! FKernal can be adopted incrementally. Initialize alongside your existing setup and migrate one screen at a time.

Does FKernal work with GraphQL?

Yes, by implementing INetworkClient to translate endpoints to GraphQL queries.

How do I handle authentication?

FKernal supports three ways to handle auth:

  1. Static Token:
AuthConfig.bearer('token', onTokenRefresh: () => auth.refresh())
  1. Dynamic Provider (Recommended for Secure Storage):
AuthConfig.dynamic(
  tokenProvider: () => secureStorage.read('access_token'),
  onTokenExpired: () => handleLogout(),
)
  1. Runtime Updates:
FKernal.instance.updateAuthToken(newToken);
FKernal.instance.clearAuthToken();

Does it support request cancellation?

Yes! Every widget can cancel its own requests, or you can do it manually:

FKernal.instance.cancelEndpoint('getUsers');

Is FKernal production-ready?

Yes! FKernal includes comprehensive error handling, automatic retry with exponential backoff, memory-efficient state management, automatic request deduplication, and built-in observability.


πŸ”„ Migration Guide

From BLoC Pattern

Before: 80+ lines (Bloc class + Repository + Screen) After: 10-15 lines (Endpoint config + FKernalBuilder)

From Riverpod

Before: FutureProvider + Consumer with .when() handling After: Endpoint config + FKernalBuilder with automatic state handling

See the full migration guide for detailed examples.


Customization

FKernal is designed to be 100% customizable, from UI components to networking logic.

Global UI Configuration

Define default builders for loading, error, and empty states in FKernalConfig:

FKernal.init(
  config: FKernalConfig(
    baseUrl: '...',
    globalUIConfig: GlobalUIConfig(
      loadingBuilder: (context) => MyCustomSpinner(),
      errorBuilder: (context, error, retry) => MyCustomError(error),
      emptyBuilder: (context) => MyCustomEmptyState(),
    ),
  ),
  endpoints: [...],
);

Custom Network Interceptors

Add your own Dio interceptors without overriding the entire network client:

FKernalConfig(
  interceptors: [
    MyCustomLoggingInterceptor(),
    AnalyticsInterceptor(),
  ],
)

Advanced Riverpod Integration

Override the ProviderContainer to provide custom service mocks or scoped providers:

FKernalConfig(
  providerContainerOverride: myCustomContainer,
)

Universal State Management

FKernal is designed to be state-management agnostic. While it uses Riverpod internally by default for maximum performance, you can configure it to use your preferred solution for both global resources and local state.

Configuration

Configure the internal engine in FKernalConfig:

FKernalConfig(
  // Choose your global state manager
  stateManagement: StateManagementType.bloc,
  
  // Provide the adapter implementation (required if not using defaults)
  stateAdapter: MyBlocAdapter(),
  
  // Configure local state factory (e.g., to use Signals for local slices)
  localStateFactory: <T>(initial) => SignalsLocalState<T>(initial),
);

Adapters

To integrate a custom solution, implement ResourceStateAdapter for global resources or LocalStateAdapter for local state.

Bridges

FKernal also provides drop-in bridges for popular packages if you prefer to use them alongside the default engine:

BLoC / Cubit

import 'package:fkernal/fkernal_bloc.dart';

class UserCubit extends ResourceCubit<User> {
  UserCubit() : super('getUsers');
}

Signals

import 'package:fkernal/fkernal_signals.dart';

final userSignal = ResourceSignal<User>('getUsers');

GetX

import 'package:fkernal/fkernal_getx.dart';

class UserController extends ResourceController<User> {
  UserController() : super('getUsers');
}

MobX

import 'package:fkernal/fkernal_mobx.dart';

final userStore = ResourceStore<User>('getUsers');

πŸ“„ License

MIT License - see LICENSE for details.


🀝 Contributing

Contributions are welcome! Please read our Contributing Guide for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Design principles

πŸ”— Resources


Built with ❀️ by the FKernal Team

Libraries

fkernal
FKernal - Configuration-driven Flutter framework
fkernal_bloc
fkernal_firebase
FKernal Firebase Integration
fkernal_getx
fkernal_mobx
fkernal_signals
testing
FKernal Testing Utilities