fkernal 1.3.0
fkernal: ^1.3.0 copied to clipboard
A configuration-driven Flutter framework that handles networking, state management, storage, error handling, and theming automatically.
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