core_kit 1.0.6+3
core_kit: ^1.0.6+3 copied to clipboard
A Flutter package bundling production-ready UI widgets, responsive layout helpers, Dio-based networking with token refresh, secure storage, and authentication modules.
CoreKit #
Tip
🚀 Get Started Instantly with the Pre-configured Template!
Skip the boilerplate setup! Start building immediately by cloning the official template repository: Cubit Template (GitHub)
This template comes ready-to-go, fully pre-configured with:
- AutoRoute (declarative, type-safe navigation with code generation)
- Cubit/Bloc (scalable, clean state management)
- CoreKit (production-ready UI components, responsive layout helpers, and authentication)
Setup steps:
# 1. Clone the template
git clone https://github.com/kmmuzahid/template_cubit.git
cd template_cubit
# 2. Install dependencies
fvm flutter pub get
# 3. Generate AutoRoute navigation code (required — do this once, and again after adding new routes)
fvm dart run build_runner build
# 4. Run the app
fvm flutter run
⚠️ Step 3 is required. The template uses AutoRoute for navigation, which relies on code generation. Without running
build_runner build, the app will not compile.Re-run
build_runner buildwhenever you add or modify route definitions. Usebuild_runner watchduring active development to auto-regenerate on file save:fvm dart run build_runner watch
CoreKit (core_kit) is a Flutter package that bundles production-oriented UI widgets, responsive layout helpers, Dio-based networking with token refresh, secure storage, and an optional authentication module. Import everything from a single entry point:
import 'package:core_kit/core_kit.dart';
Public APIs use the Ck prefix (for example CkButton, CkText, CkTransport).
Table of Contents #
- Features
- Installation
- Quick Start
- Configuration
- Splash Screen Routing
- Navigation & Global Access
- UI Components
- Forms & Validation
- Dialogs & Overlays
- Responsive Utilities
- Transport (HTTP)
- Storage
- Authentication Module
- Location Pickers
- Utilities & Extensions
- License
Features #
| Area | Highlights |
|---|---|
| Bootstrap | CoreKit / CoreKit.router / CoreKit.builder — initializes Dio, screen scaling, optional auth, 3-second splash delay with custom onInit |
| UI | Buttons, text fields, dropdowns, images, lists/grids with pagination, tabs, rating, app bar |
| Layout | .w, .h, .sp, .r scaling from a design size (default 428×926) |
| Transport | CkTransport + CkResponse, automatic token refresh, retries |
| Storage | CkStorage — secure storage with SharedPreferences fallback |
| Auth | CkAuthService, OTP flows, social login hooks, profile caching, declarative auto-navigation, splash screen routing |
Design Guidelines: Flutter Native Experience with CK #
CoreKit UI components are built to act exactly like standard Flutter widgets, but with built-in styling, validation, and optimizations to save you time. Simply use the Ck prefix (e.g. CkText, CkButton, CkTextField, CkImage).
⚠️ CRITICAL RULE: Responsive Scaling (.w, .h, .sp, .r) #
All Ck widgets handle their own internal scaling automatically. Never pass scaled values (like 16.w or 14.sp) into Ck widget parameters.
Only use .w, .h, .sp, and .r when configuring Flutter's native layout widgets (like SizedBox, Padding, Container, or TextStyle).
| Do ✅ | Don't ❌ |
|---|---|
CkText(text: 'Hello', fontSize: 16) |
CkText(text: 'Hello', fontSize: 16.sp) |
CkImage(src: '...', height: 120) |
CkImage(src: '...', height: 120.h) |
SizedBox(height: 20.h) |
SizedBox(height: 20) |
Padding(padding: EdgeInsets.all(16.w)) |
Padding(padding: EdgeInsets.all(16)) |
Installation #
1. From pub.dev (Recommended) #
Add core_kit to your pubspec.yaml dependencies:
dependencies:
core_kit: ^1.0.1
Or run:
flutter pub add core_kit
2. Direct from GitHub (Alternative) #
If you need the latest unreleased changes, add the Git dependency:
dependencies:
core_kit:
git:
url: https://github.com/kmmuzahid/flutter_core_kit.git
ref: 1.0.1 # Replace with the latest tag or branch
Run flutter pub get to download.
Requirements: Dart SDK ^3.12.0, Flutter SDK compatible with your app.
Quick Start #
CoreKit requires a GlobalKey<NavigatorState> and a CoreKitConfig. It wraps MaterialApp, runs initialization (Dio, screen utils, optional auth), enforces a 3-second splash delay for branding, and shows preInitChild until ready.
Minimal app (Navigator 1.0) #
import 'package:core_kit/core_kit.dart';
import 'package:flutter/material.dart';
final _navigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(
CoreKit(
navigatorKey: _navigatorKey,
config: CkConfigImpl(),
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomeScreen(),
),
);
}
/// Minimal config — only required overrides. See [Configuration](#configuration) for every option.
class CkConfigImpl extends CoreKitConfig with CoreKitConfigDefaults {
@override
String get imageBaseUrl => 'https://cdn.example.com/';
@override
CkTransportConfig get ckTransportConfig => CkTransportConfig(
baseUrl: 'https://api.example.com',
refreshTokenEndpoint: '/auth/refresh',
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CkAppBar(title: 'Home'),
body: Center(
child: CkButton(
titleText: 'Hello',
onTap: () => CkSnackBar(
'CoreKit is ready',
type: CkSnackBarType.success,
),
),
),
);
}
}
GoRouter / declarative routing #
void main() {
final router = GoRouter(
navigatorKey: _navigatorKey,
routes: [/* ... */],
);
runApp(
CoreKit.router(
navigatorKey: _navigatorKey,
config: CkConfigImpl(),
routerConfig: router,
title: 'My App',
theme: ThemeData(useMaterial3: true),
),
);
}
Custom MaterialApp via builder #
CoreKit.builder(
navigatorKey: _navigatorKey,
config: CkConfigImpl(),
app: (buildChild) => MaterialApp(
navigatorKey: _navigatorKey,
builder: buildChild,
home: const HomeScreen(),
),
);
Configuration #
Implement CoreKitConfig and mix in CoreKitConfigDefaults for sensible defaults on optional members. Pass your implementation to CoreKit as config: CkConfigImpl().
CoreKitConfig members #
| Override | Required | Default (with CoreKitConfigDefaults) |
Purpose |
|---|---|---|---|
imageBaseUrl |
Yes | — | Base URL for CkImage relative paths |
ckTransportConfig |
Yes | — | API base URL, timeouts, token refresh |
designSize |
No | Size(428, 926) |
Responsive scaling reference |
authConfig |
No | null |
Enables CkAuthService — tokens are handled internally (see Authentication) |
appbarConfig |
No | null |
Global CkAppBar back button & actions |
listLoaderConfig |
No | null |
Default pagination loader / end-of-list UI |
inputConfig |
No | null |
Global design defaults for all CkTextField and CkMultilineTextField (border, colors, text style, capitalization) |
snackBarConfig |
No | null |
Global style defaults for CkSnackBar (border radius, colors, text style, padding, margin, icons) |
permissionHelperConfig |
No | null |
Permission dialog copy |
permissionHandlerColors |
No | null |
Permission dialog colors |
passwordObscureIcon |
No | null |
Show/hide icons on password fields |
preInitChild |
No | Container() |
Splash while transport/auth initializes |
onInit |
No | null |
Custom async initialization tasks run during splash delay (e.g., register dependencies) |
Full reference: CkConfigImpl #
Copy this class into your app and adjust values. Every override is listed so you can see what CoreKit reads at startup.
class CkConfigImpl extends CoreKitConfig with CoreKitConfigDefaults {
// ─── Required ───────────────────────────────────────────────
@override
String get imageBaseUrl => 'https://cdn.example.com/images/';
@override
CkTransportConfig get ckTransportConfig => CkTransportConfig(
baseUrl: 'https://api.example.com',
refreshTokenEndpoint: '/auth/refresh',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
enableDebugLogs: true,
onLogout: () {
// Called when refresh token fails — navigate to login, clear session
},
);
// ─── Optional (override mixin defaults as needed) ───────────
@override
Size get designSize => const Size(428, 926);
/// Set to a [CkAuthConfig] to enable CkAuthService (tokens managed automatically).
@override
CkAuthConfig? get authConfig => null;
@override
CkAppBarConfig? get appbarConfig => CkAppBarConfig(
onBack: () => coreKitInstance.navigatorKey.currentState?.pop(),
);
@override
CkListLoaderConfig? get listLoaderConfig => const CkListLoaderConfig(
loaderWidget: Padding(
padding: EdgeInsets.all(20),
child: Center(child: CircularProgressIndicator()),
),
noMoreDataWidget: Padding(
padding: EdgeInsets.all(20),
child: Center(child: Text('No more data')),
),
);
/// Global design defaults shared by all CkTextField / CkMultilineTextField.
/// Per-widget values always take priority.
@override
CkInputConfig? get inputConfig => const CkInputConfig(
borderRadius: 12,
borderWidth: 1.2,
// borderColor: Colors.grey,
// backgroundColor: Colors.white,
// hintStyle: TextStyle(...),
// textStyle: TextStyle(...),
// fontSize: 16,
// textAlign: TextAlign.left,
// enableCapitalization: true,
);
/// Global style overrides for CkSnackBar.
@override
CkSnackBarConfig? get snackBarConfig => const CkSnackBarConfig(
// position: CkSnackBarPosition.top,
// borderRadius: 12,
// backgroundColor: Colors.white,
// margin: EdgeInsets.all(16),
// padding: EdgeInsets.symmetric(horizontal: 5, vertical: 10),
// textStyle: TextStyle(...),
// borderWidthLeft: 10,
// borderWidthOthers: 1,
// iconSize: 24,
// successColor: Colors.green,
// errorColor: Colors.red,
// warningColor: Colors.orange,
// infoColor: Colors.blue,
);
@override
CkPermissionHelperConfig? get permissionHelperConfig =>
const CkPermissionHelperConfig(
permissionDenied: 'Permission denied',
openSettings: 'Open Settings',
);
@override
PermissionHadlerColors? get permissionHandlerColors => PermissionHadlerColors(
errorColor: Colors.red,
actionColor: Colors.green,
normalColor: Colors.black,
);
@override
PasswordObscureIcon? get passwordObscureIcon => PasswordObscureIcon(
show: const Icon(Icons.visibility),
hide: const Icon(Icons.visibility_off),
);
@override
Widget? get preInitChild => const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
/// Custom initialization tasks run during the 3-second splash delay.
/// Use this to register dependencies, initialize analytics, etc.
@override
Future<void> Function()? get onInit => () async {
// Register your app's dependencies
await MyDependencyRegistry.init();
// Initialize analytics or other services
await Analytics.initialize();
};
}
Usage
CoreKit(
navigatorKey: _navigatorKey,
config: CkConfigImpl(),
home: const HomeScreen(),
);
Context rule:
contextinsideCoreKitConfigis only valid in getters that run after the first frame (appbarConfig,permissionHelperConfig,passwordObscureIcon,permissionHandlerColors). Do not usecontextinimageBaseUrl,ckTransportConfig, ordesignSize.
Tokens: You do not configure
CkTokenProviderin your app. WithauthConfigset, CkAuthService stores and refreshes tokens. Without auth, CoreKit uses an internal unauthenticated provider automatically.
Splash Screen Routing #
CoreKit provides built-in support for splash screen routing with a default 3-second delay. This ensures your splash screen is visible long enough for branding and allows you to run custom initialization tasks during the delay.
How It Works #
- App Launch →
CoreKitRouterGateblocks rendering until initialization completes - Custom Initialization → If
config.onInitis provided, it runs during the splash delay (e.g., register other dependencies) - 3-Second Delay → Enforced minimum splash screen duration for branding/UX
- Auto-Navigation → After the delay,
CkAuthService.instance.autoNavigate()routes to the appropriate screen based on auth state
Using onInit for Custom Initialization #
The onInit callback is perfect for initializing services that don't require navigation context:
class CkConfigImpl extends CoreKitConfig with CoreKitConfigDefaults {
@override
String get imageBaseUrl => 'https://cdn.example.com/';
@override
CkTransportConfig get ckTransportConfig => CkTransportConfig(
baseUrl: 'https://api.example.com',
refreshTokenEndpoint: '/auth/refresh',
);
/// Custom initialization tasks run during the 3-second splash delay.
@override
Future<void> Function()? get onInit => () async {
// Register your app's dependencies
await MyDependencyRegistry.init();
// Initialize analytics or other services
await Analytics.initialize();
// Load cached data
await CacheManager.warmUp();
};
}
Splash Screen with Auth #
When using the authentication module, configure navigation callbacks in CkAuthFlowHandlers:
@override
CkAuthConfig<UserProfile> get authConfig => CkAuthConfig(
endpoints: const CkAuthEndpoints(
signup: '/auth/signup',
signin: '/auth/login',
forgotPassword: '/auth/forgot-password',
sendOtp: '/auth/otp/send',
verifyOtp: '/auth/otp/verify',
getProfile: '/auth/profile',
updateProfile: '/auth/profile',
logout: '/auth/logout',
resetPassword: '/auth/reset-password',
),
loginBodyBuilder: (cb) => {'email': cb.username, 'password': cb.password},
otpConfig: CkOtpConfig(
autoTriggers: {CkOtpTrigger.signup, CkOtpTrigger.forgetPassword},
verifyBodyBuilder: (ctx) async => {'otp': ctx.otp},
resendBodyBuilder: (ctx) => {'email': ctx.identifier},
),
extractors: CkAuthExtractors(
accessToken: (data) => data['token'] as String?,
profile: (data) => UserProfile.fromJson(data['user'] as Map<String, dynamic>),
),
handlers: CkAuthFlowHandlers(
onAuthenticated: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const HomeScreen()),
(_) => false,
);
},
showLogin: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const LoginScreen()),
(_) => false,
);
},
showOnboarding: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const OnboardingScreen()),
(_) => false,
);
},
),
);
The Flow #
- App starts →
preInitChildis shown (your splash screen) - CoreKit initializes Dio, screen utils, and auth (if configured)
onInitcallback runs (if provided) - perfect for dependency registration- 3-second minimum delay is enforced (if initialization finishes faster, it waits)
CkAuthService.instance.autoNavigate()is called automatically- User is routed to the appropriate screen based on auth state
This ensures a smooth, branded splash experience while giving you time to initialize your app's dependencies.
Navigation & Global Access #
// Navigator
coreKitInstance.navigatorKey.currentState?.push(
MaterialPageRoute(builder: (_) => const ProfileScreen()),
);
coreKitInstance.navigatorKey.currentState?.pop();
// Theme / colors (after CoreKit is mounted)
final theme = coreKitInstance.theme;
final primary = coreKitInstance.primaryColor;
// Image base URL for CkImage network paths
final base = coreKitInstance.imageBaseUrl;
UI Components #
Text #
CkText(
text: 'Hello World',
fontSize: 18,
fontWeight: FontWeight.bold,
textColor: Colors.blue,
top: 10,
bottom: 10,
)
CkRichText(
richTextContent: [
CkSimpleRichTextContent(
text: 'By continuing you agree to our ',
style: TextStyle(color: Colors.grey),
),
CkSimpleRichTextContent(
text: 'Terms',
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
ontap: () => openTerms(),
),
],
)
Buttons #
CkButton(
titleText: 'Submit',
onTap: () => submit(),
isLoading: _submitting,
buttonWidth: 200,
buttonHeight: 48,
gradient: LinearGradient(colors: [Colors.blue, Colors.purple]),
)
CkSelectableButton(
title: 'Premium',
isSelected: _premium,
onTap: () => setState(() => _premium = !_premium),
)
CkRadioGroup<String>(
items: const ['A', 'B', 'C'],
selectedItem: _selected,
onChanged: (v) => setState(() => _selected = v),
itemBuilder: (item, selected) => Text(item),
)
CkMultipleSelector<String>(
items: const ['Tag 1', 'Tag 2', 'Tag 3'],
selectedItems: _tags,
onChanged: (items) => setState(() => _tags = items),
itemBuilder: (item, selected) => Chip(label: Text(item)),
)
Text fields #
CkTextField(
validationType: CkValidationType.validateEmail,
hintText: 'Email',
labelText: 'Email',
onSaved: (value, controller) => _email = value,
)
// Disable auto-capitalization on a specific field
CkTextField(
validationType: CkValidationType.validateRequired,
hintText: 'Username',
enableCapitalization: false,
onSaved: (value, controller) => _username = value,
)
CkMultilineTextField(
hintText: 'Description',
maxLines: 5,
maxLength: 500,
maxWords: 100,
validationType: CkValidationType.notRequired,
onSaved: (value, controller) => _description = value,
)
CkPhoneNumberTextField(
hintText: 'Phone',
initalCountryCode: 'BD',
onChanged: (phone) => debugPrint(phone.completeNumber),
)
CkDateInputTextField(
hintText: 'Birth date',
firstDate: DateTime(1900),
lastDate: DateTime.now(),
onDateSelected: (date) => _birthDate = date,
)
URL auto-lowercase: Any URL typed or pasted in
CkTextFieldorCkMultilineTextFieldis automatically lowercased. The rest of the text is unchanged.
enableCapitalization(defaulttrue): Set tofalseon any field to disable automatic sentence capitalization.
Global defaults: Use
inputConfigin yourCorekitConfigImplto set border, colors, text style, and capitalization for all fields at once. Per-field values always win.
CkValidationType |
Purpose |
|---|---|
validateRequired |
Non-empty |
validateEmail |
Email format |
validatePassword |
Strong password rules |
validatePhone |
Phone format |
validateFullName |
Full name |
validateConfirmPassword |
Matches originalPassword callback |
validateNumber |
Numeric |
notRequired |
Optional field |
validateOTP |
6-digit OTP |
validateUsernameOrEmail |
Accepts either a valid username or a valid email address |
| … | See ck_validation_type.dart for full list |
Validation messages live in CkString (for example CkString.invalidEmail).
Dropdowns #
CkDropDown<User>(
items: users,
hint: 'Select user',
nameBuilder: (user) => user.name,
onChanged: (user) => setState(() => _user = user),
)
CkRadioGroupFormField<String>(
options: const ['Option 1', 'Option 2'],
labelBuilder: (option) => option,
initialValue: _selectedOption,
onSaved: (value) => _selectedOption = value,
)
Images #
CkImage is a highly versatile image loading widget that dynamically resolves the source based on prefix or file type. It accepts SVG assets, network URLs, local file system paths, and standard asset images. It also provides built-in support for rendering images in grayscale.
// 1. Network Image (absolute URL or relative path prefixed with imageBaseUrl)
CkImage(
src: 'products/item.jpg', // relative
// src: 'https://example.com/logo.png', // absolute URL
size: 100,
borderRadius: 12,
fill: BoxFit.cover,
)
// 2. SVG Asset Image (recognized automatically if starting with assets/ and ending/containing .svg)
CkImage(
src: 'assets/svg/icon.svg',
size: 24,
)
// 3. Local File System Image (recognized by file path structure/existence)
CkImage(
src: '/var/mobile/Containers/Data/Application/.../image.jpg',
size: 150,
)
// 4. Standard Png/Jpg Asset Image
CkImage(
src: 'assets/images/banner.png',
size: 200,
)
// 5. Grayscale Support
CkImage(
src: 'products/item.jpg',
enableGrayscale: true, // Converts image colors to grayscale
size: 100,
)
// Image pickers
CkImagePicker(
onImagePicked: (file) => setState(() => _image = file),
placeholder: const Icon(Icons.add_a_photo),
)
CkMultiImagePicker(
maxImages: 5,
onImagesChanged: (files) => setState(() => _images = files),
)
Lists & grids (pagination) #
Parent widgets own the data list and pass counts/flags into loaders.
class ProductListPage extends StatefulWidget {
const ProductListPage({super.key});
@override
State<ProductListPage> createState() => _ProductListPageState();
}
class _ProductListPageState extends State<ProductListPage> {
final _items = <Product>[];
bool _loading = false;
bool _loadDone = false;
Future<void> _fetch(int page) async {
setState(() => _loading = true);
final res = await CkTransport.request<List<Product>>(
input: RequestInput(
endpoint: '/products',
method: RequestMethod.GET,
queryParams: {'page': page, 'limit': 10},
),
responseBuilder: (data) =>
(data as List).map((e) => Product.fromJson(e)).toList(),
);
if (res.isSuccess && res.data != null) {
setState(() {
if (page == 1) _items.clear();
_items.addAll(res.data!);
_loadDone = res.data!.length < 10;
_loading = false;
});
} else {
setState(() => _loading = false);
}
}
@override
void initState() {
super.initState();
_fetch(1);
}
@override
Widget build(BuildContext context) {
return CkListView(
itemCount: _items.length,
isLoading: _loading,
isLoadDone: _loadDone,
onRefresh: () => _fetch(1),
onLoadMore: (page) => _fetch(page),
emptyWidget: const Center(child: Text('No products')),
itemBuilder: (context, index) => ListTile(
title: Text(_items[index].name),
),
);
}
}
CkGridView(
itemCount: _items.length,
isLoading: _loading,
isLoadDone: _loadDone,
onLoadMore: (page) => _fetch(page),
gridConfig: CkGridConfig(itemInRow: 2, crossAxisSpacing: 8),
itemBuilder: (context, index) => ProductCard(_items[index]),
)
Tabbed lists use CkTabListView with one CkTabConfig per tab:
CkTabListView(
value: _activeTab,
tabs: [
CkTabConfig(tab: 'all', itemCount: _all.length, isLoading: _loadingAll),
CkTabConfig(tab: 'active', itemCount: _active.length, isLoading: _loadingActive),
],
itemBuilder: (ctx, index) {
final list = ctx.tab == 'all' ? _all : _active;
return ListTile(title: Text(list[index].title));
},
onLoadMore: (ctx, page) => _loadTab(ctx.tab, page),
onRefresh: (ctx) => _loadTab(ctx.tab, 1),
)
Other widgets #
CkTabBar(
tabs: const ['Tab 1', 'Tab 2'],
views: const [TabOne(), TabTwo()],
)
CkRatingBar(
initialRating: 3.5,
allowHalfRating: true,
onRatingUpdate: (r) => setState(() => _rating = r),
)
CkDottedBorder(
borderRadius: 12,
child: const Padding(
padding: EdgeInsets.all(24),
child: Text('Drop files here'),
),
)
CkLoader(size: 40, color: Colors.blue)
Forms & Validation #
class LoginEntity {
String email = '';
String password = '';
}
CkFormBuilder<LoginEntity>(
entity: LoginEntity(),
builder: (context, formKey, entity) => Column(
children: [
CkTextField(
validationType: CkValidationType.validateEmail,
hintText: 'Email',
onSaved: (v, _) => entity.email = v,
),
CkTextField(
validationType: CkValidationType.validatePassword,
hintText: 'Password',
onSaved: (v, _) => entity.password = v,
),
CkButton(
titleText: 'Login',
onTap: () {
if (formKey.validateAndSave()) {
// entity.email & entity.password are set
}
},
),
],
),
)
validateAndSave() is an extension on GlobalKey<FormState> from extension.dart.
Simple Forms (CkForm) #
For basic form layouts that do not require binding to an entity object, you can use CkForm. It provides a clean way to manage the internal GlobalKey<FormState> automatically:
CkForm(
builder: (context, formKey) => Column(
children: [
CkTextField(
validationType: CkValidationType.validateRequired,
hintText: 'Full Name',
),
CkButton(
titleText: 'Submit',
onTap: () {
if (formKey.currentState?.validate() ?? false) {
formKey.currentState?.save();
// process form submission
}
},
),
],
),
)
Dialogs & Overlays #
App bar #
CkAppBar(
title: 'Settings',
onBackPress: () => Navigator.pop(context),
)
Dialogs #
await CkDialog(
context: context,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CkText(text: 'Welcome', fontSize: 20),
CkButton(
titleText: 'OK',
onTap: () => Navigator.pop(context),
),
],
),
);
await CkDialogWithActions(
context: context,
title: 'Delete item?',
content: [const CkText(text: 'This cannot be undone.')],
action: 'Delete',
cancel: 'Cancel',
onConfirm: () => deleteItem(),
);
// Form validation inside dialog actions
await CkDialogWithActions(
context: context,
title: 'Enter feedback',
content: [
CkTextField(
validationType: CkValidationType.validateRequired,
hintText: 'Your comment',
),
],
validationRequired: true, // wraps in a CkForm automatically and validates before confirm
onConfirm: () => submitFeedback(),
);
Bottom sheet, snackbar, alert, menu #
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => CkDraggableBottomSheet(
minChildSize: 0.3,
maxChildSize: 0.9,
child: const YourSheetContent(),
),
);
CkSnackBar('Saved', type: CkSnackBarType.success);
CkSnackBar('Failed', type: CkSnackBarType.error);
Global overrides: Style screen position (top/bottom), margins, border radius, background color, shadow effects, padding, and semantic colors/icons using
snackBarConfiginCorekitConfigImpl. Per-type semantic overrides (e.g.successColor,errorIcon) apply instantly to the custom snackbar layout.
// Shows immediately when constructed CkAlert( title: 'Confirm', content: const Text('Proceed?'), actionButtonTittle: 'Yes', cancelButtonTittle: 'No', onTap: () => doAction(), );
CkPopupMenu
---
## Responsive Utilities
Scaling is relative to `designSize` (default **428×926**). Initialized automatically by `CoreKit`.
```dart
Container(
width: 200.w,
height: 100.h,
padding: EdgeInsets.all(16).responsive,
child: Text('Title', style: TextStyle(fontSize: 18.sp)),
)
Column(
children: [
const Widget1(),
20.height, // SizedBox(height: 20.h)
const Widget2(),
],
)
Transport (HTTP) #
CkTransport is initialized by CoreKit (or CkAuthService when auth is enabled).
final response = await CkTransport.request<User>(
input: RequestInput(
endpoint: '/users/me',
method: RequestMethod.GET,
),
responseBuilder: (data) => User.fromJson(data as Map<String, dynamic>),
showMessage: true, // show error snackbar on failure or success message
);
if (response.isSuccess) {
final user = response.data;
} else {
debugPrint(response.message ?? 'Request failed');
}
POST with JSON body #
await CkTransport.request(
input: RequestInput(
endpoint: '/orders',
method: RequestMethod.POST,
jsonBody: {'productId': id, 'qty': 1},
),
responseBuilder: (data) => data,
);
Upload (multipart) #
await CkTransport.request(
input: RequestInput(
endpoint: '/upload',
method: RequestMethod.POST,
files: {'avatar': pickedXFile}, // XFile from image_picker
),
responseBuilder: (data) => data,
);
CkTransportConfig response shape #
Default extractor expects:
{ "success": true, "meta": { ... }, "data": { ... }, "message": "..." }
meta is optional — if the API omits it, CkResponse.meta is null. When present, it is passed through as dynamic (pagination, totals, etc.).
final response = await CkTransport.request<PaginatedItems>(/* ... */);
final items = response.data;
final pagination = response.meta as Map<String, dynamic>?;
Override with a custom CkResponseExtractor in CkTransportConfig if your API differs:
responseExtractor: CkResponseExtractor(
data: (body) => body['result'],
message: (body) => body['msg']?.toString(),
meta: (body) => body['pagination'], // or omit to use defaultMeta
),
RequestMethod values #
GET, POST, PUT, DELETE, PATCH
RequestInput fields #
| Field | Type | Notes |
|---|---|---|
endpoint |
String |
Required. Path relative to baseUrl. |
method |
RequestMethod |
Required. |
pathParams |
List<String>? |
Appended to the endpoint path. |
queryParams |
Map<String, dynamic>? |
URL query string. |
headers |
Map<String, String>? |
Per-request headers merged with global ones. |
jsonBody |
Map<String, dynamic>? |
JSON encoded body. |
formFields |
Map<String, dynamic>? |
Multipart form fields. |
files |
Map<String, dynamic>? |
File uploads (XFile values). |
listBody |
List<Map<String, dynamic>>? |
JSON array body. |
requiresToken |
bool |
Default true. Set false for public endpoints. |
cancelToken |
CancelToken? |
Dio cancel token for request cancellation. |
onSendProgress |
Function(int, int)? |
Upload progress callback. |
onReceiveProgress |
Function(int, int)? |
Download progress callback. |
CkTransportConfig fields #
| Field | Default | Notes |
|---|---|---|
baseUrl |
required | API base URL |
refreshTokenEndpoint |
required | Path for token refresh |
connectTimeout |
15s | |
receiveTimeout |
15s | |
sendTimeout |
15s | |
tokenHeaderKey |
'Authorization' |
Header name for access token |
refreshTokenHeaderKey |
'refreshToken' |
Header name for refresh token |
isBearerToken |
true |
Prepends 'Bearer ' to the token value |
refreshTokenRequestMethod |
POST |
|
enableDebugLogs |
false |
Logs Dio requests/responses |
onLogout |
null |
Called when refresh fails — clear session, navigate to login |
responseExtractor |
— | Custom CkResponseExtractor if your API shape differs |
Storage #
CkStorage uses FlutterSecureStorage with automatic fallback to SharedPreferences. No extra storage dependencies in your app.
await CkStorage.write('theme', 'dark');
final theme = await CkStorage.read('theme'); // returns null if key absent
await CkStorage.delete('theme');
await CkStorage.deleteAll();
Storage internals (zero-latency reads):
- On first init, all keys are loaded into an in-memory cache in a single platform call.
readreturns from cache — O(1), no I/O.write/deleteupdate the cache synchronously, then persist to disk in the background.- If
FlutterSecureStoragetimes out (known Android Keystore warm-up bug), it automatically falls back toSharedPreferences.
CkStorage.initialize() is called automatically when the auth module starts; you may call it early if needed.
Authentication Module #
When authConfig is set on CoreKitConfig, CoreKit initializes the authentication module internally — tokens, profile cache, OTP, logout, and routing callbacks are handled automatically.
All public auth types use the Ck prefix (aligned with the rest of CoreKit).
Public API (Ck prefix) #
| Type | Role |
|---|---|
CkAuthConfig<T> |
Auth module configuration (endpoints, extractors, handlers) |
CkAuthEndpoints |
Sign-in, sign-up, OTP, profile, logout URL strings + method overrides |
CkAuthExtractors |
Maps CkResponse.data → tokens, profile, verification tokens, messages |
CkAuthFlowHandlers |
Navigation callbacks triggered automatically by CoreKit |
CkAuthResult<T> |
Result wrapper for sign-in, OTP, profile calls |
CkAuth |
Static gateway class (sign-in, OTP, profile, social, logout) |
CkAuthLoadingType |
Enum of all auth operations that expose loading state |
CkAuthLoadingController |
Manages per-operation CkBehaviorStream<bool> loading streams |
CkOtpConfig / CkOtpTrigger |
OTP behaviour and triggers |
CkSocialLoginConfig |
Google / Apple / Facebook / custom social |
CkGoogleAuthConfig / CkGoogleAuthData |
Google backend + credential payload |
CkAppleAuthConfig / CkAppleAuthData |
Apple backend + credential payload |
CkFacebookAuthConfig / CkFacebookAuthData |
Facebook backend + credential payload |
CkCustomSocialAuthConfig |
Custom provider URL + body builder |
CkBehaviorStream<T> |
Replay-latest stream (profile, auth status, OTP timer) |
CkAuthFlowHandlers callbacks #
| Callback | Required | When called |
|---|---|---|
onAuthenticated |
Yes | After successful login / signup / social / token restore |
showLogin |
Yes | When unauthenticated and onboarding is skipped / done |
showOnboarding |
No | First time (or always if firstTimeOnly: false) unauthenticated users |
showOtpVerification |
No | After sign-up / forgot-password when OTP is auto-triggered |
showResetPassword |
No | After OTP verification for forget-password flow |
firstTimeOnly |
— | true (default) = onboarding only for new users |
CkAuthEndpoints parameters #
| Parameter | Default method | Notes |
|---|---|---|
signup |
POST |
|
signin |
POST |
|
forgotPassword |
POST |
|
sendOtp |
POST |
|
verifyOtp |
POST |
|
verifyForgetOtp |
POST |
Optional separate endpoint for forget-password OTP |
getProfile |
GET |
|
updateProfile |
PATCH |
|
logout |
POST |
|
resetPassword |
POST |
Override via resetPasswordMethod |
Each endpoint also has a corresponding *Method override (e.g. sendOtpMethod: RequestMethod.PATCH).
Extractors & profile (CkResponse.data) #
Transport unwraps { success, data, message, meta? } so CkResponse.data is the inner payload your backend returns (tokens, user object, etc.).
CkAuthExtractorscallbacks always receive thatdynamicdata — not the raw HTTP envelope.CkAuthExtractors.standard()reads keys ondata(accessToken,user, …).CkAuthServiceautomatically manages profile parsing, caching, and stream updates.applyFromResponseparses the response,jsonEncodesresponse.datafor storage, and skips re-parse when the encoded string is unchanged.- On cold start, stored JSON is
jsonDecoded and restored intocurrentProfileandprofileStreamautomatically.
Customizing Response Extractors (CkAuthExtractors)
CkAuthExtractors is non-generic — it holds extractors for tokens, profile, verification tokens, and messages. Profile parsing is handled through:
profile— extracts profile from API response data and cold-start restore (dynamic → your model)
Here is a comprehensive example showing where to specify extractors and how to provide each of the available callbacks:
extractors: CkAuthExtractors(
// 1. Core Authentication Tokens
accessToken: (data) => data['token'] as String?,
refreshToken: (data) => data['refresh_token'] as String?,
// 2. Profile extraction from API response and cold-start restore
profile: (data) => UserProfile.fromJson(data['user'] as Map<String, dynamic>),
// 3. OTP / Flow Verification Tokens (unified trigger-keyed map)
verificationTokens: {
CkOtpTrigger.signup: (data) => data['signUpVerificationToken'] as String?,
CkOtpTrigger.login: (data) => data['loginVerificationToken'] as String?,
CkOtpTrigger.forgetPassword: (data) => data['forgetToken'] as String?,
},
// 4. Custom API message extraction
message: (data) => data['message'] as String?,
),
Quick Mappings using Factory Helper Methods
If your backend keys are standard, you can utilize built-in factory builders instead of writing manual lambdas:
-
CkAuthExtractors.standard(): Uses standard keys located directly at the root ofresponse.data. Internalizes verification token keys ('createUserToken','loginUserToken', and'forgetToken') without exposing any parameter configuration for them.extractors: CkAuthExtractors.standard( accessTokenKey: 'token', refreshTokenKey: 'refresh_token', profileKey: 'user', messageKey: 'message', ), -
CkAuthExtractors.fromPaths(): Uses nested dot-notation paths inside the response. Internalizes standard verification token paths automatically without requiring custom parameters.extractors: CkAuthExtractors.fromPaths( accessTokenPath: 'auth.tokens.access', refreshTokenPath: 'auth.tokens.refresh', profilePath: 'nested.profile.data', messagePath: 'status.message', ),
Mock Auth (Bypassing Authentication API Calls) #
If you want to design and test your UI without making actual HTTP request calls, you can set mockAuth: true in CkAuthConfig.
CkAuthConfig(
mockAuth: true, // Default is false. When true, all network requests are bypassed.
// ...
)
When mockAuth is set to true:
- Auth operations (e.g.
signIn,signUp,socialLogin,verifyOtp) instantly complete and mock a successful response. - Secure tokens are stubbed with dummy mock tokens locally so the app behaves as if authenticated.
- Navigation transitions and state flow (like redirection to OTP or onboarding) still execute normally so you can test all user flow states without any working backend.
1. Profile model & auth override #
Add this to your CkConfigImpl (keep the other overrides from Configuration):
class UserProfile {
final String id;
final String email;
final String name;
const UserProfile({required this.id, required this.email, required this.name});
factory UserProfile.fromJson(Map<String, dynamic> json) => UserProfile(
id: json['id']?.toString() ?? '',
email: json['email']?.toString() ?? '',
name: json['name']?.toString() ?? '',
);
Map<String, dynamic> toJson() => {'id': id, 'email': email, 'name': name};
}
class CkConfigImpl extends CoreKitConfig with CoreKitConfigDefaults {
@override
String get imageBaseUrl => 'https://cdn.example.com/';
@override
CkTransportConfig get ckTransportConfig => CkTransportConfig(
baseUrl: 'https://api.example.com',
refreshTokenEndpoint: '/auth/refresh',
);
/// When set, CoreKit initializes [CkAuthService] and manages tokens internally.
@override
CkAuthConfig<UserProfile> get authConfig => CkAuthConfig(
mockAuth: false, // Set to true to bypass actual backend API calls during UI design
endpoints: const CkAuthEndpoints(
signup: '/auth/signup',
signin: '/auth/login',
forgotPassword: '/auth/forgot-password',
sendOtp: '/auth/otp/send',
verifyOtp: '/auth/otp/verify',
getProfile: '/auth/profile',
updateProfile: '/auth/profile',
logout: '/auth/logout',
resetPassword: '/auth/reset-password',
),
extractors: CkAuthExtractors(
accessToken: (data) => data['accessToken']?.toString(),
profile: (data) => UserProfile.fromJson(data),
),
loginBodyBuilder: (cb) => {'email': cb.username, 'password': cb.password},
handlers: CkAuthFlowHandlers(
onAuthenticated: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const HomeScreen()),
(_) => false,
);
},
showLogin: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const LoginScreen()),
(_) => false,
);
},
showOnboarding: () {
coreKitInstance.navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const OnboardingScreen()),
(_) => false,
);
},
// firstTimeOnly: true, // Optional: default is true (onboarding for first-timers only)
),
otpConfig: CkOtpConfig(
autoTriggers: {CkOtpTrigger.signup, CkOtpTrigger.forgetPassword},
resendCooldown: const Duration(seconds: 120),
verifyBodyBuilder: (ctx) async => {'otp': ctx.otp},
resendBodyBuilder: (ctx) => {'email': ctx.identifier},
),
socialLoginConfig: CkSocialLoginConfig(
google: CkGoogleAuthConfig(
backendUrl: '/auth/google',
bodyBuilder: (data) => {
'idToken': data.idToken,
'email': data.email,
},
),
),
);
}
Real-World Advanced Configuration Example
Here is a complete, advanced real-world implementation example (CorekitConfigImpl) demonstrating custom initialization (onInit), zero splash delay, custom endpoint method configurations (e.g., PATCH), GetX integration for flow navigation handlers, and asynchronous verifyBodyBuilder with local storage integration:
class CorekitConfigImpl extends CoreKitConfig with CoreKitConfigDefaults {
@override
Size get designSize => const Size(428, 926);
@override
String get imageBaseUrl => ApiEndPoints.imageUrl;
/// Disable enforced splash delay for faster navigation
@override
int get splashDelayMs => 0;
/// Custom initialization tasks run during the 3-second splash delay.
/// Use this to register dependencies, initialize services, etc.
@override
Future<void> Function()? get onInit => () async {
// Initialize TimerService as a service
Get.put(TimerService(), permanent: true);
// Initial Bindings
AppInitialBindings().dependencies();
};
@override
CkTransportConfig get ckTransportConfig => CkTransportConfig(
baseUrl: ApiEndPoints.baseUrl,
refreshTokenEndpoint: ApiEndPoints.refreshToken,
enableDebugLogs: kDebugMode,
);
@override
CkAuthConfig get authConfig => CkAuthConfig(
mockAuth: false, // When true, bypasses network calls and stubs mock session
endpoints: CkAuthEndpoints(
resetPassword: ApiEndPoints.resetPassword,
forgotPassword: ApiEndPoints.forgotPassword,
signup: ApiEndPoints.createUser,
signin: ApiEndPoints.login,
sendOtp: ApiEndPoints.resendOtp,
verifyOtp: ApiEndPoints.verifyOtp,
getProfile: ApiEndPoints.getMyProfile,
updateProfile: ApiEndPoints.editMyProfile,
verifyForgetOtp: ApiEndPoints.forgotPasswordOtpMatch,
logout: "",
resetPasswordMethod: RequestMethod.PATCH,
verifyForgotOtpMethod: RequestMethod.PATCH,
sendOtpMethod: RequestMethod.PATCH,
),
loginBodyBuilder: (LoginCallback loginCallBack) {
return {
'email': loginCallBack.username,
'password': loginCallBack.password,
};
},
extractors: CkAuthExtractors(
accessToken: (data) => data['accessToken']?.toString(),
refreshToken: (data) => data['refreshToken']?.toString(),
resetPasswordToken: (data) => data['forgetOtpMatchToken']?.toString(),
profile: (data) {
final profile = ProfileData.fromJson(data);
return profile;
},
message: (data) => data['message']?.toString(),
verificationTokens: {
CkOtpTrigger.signup: (data) =>
data['createUserToken']?.toString() ?? data['token']?.toString(),
CkOtpTrigger.login: (data) => data['loginUserToken']?.toString(),
CkOtpTrigger.forgetPassword: (data) => data['forgetToken']?.toString(),
},
),
otpConfig: CkOtpConfig(
autoTriggers: {CkOtpTrigger.signup, CkOtpTrigger.forgetPassword, CkOtpTrigger.login},
verificationStrategy: CkOtpVerificationStrategy.tokenBased,
verificationTokenHeaderKey: 'token',
sendVerificationTokenInHeader: true,
verifyBodyBuilder: (ctx) async {
if (ctx.trigger == CkOtpTrigger.signup) {
final responses = await StorageService.instance
.getQuestionnaireResponses();
final questionOutput = await StorageService.instance
.getQuestionnaireOutput();
return {
"otp": ctx.otp,
"questions": responses,
"questionOutput": questionOutput,
};
}
return {"otp": ctx.otp};
},
resendBodyBuilder: (ctx) {
return {"email": ctx.identifier};
},
),
handlers: CkAuthFlowHandlers(
showResetPassword: () {
Get.offNamed(
AppRoute.changePasswrodScreen,
arguments: {'isForgetPassword': true},
);
},
showOtpVerification: () {
Get.toNamed(
AppRoute.otpVerificationScreen,
arguments: {'screen': "", 'email': ""},
);
},
onAuthenticated: () {
final profile = const CkAuth<ProfileData>().profile;
if (profile?.subscriptionPackageId == null) {
Get.offAllNamed(AppRoute.subscriptionscreen);
} else {
Get.offAllNamed(AppRoute.bottomNav);
}
},
showLogin: () {
Get.offAllNamed(AppRoute.loginScreen);
},
showOnboarding: () {
Get.offAllNamed(AppRoute.onboardingscreen);
},
),
);
}
2. Declarative Auto-Navigation #
You do not need an AuthGate widget. When you configure handlers in your CkAuthConfig, CoreKit handles navigation and flow direction automatically:
- Splash Screen: On app launch,
preInitChildis shown. CoreKit enforces a 3-second minimum splash delay for branding, during which you can run custom initialization viaconfig.onInit. - Session Restoration: After the splash delay, CoreKit checks if secure tokens exist.
- Onboarding Integration: If the user is unauthenticated and
showOnboardingis provided, they are routed to onboarding.- If
firstTimeOnly: true(default), onboarding is only shown to first-time users. Returning unauthenticated users go directly to the login screen. - If
firstTimeOnly: false, all unauthenticated users are routed to onboarding.
- If
- Redirection: On successful authentication (login, signup, or social login),
onAuthenticatedis called. On logout or refresh failure,showLoginorshowOnboardingis called.
The auto-navigation is triggered automatically by CkAuthService.instance.autoNavigate() after the splash delay completes. If you need manual control, you can call autoNavigate() yourself at any time.
If you prefer to listen to auth state changes manually (e.g. via a Bloc or routing guard), simply omit handlers (set it to null or leave it out). CkAuthConfig's handlers parameter is completely optional.
3. Sign in / sign up #
To perform auth operations, declare a global instance of CkAuth typed to your profile model (typically in constants.dart or auth.dart):
const auth = CkAuth<UserProfile>();
Sign In
auth.signIn(
username: email,
password: password,
);
Sign Up
await auth.signUp(
body: {'email': email, 'password': password},
);
Forgot Password
await auth.forgotPassword(
body: {'email': email},
);
Reset Password
await auth.updatePassword(
body: {'password': newPassword, 'confirmPassword': newPassword},
);
4. OTP #
// Countdown stream (seconds remaining; 0 = can resend)
auth.otpManager.resendCountdown.listen((seconds) {
setState(() => _seconds = seconds);
});
// Or reactively bind OTP countdown updates directly to UI (similar to profileUi)
// The builder receives only the remaining seconds as an int — no context or snapshot.
auth.otpCountdownUi(
builder: (seconds) {
if (seconds > 0) {
return Text('Resend OTP in ${seconds}s');
}
return TextButton(
onPressed: () => auth.sendOtp(),
child: const Text('Resend OTP'),
);
},
)
await auth.sendOtp();
final verified = await auth.verifyOtp(
otp: '123456',
);
5. Loading State UI #
Every auth operation (sign-in, sign-up, OTP, profile, social, logout, etc.) automatically tracks its loading state. Use auth.loadingUi to reactively bind loading state to your widgets — the builder receives only a bool.
Available loading types (CkAuthLoadingType)
| Type | Tracks |
|---|---|
signUp |
auth.signUp() |
signIn |
auth.signIn() |
forgotPassword |
auth.forgotPassword() |
verifyOtp |
auth.verifyOtp() |
sendOtp |
auth.sendOtp() |
updatePassword |
auth.updatePassword() |
socialLogin |
auth.signInWithGoogle(), auth.signInWithApple(), auth.signInWithFacebook(), auth.signInWithCustom() |
logout |
auth.logout() |
fetchProfile |
auth.fetchProfile() |
updateProfile |
auth.updateProfile() |
Basic usage
// Wrap a button to show loading state during sign-in
auth.loadingUi(
type: CkAuthLoadingType.signIn,
builder: (isLoading) => CkButton(
titleText: 'Sign In',
isLoading: isLoading,
onTap: isLoading
? null
: () => auth.signIn(username: email, password: password),
),
)
Sign-up button with loading
auth.loadingUi(
type: CkAuthLoadingType.signUp,
builder: (isLoading) => ElevatedButton(
onPressed: isLoading ? null : () => auth.signUp(body: formData),
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Create Account'),
),
)
Verify OTP with loading
auth.loadingUi(
type: CkAuthLoadingType.verifyOtp,
builder: (isLoading) => CkButton(
titleText: 'Verify',
isLoading: isLoading,
onTap: isLoading ? null : () => auth.verifyOtp(otp: otpCode),
),
)
Multiple loading types on the same screen
Column(
children: [
// Sign-in button
auth.loadingUi(
type: CkAuthLoadingType.signIn,
builder: (isLoading) => CkButton(
titleText: 'Sign In',
isLoading: isLoading,
onTap: () => auth.signIn(username: email, password: password),
),
),
const SizedBox(height: 16),
// Google sign-in button
auth.loadingUi(
type: CkAuthLoadingType.socialLogin,
builder: (isLoading) => CkButton(
titleText: 'Continue with Google',
isLoading: isLoading,
onTap: () => auth.signInWithGoogle(googleData),
),
),
],
)
Programmatic access (without UI)
// Check loading state synchronously
final isSigningIn = auth.loadingController.isLoading(CkAuthLoadingType.signIn);
// Listen to loading state changes
auth.loadingController.streamOf(CkAuthLoadingType.signIn).listen((isLoading) {
debugPrint('Sign-in loading: $isLoading');
});
6. Profile #
// Declare a global typesafe gateway instance (typically in constants.dart or auth.dart)
const auth = CkAuth<UserProfile>();
// Synchronously read the cached active user profile directly (fully typesafe!)
final UserProfile? user = auth.profile;
// Reactively bind profile updates to UI (profile is auto-inferred as UserProfile?)
auth.profileUi(
builder: (context, profile) {
if (profile == null) return const Text('Guest');
return Text('Hello, ${profile.name}');
},
)
// Option B: Instantiate on the fly / inline (extremely lightweight using const)
final userInline = const CkAuth<UserProfile>().profile;
// Fetch fresh profile from remote (automatically caches and updates the stream/UI)
final result = await auth.fetchProfile();
// Update profile on the remote (automatically updates the stream and cache/UI)
final updated = await auth.updateProfile(
jsonBody: {'name': 'New Name'},
);
7. Social login (Google example) #
After your app obtains Google credentials (for example via google_sign_in):
final result = await auth.signInWithGoogle(
CkGoogleAuthData(
idToken: idToken,
accessToken: accessToken,
email: email,
displayName: displayName,
),
);
Also available: signInWithApple, signInWithFacebook, signInWithCustom.
8. Logout #
await auth.logout();
// Clears tokens/profile and auto-navigates (calls showLogin or showOnboarding)
Auth architecture (overview) #
flowchart LR
CoreKit --> CkAuthService
CoreKit --> CkTransport
CkAuthService --> CkAuthTokenManager
CkAuthService --> CkAuthLoadingController
CkAuthService --> CkProfileExtractor
CkAuthService --> CkOtpFlowManager
CkAuthService --> CkLogoutHandler
Location Pickers #
Country / state / city widgets use bundled data from flutter_country_state — no backend required.
MapEntry<String, String>? _country;
CkStateDropDownItemProperty? _selectedState;
String? _city;
CkCountryPicker(
hint: 'Country',
onChanged: (entry) => setState(() {
_country = entry;
_selectedState = null;
_city = null;
}),
)
CkStateDropDown(
countryName: _country?.value ?? '',
initialState: _selectedState?.abbreviation ?? _selectedState?.stateName, // Accepts state name or abbreviation
hint: 'State',
onChanged: (property) => setState(() {
_selectedState = property; // Contains property.stateName and property.abbreviation
_city = null;
}),
selectedItemBuilder: (property) => CkText(
text: property.stateName,
),
nameBuilder: (property) => CkText(
text: '${property.stateName}${property.abbreviation != null ? ' (${property.abbreviation})' : ''}',
),
)
CkCityDropDown(
selectedCountry: _country?.value ?? '',
selectedState: _selectedState?.abbreviation ?? _selectedState?.stateName ?? '', // Accepts state abbreviation or name
hint: 'City',
onChange: (city) => setState(() => _city = city),
)
CkCountryPickerreceivesMapEntry<String, String>?(country name → country code).CkStateDropDownreceivesCkStateDropDownItemProperty?containingstateName(e.g.'California') andabbreviation(e.g.'CA').CkCityDropDownaccepts either state abbreviation or full state name inselectedState.
Utilities & Extensions #
Permissions #
final granted = await CkPermissionHelper.request(Permission.camera);
if (granted) {
// use camera
}
Sharing #
await CkShare.instance.content(
title: 'Check this out',
imageUrl: 'banners/promo.jpg', // relative to imageBaseUrl
deepLinkUrl: 'https://myapp.com/item/42',
);
Logger #
Logs can be written using static methods on the CkLogger class, or by using convenient global helper functions directly.
// Static class calls
CkLogger.info('App started', tag: 'APP');
CkLogger.warning('Low disk space', tag: 'SYS');
CkLogger.error('Request failed', tag: 'API');
CkLogger.debug('Payload: $payload', tag: 'API');
// Global helper shortcuts
ckInfo('App started', tag: 'APP');
ckWarning('Low disk space', tag: 'SYS');
ckError('Request failed', tag: 'API');
ckDebug('Payload: $payload', tag: 'API');
ckApiDebug('Payload: $payload', tag: 'API');
ckApiError('Request failed', tag: 'API');
ckScreen('Home loaded', tag: 'NAV');
Logs are enabled in debug mode by default (CkLogger.enableLogs).
Debouncer #
final _debouncer = CkDebouncer(milliseconds: 500);
void onQueryChanged(String q) {
_debouncer.run(() => search(q));
}
@override
void dispose() {
_debouncer.dispose();
super.dispose();
}
String / date extensions (extension.dart) #
// String casing
'hello world'.capitalize; // Hello World
'flutter'.capitalizeFirst; // Flutter
'hello world'.capitalizeEachWord(); // Hello World (alias)
'someEnumValue'.toDateTime; // DateTime? (parses ISO string)
// Enum display name
SomeEnum.myValue.displayName; // "My Value" (camelCase → spaced title)
// DateTime
DateTime.now().subtract(Duration(hours: 2)).ago; // "2h ago"
DateTime.now().time; // "2:30 PM"
DateTime.now().date; // "07-06-2026"
DateTime.now().dayName; // "Sat"
// TimeOfDay ↔ String
TimeOfDay.now().toHHmm(); // "14:30" — 24-hour, zero-padded
TimeOfDay.now().to12HourString(); // "2:30 PM"
'14:30'.toTimeOfDay24(); // TimeOfDay(hour: 14, minute: 30)
'2:30 PM'.toTimeOfDay12(); // TimeOfDay(hour: 14, minute: 30)
'14:30'.toTimeOfDayAuto(); // Smart detection (tries 24h first)
'2:30 PM'.tryToTimeOfDay(context); // TimeOfDay? — safe nullable version
Widget extensions (extension.dart) #
// Sizing helpers
20.height // SizedBox(height: 20.h)
16.width // SizedBox(width: 16.w)
// Alignment
Text('Hello').start // Align(alignment: Alignment.centerLeft, ...)
Text('Hello').end // Align(alignment: Alignment.centerRight, ...)
Text('Hello').center // Align(child: ...)
// Padding
Text('Hello').paddingAll(12)
Text('Hello').paddingSymmetric(horizontal: 16, vertical: 8)
Text('Hello').paddingOnly(left: 12, top: 4)
Text('Hello').paddingZero
// Margin
Text('Hello').marginAll(12)
Text('Hello').marginSymmetric(horizontal: 16, vertical: 8)
Text('Hello').marginOnly(left: 12, top: 4)
Text('Hello').marginZero
// CustomScrollView helpers
SomeWidget().sliverBox // SliverToBoxAdapter(child: ...)
// Global error boundary
() async {
await doSomething();
}.tryCatch(); // Logs stack trace via CkLogger on error, never throws
CkUtils #
CkUtils is a collection of static helpers for dividers, date calculations, and string/date formatting:
// 1. RepaintBoundary divider with outline color support
final divider = CkUtils.divider();
// 2. Date parsing and calculations
final date = CkUtils.parseDate('2026-05-31');
final birthDate = CkUtils.subtractYears(DateTime.now(), 18);
// 3. String & date formatting helpers
final hms = CkUtils.formatDateTimeToHms(DateTime.now()); // "14:30:00"
final duration = CkUtils.formatDurationToHms(const Duration(minutes: 75)); // "01:15:00"
final formattedTime = CkUtils.formatTime(DateTime.now()); // "2:30 PM"
final shortDate = CkUtils.formatDateToShortMonth(DateTime.now()); // "31 May 2026"
final doubleStr = CkUtils.formatDouble(3.1415); // "3.1"
Screenshot preview & spotlight #
CkScreenshotPreview(
buildContext: context,
imagePath: screenshotPath,
duration: const Duration(seconds: 3),
).show();
CkSpotlight(
center: const Offset(200, 400),
radius: 80,
color: Colors.black54,
)
License #
MIT — see LICENSE.
Author #
Km Muzahid — km.muzahid@gmail.com