flutter_multi_app_core 1.0.0
flutter_multi_app_core: ^1.0.0 copied to clipboard
A shared Flutter package providing configurable theme, reusable widgets, network client, storage, and utilities for multi-app projects.
app_core #
A shared Flutter package providing configurable theme, reusable widgets, network client, secure storage, and utilities — designed to be consumed by multiple apps from a single codebase.
Installation #
In your app's pubspec.yaml:
dependencies:
app_core:
path: ../app_core # local path
# or via git:
# git:
# url: https://github.com/your-org/app_core.git
Then import everything with one line:
import 'package:app_core/app_core.dart';
Theme #
Basic setup #
MaterialApp(
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
);
Custom brand colors (recommended per app) #
Only primary and secondary are required. Everything else has sensible defaults.
final scheme = AppColorScheme(
primary: Color(0xFFFF6D00),
secondary: Color(0xFF00BFA5),
);
MaterialApp(
theme: AppTheme.light(scheme),
darkTheme: AppTheme.dark(scheme),
);
Full customisation #
final scheme = AppColorScheme(
primary: Color(0xFF4A148C),
onPrimary: Colors.white,
secondary: Color(0xFF00C853),
onSecondary: Colors.black, // if secondary is light
error: Color(0xFFB71C1C),
warning: Color(0xFFF57F17),
info: Color(0xFF0288D1),
success: Color(0xFF2E7D32), // defaults to secondary if omitted
);
Accessing extra theme tokens in widgets #
// success, warning, info, divider colors
final ext = context.appTheme;
Container(color: ext.success);
Divider(color: ext.divider);
Widgets #
Buttons #
AppButton(label: 'Submit', onPressed: _submit)
AppButton(label: 'Cancel', onPressed: _cancel, variant: AppButtonVariant.secondary)
AppButton(label: 'Delete', onPressed: _delete, variant: AppButtonVariant.text)
AppButton(label: 'Loading', onPressed: null, isLoading: true)
AppButton(label: 'Icon', onPressed: _go, icon: Icon(Icons.send), size: AppButtonSize.small)
AppIconButton(icon: Icon(Icons.edit), onPressed: _edit)
AppIconButton(icon: Icon(Icons.delete), onPressed: _delete, variant: AppIconButtonVariant.filled)
Fields #
// Standard text field
AppTextField(
label: 'Email',
prefixIcon: Icon(Icons.email),
validator: Validators.compose([Validators.required, Validators.email]),
)
// Password field — visibility toggle built in
AppPasswordField(
label: 'Password',
validator: (v) => Validators.minLength(v, 8),
)
// Search field — clear button built in
AppSearchField(
hint: 'Search products...',
onChanged: (v) => bloc.add(SearchChanged(v)),
onSearch: (v) => bloc.add(SearchSubmitted(v)),
)
Dropdown #
AppDropdown<String>(
label: 'Country',
value: _selected,
items: ['US', 'UK', 'AU'].map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
onChanged: (v) => setState(() => _selected = v),
)
Card #
AppCard(
header: Text('Title', style: context.textTheme.titleMedium),
footer: AppButton(label: 'Action', onPressed: _action),
onTap: _onTap,
child: Text('Card body content'),
)
Dialog #
AppDialog.show(
context,
title: 'Delete item?',
content: Text('This cannot be undone.'),
confirmLabel: 'Delete',
isDestructive: true,
onConfirm: _delete,
);
Bottom Sheet #
AppBottomSheet.show(
context,
title: 'Options',
child: Column(children: [...]),
);
States #
AppStateWidget.empty(message: 'No results found')
AppStateWidget.error(onRetry: _reload)
AppStateWidget.noInternet(onRetry: _reload)
Loaders #
AppLoader() // circular
AppShimmer(child: YourWidget()) // shimmer effect
Network #
final client = ApiClient.create(
baseUrl: 'https://api.example.com',
tokenProvider: () => storage.read(AppConstants.keyAccessToken),
tokenRefresher: () => authService.refresh(),
enableLogging: kDebugMode,
);
// All calls return ApiResponse<T> — no try/catch needed
final response = await client.get(
'/users/me',
fromJson: (data) => User.fromJson(data),
);
response.when(
success: (user) => print(user.name),
failure: (e) => print(e.message),
);
Network connectivity #
final networkInfo = NetworkInfoImpl(Connectivity());
if (await networkInfo.isConnected) { ... }
Storage #
Wraps flutter_secure_storage — values are encrypted on device.
final storage = AppStorage();
await storage.write(AppConstants.keyAccessToken, token);
final token = await storage.read(AppConstants.keyAccessToken);
await storage.delete(AppConstants.keyAccessToken);
await storage.deleteAll();
Validators #
// Single
AppTextField(validator: Validators.required)
AppTextField(validator: Validators.email)
// Composed — first error wins
AppTextField(
validator: Validators.compose([
Validators.required,
Validators.email,
]),
)
Extensions #
// String
'hello world'.titleCase // 'Hello World'
'test@x.com'.isEmail // true
' '.isBlank // true
// BuildContext
context.colors.primary
context.textTheme.bodyLarge
context.isDark
context.screenWidth
context.showSnackBar('Saved!', isError: false)
// Num spacing
16.verticalSpace // SizedBox(height: 16)
8.horizontalSpace // SizedBox(width: 8)
16.allPadding // EdgeInsets.all(16)
Constants #
AppConstants.keyAccessToken // 'access_token'
AppConstants.keyRefreshToken // 'refresh_token'
AppConstants.defaultPageSize // 20
AppConstants.animNormal // Duration(milliseconds: 300)
What each app needs to do #
- Add
app_coretopubspec.yaml - Create an
AppColorSchemewith your brandprimaryandsecondary - Pass it to
AppTheme.light(scheme)andAppTheme.dark(scheme) - Create an
ApiClientwith yourbaseUrland token callbacks - That's it — all widgets, utils, and network are ready to use