FKernal π
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
- Features
- Installation
- Quick Start
- Core Concepts
- Widgets Reference
- Context Extensions
- Theming
- Error Handling
- Models
- Caching Strategy
- Extensibility
- Advanced Patterns
- Best Practices
- FAQ
- Migration Guide
- License
- Contributing
π Documentation
Deep dive into specific topics:
- ποΈ Architecture & Configuration
- π Networking
- π State Management
- πΎ Caching Strategy
- π¨ Theming
- π Migration Guide
β¨ 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 freshCacheConfig.short- 1 minuteCacheConfig.medium- 5 minutesCacheConfig.long- 1 hourCacheConfig.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
invalidatesto 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:
- Static Token:
AuthConfig.bearer('token', onTokenRefresh: () => auth.refresh())
- Dynamic Provider (Recommended for Secure Storage):
AuthConfig.dynamic(
tokenProvider: () => secureStorage.read('access_token'),
onTokenExpired: () => handleLogout(),
)
- 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