fl_core_kit
A Flutter toolkit providing theme management, locale switching, extension methods, state mixins, system utilities, async timing utilities, and common widgets.
Features
- π¨ Theme system β Light/dark theme switching with system-follow mode, customizable theme data base class
- π Locale management β Runtime language switching with inherited widget propagation
- π§© Extension methods β BuildContext, GlobalKey, List, Map, ScrollNotification, String
- π§ State mixins β Safe setState, performance tracing via Timeline
- β± Async timing β Measure Future execution time with
TimeMeasureResult - π± Common widgets β Decimal input formatter, keyboard avoidance, bottom navigation, layout change notifier, and more
Installation
dependencies:
fl_core_kit:
git:
url: https://github.com/LqDeveloper/fl_core_kit.git
Or use the local path:
dependencies:
fl_core_kit:
path: ../fl_core_kit
Import:
import 'package:fl_core_kit/fl_core_kit.dart';
Quick Start
Widget nesting order (outer to inner): FlLocaleWidget β FlThemeWidget β MaterialApp.
import 'package:flutter/material.dart';
import 'package:fl_core_kit/fl_core_kit.dart';
// 1. Define custom theme data (extend FlBaseThemeData)
class AppTheme extends FlBaseThemeData {
final Color primary;
final Color background;
const AppTheme({required this.primary, required this.background});
factory AppTheme.light() => const AppTheme(
primary: Colors.blue,
background: Colors.white,
);
@override
Color? get primaryColor => primary;
@override
Color? get scaffoldBgColor => background;
@override
ColorScheme? get colorScheme =>
ColorScheme.fromSeed(seedColor: primary);
}
// 2. App entry
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return FlLocaleWidget(
initLocale: const Locale('en', 'US'),
builder: (context, locale) {
return FlThemeWidget<AppTheme>(
initialModel: FlThemeMode.system,
lightTheme: AppTheme.light(),
darkTheme: AppTheme.dark(),
builder: (context, themeData) {
return MaterialApp(
title: 'My App',
theme: themeData.themeData,
locale: locale,
home: const HomePage(),
);
},
);
},
);
}
}
// 3. Use in pages
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final theme = context.wt<AppTheme>();
final mode = context.themeMode;
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
children: [
Text('Primary: ${theme.primary}'),
Text('Mode: $mode'),
ElevatedButton(
onPressed: () => context.currentLocale = const Locale('zh'),
child: const Text('Switch to Chinese'),
),
ElevatedButton(
onPressed: () => context.setupTheme(FlThemeMode.dark),
child: const Text('Dark Mode'),
),
],
),
),
);
}
}
Modules
1. Theme System
1.1 FlBaseThemeData β Theme Data Base Class
Abstract base class for custom themes. Override getters to define component styles.
class MyTheme extends FlBaseThemeData {
final Color primary;
final Color background;
const MyTheme({required this.primary, required this.background});
factory MyTheme.light() => const MyTheme(
primary: Colors.blue,
background: Colors.white,
);
@override
Color? get primaryColor => primary;
@override
Color? get scaffoldBgColor => background;
@override
ColorScheme? get colorScheme =>
ColorScheme.fromSeed(seedColor: primary);
@override
Color? get splashColor => Colors.transparent;
@override
Color? get highlightColor => Colors.transparent;
@override
InteractiveInkFeatureFactory? get splashFactory => NoSplash.splashFactory;
@override
AppBarTheme? get appBarTheme => null;
@override
TabBarThemeData? get tabBarTheme => null;
@override
BottomNavigationBarThemeData? get bottomNavigationBarTheme => null;
@override
BottomSheetThemeData? get bottomSheetTheme => null;
@override
DialogThemeData? get dialogTheme => null;
@override
SystemUiOverlayStyle? get systemOverlay => null;
@override
String? get fontFamily => null;
@override
String? get package => null;
@override
PageTransitionsTheme? get pageTransitionsTheme => null;
/// All getters are automatically assembled into ThemeData.
@override
ThemeData get themeData => super.themeData;
}
| Getter | Type | Description |
|---|---|---|
primaryColor |
Color? |
Primary color |
colorScheme |
ColorScheme? |
Color scheme |
scaffoldBgColor |
Color? |
Scaffold background color |
splashColor |
Color? |
Splash/ripple color (default transparent) |
highlightColor |
Color? |
Highlight color (default transparent) |
splashFactory |
InteractiveInkFeatureFactory? |
Splash factory (default NoSplash) |
fontFamily |
String? |
Global font family |
systemOverlay |
SystemUiOverlayStyle? |
Status bar / navigation bar style |
appBarTheme |
AppBarTheme? |
AppBar theme |
tabBarTheme |
TabBarThemeData? |
TabBar theme |
bottomNavigationBarTheme |
BottomNavigationBarThemeData? |
Bottom navigation bar theme |
bottomSheetTheme |
BottomSheetThemeData? |
BottomSheet theme |
dialogTheme |
DialogThemeData? |
Dialog theme |
package |
String? |
Resource package name |
pageTransitionsTheme |
PageTransitionsTheme? |
Page transition animation |
themeData |
ThemeData |
Assembly into Flutter native ThemeData |
Call updateSystemUiOverlay() to apply the status bar style automatically.
1.2 FlThemeMode β Theme Mode Enum
enum FlThemeMode { light, dark, system }
// Properties
mode.isLight; // bool
mode.isDark; // bool
mode.isSystem; // bool
mode.val; // Display name
// Static parse
FlThemeMode.fromVal('Light'); // FlThemeMode.light
1.3 FlThemeWidget<T> β Theme Widget
| Parameter | Type | Description |
|---|---|---|
initialModel |
FlThemeMode |
Initial theme mode |
lightTheme |
T |
Light theme data |
darkTheme |
T |
Dark theme data |
builder |
FlThemeBuilder<T> |
Builder callback (context, themeData) => Widget |
Static methods:
// Get State
FlThemeWidget.of<T>(context); // FlThemeState<T>, asserts if not found
FlThemeWidget.maybeOf<T>(context); // FlThemeState<T>?, safe
// Set theme mode
FlThemeWidget.setupMode(context, FlThemeMode.dark);
// Read theme data
FlThemeWidget.watch<T>(context); // Reactive (widget rebuilds)
FlThemeWidget.read<T>(context); // One-time read (no rebuild)
// Force refresh
FlThemeWidget.reloadTheme(context);
1.4 ThemeContextExtension β Theme Context Extension
Must be used within FlThemeWidget subtree.
context.setupTheme(FlThemeMode.light); // Set mode
FlThemeMode mode = context.themeMode; // Read mode
// Reactive read (widget rebuilds on change)
final theme = context.wt<AppTheme>();
// One-time read (no rebuild)
final theme = context.rt<AppTheme>();
context.reloadTheme(); // Force refresh
2. Locale
2.1 FlLocaleWidget β Locale Widget
FlLocaleWidget(
initLocale: Locale('en', 'US'),
builder: (context, locale) {
return MaterialApp(
locale: locale,
localizationsDelegates: [...],
supportedLocales: [...],
home: const MyApp(),
);
},
)
Static methods:
FlLocaleWidget.of(context); // FlLocaleWidgetState
FlLocaleWidget.maybeOf(context); // FlLocaleWidgetState?
FlLocaleWidget.setupLocal(context, Locale('en'));
FlLocaleWidget.watch(context); // Locale? (reactive)
FlLocaleWidget.read(context); // Locale? (one-time)
2.2 LocaleContextExtension β Locale Context Extension
context.currentLocale = Locale('zh'); // Set locale
Locale? locale = context.wLocal; // Reactive read
Locale? locale = context.rLocal; // One-time read
3. BuildContext Extensions
Available directly on any BuildContext.
// Theme shortcut
context.themeData; // ThemeData
// Screen metrics
context.screenSize; // Size
context.width; // double
context.height; // double
context.orientation; // Orientation
context.isLandscape; // bool
context.isPortrait; // bool
// Widget geometry
context.renderBox; // RenderBox?
context.size; // Size?
context.localToGlobal( // Offset?
point: Offset.zero,
ancestor: someRenderObject,
);
// Safe area & system UI
context.flutterView; // FlutterView?
context.navigationBarHeight; // double β AppBar height + status bar
context.windowPadding; // EdgeInsets β keyboard & system UI insets
context.topSafeHeight; // double β status bar height
context.bottomSafeHeight; // double β home indicator area
// Route info
context.routeName; // String?
context.arguments; // Object?
4. GlobalKey Extensions
final key = GlobalKey();
key.renderBox; // RenderBox?
key.location; // Offset? β screen position (top-left)
key.size; // Size?
5. List Extensions
// Sorting
[3, 1, 2].sortSelf(SortOrder.asc); // [1, 2, 3] in-place
[3, 1, 2].sortSelf(SortOrder.desc); // [3, 2, 1] in-place
list.sortBy((e) => e.toString().length); // Sort by derived key (in-place)
list.sortedCopyBy((e) => e.toString().length); // Sorted copy (original unchanged)
// Serialization
[1, 2, 3].toJsonString; // '[1,2,3]'
[1, 2, 3].toJsonPretty; // Pretty-printed JSON
// Safe access
[1, 2, 3].safeGet(5, defVal: 0); // 0 (out of bounds)
[].safeRemoveLast(); // null (safe removal)
// Transformation & immutability
[1, 2, 3].mapList((e) => e.toString()); // ['1', '2', '3']
[1, 2, 3].unmodifiable; // Unmodifiable view
[1, 2, 3].copyList(); // Shallow copy
6. Map Extensions
// Serialization
{'a': 1}.toJsonString; // '{"a":1}'
{'a': 1}.toJsonPretty; // Pretty-printed JSON
// Safe access
{'a': 1}.safeGet('b', defVal: 0); // 0 (key not found)
// Unmodifiable view
{'a': 1}.unmodifiable;
// Key/value lists
{'a': 1}.keyList; // ['a']
{'a': 1}.valueList; // [1]
// Filtering
{'a': 1, 'bb': 2}.whereKey((k) => k.length > 1); // {'bb': 2}
{'a': 1, 'b': 2}.whereValue((v) => v > 1); // {'b': 2}
{'a': 1, 'b': null}.removeNull; // {'a': 1}
7. ScrollNotification Extensions
Use within NotificationListener<ScrollNotification>.
notification.minScrollExtent; // double
notification.maxScrollExtent; // double
notification.pixels; // double
notification.viewportDimension; // double
notification.fullScrollExtent; // double
notification.axis; // Axis
notification.outOfRange; // bool
notification.atEdge; // bool
notification.canScroll; // bool
notification.extentBefore; // double
notification.extentInside; // double
notification.extentAfter; // double
notification.desc; // String β formatted debug output
8. String Extensions
// Blank checks
''.isBlank; // true
' '.isBlank; // true
'a'.isNotBlank; // true
// Validation
'user@example.com'.isEmail; // true
'123'.isNumeric; // true
'https://example.com'.isUrl; // true
// Safe parsing
'42'.toInt(); // 42
'abc'.toInt(defaultValue: -1); // -1
'3.14'.toDouble(); // 3.14
// Text transformation
'hello'.capitalize; // 'Hello'
// Null-safe checks (on String?)
String? s;
s.isNullOrEmpty; // true
s.isNotNullOrEmpty; // false
' '.isNullOrEmpty; // false (whitespace is not empty)
9. State Mixins
All mixins are used via with in a State subclass.
9.1 StateSafeUpdateMixin β Safe setState
Call setState during the build phase without throwing. Automatically defers to post-frame callback.
class _MyPageState extends State<MyPage>
with StateSafeUpdateMixin<MyPage> {
void _onScroll() {
safeSetState(() {
_offset = newOffset;
});
}
}
9.2 TimelineMixin β Performance Tracing
Timeline tracing for debugging and profiling. Only works in Debug/Profile mode; no overhead in Release mode.
class MyService with TimelineMixin {
void fetchData() {
// Sync tracing
startSyncTimeline('fetchData');
// ... sync operation
finishSyncTimeline();
// Sync with automatic timing
final result = timeSyncTimeline('compute', function: () {
return heavyComputation();
});
// Async tracing
timeAsyncTimeline('networkRequest', function: () async {
return await http.get(url);
});
}
}
| Method | Description |
|---|---|
startSyncTimeline(name, ...) |
Start sync tracing |
finishSyncTimeline() |
Finish sync tracing |
timeSyncTimeline<R>(name, function: ...) |
Sync operation + auto trace |
startAsyncTimeline(name, ...) |
Start async tracing, returns TimelineTask? |
finishAsyncTimeline(task, ...) |
Finish async tracing |
timeAsyncTimeline<R>(name, function: ...) |
Async operation + auto trace |
10. SystemUtils
// Image cache
SystemUtils.clearImageCache();
// Keyboard control
SystemUtils.hideKeyboard();
SystemUtils.showKeyboard();
SystemUtils.dismissKeyboard(); // Via focus management
SystemUtils.clearClientKeyboard();
// Clipboard
await SystemUtils.copyToClipboardWithCallback('text', (result) => ...);
await SystemUtils.getClipboardData((result) => ...);
// Screen orientation
await SystemUtils.setOrientations([DeviceOrientation.portraitUp]);
// Text measurement
Size textSize = SystemUtils.calTextSize(
context,
text: 'Hello',
style: TextStyle(fontSize: 16),
);
11. TimelineUtils
Low-level API wrapping dart:developer.Timeline. Usually used via TimelineMixin.
// Sync tracing
TimelineUtils.startSync('eventName', tag: 'MyTag');
// ... operation
TimelineUtils.stopSync();
// Sync with automatic recording (returns result)
final result = TimelineUtils.timeSync<int>('compute', function: () => 42);
// Async tracing (auto start/stop)
final data = await TimelineUtils.timeAsync('fetch', function: () => api.get());
12. FutureMeasureUtils β Async Timing
Measure the execution time of any async operation.
// Using extension method (preferred)
final measurement = await () => fetchData().measure();
print('Took ${measurement.duration}');
// Using static method
final result = await FutureMeasureUtils.measure(() => fetchData());
// With result extraction
final data = measurement.getOrThrow(); // Throws if failed
final safe = measurement.getOrDefault( // Fallback on failure
(error, stack) => fallbackData,
);
// Transform result
final parsed = measurement.map((raw) => Model.fromJson(raw));
TimeMeasureResult<T> properties:
| Member | Type | Description |
|---|---|---|
result |
T? |
Return value on success |
duration |
Duration |
Elapsed time |
error |
Object? |
Captured error on failure |
isSuccess |
bool |
Whether it completed successfully |
getOrThrow() |
T |
Returns result or throws captured error |
map<R>() |
TimeMeasureResult<R> |
Transforms result on success |
getOrDefault() |
T |
Returns result or handler's fallback |
13. Widgets
13.1 DecimalInputFormatter
Restricts TextField to valid decimal numbers with configurable precision.
TextField(
inputFormatters: [
DecimalInputFormatter(decimalDigits: 2),
],
keyboardType: TextInputType.number,
)
Features:
- Only allows digits
0-9and. - Prevents multiple decimal points
- Auto-prefix zero for leading dot (
.5β0.5) - Truncates excess decimal digits
13.2 KeyboardPadding
Automatically adds bottom padding equal to the keyboard height when the keyboard appears.
Scaffold(
body: KeyboardPadding(
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
child: Column(
children: [
Expanded(child: content),
TextField(),
],
),
),
)
13.3 NoIndicatorBehavior
Removes overscroll glow/stretch indicators from scrollable views.
ScrollConfiguration(
behavior: NoIndicatorBehavior(),
child: ListView(children: [...]),
)
13.4 ScrollNotificationListener
Splits ScrollNotification into three separate callbacks.
ScrollNotificationListener(
onStart: (notification) => print('Scroll started'),
onUpdate: (notification) => print('Scrolling: ${notification.pixels}'),
onEnd: (notification) => print('Scroll ended'),
child: ListView.builder(itemCount: 100, itemBuilder: ...),
)
13.5 StatusBarColorView
Controls the status bar icon brightness for a subtree via AnnotatedRegion.
StatusBarColorView(
brightness: Brightness.dark, // Dark icons for light backgrounds
child: Scaffold(...),
)
13.6 BottomNavBar β Bottom Navigation Container
Combines PageView (non-scrollable) with BottomNavigationBar.
BottomNavBar(
pages: [HomePage(), SearchPage(), ProfilePage()],
itemLabels: ['Home', 'Search', 'Profile'],
itemBuilder: (context, index, isSelected) {
final icons = [Icons.home, Icons.search, Icons.person];
return Icon(icons[index],
color: isSelected ? Colors.blue : Colors.grey);
},
controller: myController,
onPageChanged: (event) => print('${event.from} β ${event.to}'),
transitionGuard: (from, to) async {
if (to == 2 && !isLoggedIn) {
await showLoginDialog();
return isLoggedIn;
}
return true;
},
)
Parameters:
| Parameter | Type | Description |
|---|---|---|
pages |
List<Widget> |
Page widgets |
itemLabels |
List<String> |
Bottom navigation labels |
itemBuilder |
NavigationItemBuilder |
Icon builder (context, index, isSelected) => Widget |
controller |
NavigationController? |
Programmatic controller |
initialIndex |
int |
Initial page index (default 0) |
onPageChanged |
PageSwitchCallback? |
Page switch callback |
themeData |
BottomNavigationBarThemeData? |
Theme override |
transitionGuard |
TransitionGuard? |
Switch guard, return false to block |
pagesanditemLabelsmust have the same length.
Programmatic control:
final controller = NavigationController();
controller.jumpTo(1);
int current = controller.currentIndex;
controller.dispose(); // Clean up when no longer needed
Global singleton (for single BottomNavBar apps):
final controller = DefaultNavController();
controller.jumpTo(2);
13.7 SizeInfoNotifier β Layout Change Notifier
Monitors layout constraint/size changes via a custom RenderObject.
SizeInfoNotifier(
index: 3,
layoutChanged: (constraints, size) {
print('New constraints: $constraints, size: $size');
},
child: Container(width: 100, height: 200, color: Colors.blue),
)
Bubble notification (parent listens):
NotificationListener<LayoutInfoNotification>(
onNotification: (notification) {
print('index:${notification.index}, size:${notification.size}');
return false; // Don't block bubbling
},
child: SizeInfoNotifier(index: 0, child: yourWidget),
)
API Reference
| Category | Core Types |
|---|---|
| Theme | FlBaseThemeData, FlThemeMode, FlThemeWidget<T>, ThemeContextExtension |
| Locale | FlLocaleWidget, FlLocaleWidgetState, LocaleContextExtension |
| Extensions | ThemeExtensions (Context), CustomRouterExtension (Context), GlobalKeyExtension, ListSortExtensions, ListExtensions, MapExtensions, ScrollNotificationExtension, StringExtension, NullableStringExtension |
| Mixins | StateSafeUpdateMixin, TimelineMixin |
| Utilities | SystemUtils, TimelineUtils, FutureMeasureUtils, TimeMeasureResult<T> |
| Widgets | DecimalInputFormatter, KeyboardPadding, NoIndicatorBehavior, ScrollNotificationListener, StatusBarColorView, BottomNavBar, SizeInfoNotifier, NavigationController, DefaultNavController |
Enums
| Enum | Values | Description |
|---|---|---|
SortOrder |
asc, desc |
List sort direction |
FlThemeMode |
light, dark, system |
Theme mode |
Typedefs
| Alias | Signature | Module |
|---|---|---|
LocaleBuilder |
Widget Function(BuildContext, Locale?) |
Locale |
FlThemeBuilder<T> |
Widget Function(BuildContext, T) |
Theme |
CopyCallback |
void Function({required bool result}) |
SystemUtils |
CopyDataCallback |
void Function({required String? result}) |
SystemUtils |
NavigationItemBuilder |
Widget Function(BuildContext, int, bool) |
BottomNavBar |
PageSwitchCallback |
void Function(PageSwitchEvent) |
BottomNavBar |
TransitionGuard |
FutureOr<bool> Function(int, int) |
BottomNavBar |
OnStart |
void Function(ScrollStartNotification) |
Widget |
OnUpdate |
void Function(ScrollUpdateNotification) |
Widget |
OnEnd |
void Function(ScrollEndNotification) |
Widget |
LayoutChangedCallback |
void Function(BoxConstraints, Size) |
SizeInfoNotifier |
License
MIT