layer_kit 1.1.1
layer_kit: ^1.1.1 copied to clipboard
A Flutter architecture package that combines Clean Architecture and MVVM patterns using Provider for state management.
LayerKit #
Where MVVM meets Clean Architecture
LayerKit is a powerful Flutter framework that combines the best aspects of the MVVM (Model-View-ViewModel) pattern with Clean Architecture principles to create a robust, maintainable, and scalable application structure.
[Layer Kit]
Features #
- Clean Project Structure - Organized folder structure that follows both MVVM and Clean Architecture principles
- Feature Generation - CLI tools to quickly scaffold new features with all necessary components
- Project Generation - Quickly bootstrap an entire project with best practices built-in
- Error Handling - Standardized exceptions and failures for consistent error management
- Configuration System - Flexible configuration for different environments
- Extension Methods - Utility extensions for common types to reduce boilerplate code
- UI Utilities - Helper methods for common UI tasks such as decorations
- Logging - Development-friendly logging system
Table of Contents #
- Installation
- Project Structure
- Getting Started
- Core Concepts
- Configuration
- Extensions
- Contributing
- License
Installation #
Add LayerKit to your pubspec.yaml:
dependencies:
layer_kit:
Run:
flutter pub get
Getting Started #
Generating a New Project #
Create a new Flutter project, then run:
⚠️ IMPORTANT: Run this command only immediately after creating a new project. If you already have files and folders inside your
libdirectory, you will lose them as this command overwrites thelibfolder with the package template. To avoid data loss, take a ZIP backup of yourlibfolder before running the command.
dart run layer_kit --project
This will generate a properly structured project with all necessary files and configurations.
Generating a New Feature #
To add a new feature to your project:
⚠️ NOTE: Run this command only if you have created your project structure using LayerKit. Running it in projects without the LayerKit structure may not work as expected.
dart run layer_kit --feature feature_name
This command will:
- Create a feature directory in the
srcfolder - Generate all necessary files following MVVM and Clean Architecture principles
- Update routes to include the new feature
Project Structure #
LayerKit organizes your project into a well-defined structure:
lib/
├── config/ # Configuration files
│ ├── data/ # Data-related configuration
│ ├── lang/ # Localization
│ ├── routes/ # Routing
│ └── theme/ # Theme configuration
├── core/ # Core components
│ ├── callbacks/ # Callback interfaces
│ ├── common/ # Common utilities
│ │ └── widgets/ # Reusable widgets
│ ├── constants/ # Constants
│ ├── extensions/ # Extension methods
│ ├── helper/ # Helper methods
│ ├── network/ # Network service
│ └── utils/ # Utilities
├── src/ # Features
│ ├── feature1/ # Feature module
│ │ ├── datasource/ # Data sources
│ │ │ ├── models/ # Data models
│ │ │ │ ├── body_models/ # UI display models (used in screens)
│ │ │ │ ├── requests/ # API request models
│ │ │ │ └── response/ # API response models
│ │ │ └── repo/ # Repository implementation
│ │ ├── providers/ # Feature providers
│ │ └── screens/ # UI screens
│ └── feature2/ # Another feature module
├── defaults.dart # Default values used across the app
├── di_container.dart # Dependency Injection imports (part of di main)
├── di_container.main.dart # DI main file - injection of all dependencies (using 'Get It')
└── main.dart # Entry point
Configuration #
LayerKit provides a flexible configuration system:
// main.dart
await AppLocalization.init();
await AppTheme.init();
runApp(AppLocalization(
child: AppTheme(
child: MyApp(),
),
));
/// ...code....
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
// DeviceOrientation.landscapeRight,
// DeviceOrientation.landscapeLeft,
]);
/// FOR THEME
ThemeConfig.init(context);
/// FOR ROUTING (SEE https://pub.flutter-io.cn/packages/flutter_easy_routing)
RouteConfig.setDefaultTransition(TransitionType.fade);
return ToastificationWrapper(
child: MaterialApp(
title: AppConsts.appName,
navigatorKey: AppRouter.navigatorKey,
debugShowCheckedModeBanner: false,
onGenerateRoute: (s) => AppRouter.generateRoute(s, SplashScreen()),
scrollBehavior: const StretchScrollBehavior(),
initialRoute: Routes.splash.path,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
theme: context.theme.currentTheme,
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
child: child ?? SizedBox(),
);
},
),
);
}
/// ...code....
Core Concepts #
MVVM + Clean Architecture #
LayerKit combines the MVVM pattern with Clean Architecture principles:
- View - Flutter UI components (screens, widgets)
- ViewModel - Business logic and state management
- Model - Data models and repository interfaces
With additional Clean Architecture layers:
- Repositories - Abstract data source interactions and implementations that access data models
- Data Sources - Concrete implementations of data access including models, requests, and responses
ViewModels #
ViewModels in LayerKit extend the BaseViewModel class:
class MovieProvider extends BaseViewModel {
final MovieRepo _movieRepo;
final NetworkService _networkService;
MovieProvider({required MovieRepo movieRepo, required NetworkService networkService})
: _movieRepo = movieRepo,
_networkService = networkService;
Future<bool> getData({
required String data1,
required String data2,
bool listen = true,
}) async {
setLoading();
final isNetwork = await _networkService.isConnected;
final isSuccess = await apiCallback(
name: "getData",
isNetwork: isNetwork,
doWhenOnline: () async {
final req = MovieReq(data1: data1, data2: data2);
final res = await _movieRepo.getData(req);
showSnackbar(res.message);
setLoaded();
return res.status;
},
errorListener: (e) {
setError(e.toString());
},
);
if (listen) notifyListeners();
return isSuccess;
}
}
Repositories #
Repositories abstract data sources:
abstract interface class MovieRepo {
Future<MovieResponse> getData(MovieReq req);
}
class MovieRepoImpl implements MovieRepo {
final DioClient _dioClient;
MovieRepoImpl({required DioClient dioClient}) : _dioClient = dioClient;
@override
Future<MovieResponse> getData(MovieReq req) async {
return await repoCallback<MovieResponse>(
name: "getData",
callback: () async {
final res = await _dioClient.post(Apis.getData, data: req.toJson());
return MovieResponse.fromJson(res.data ?? {});
},
);
}
}
Theme Configuration #
- All theme configuration is located under the path:
lib/config/theme - LayerKit includes
AppColorswhere you can define your app colors andthemeColors(of typeThemeColorsModel()) where you can define your dynamic theme colors - By default, LayerKit loads the user's device system theme
- There is also a
ThemeSelectorDropdownwidget that provides a UI to change theme colors (not dark/light mode; this is used for multiple themes. For dark and light mode, usecontext.toggleThemeMode();theme extensions)
Size Configuration #
- Dynamic width, height, and radius values
- These are all extensions on
numthat returndoublevalues .h-> dynamic height10.h= 10% of screen height
.w-> dynamic width10.w= 10% of screen width
.t-> dynamic value for text sizing.r-> dynamic radius used for border radius, equals height and width (like for square images)
Environment Types (EnvType) #
- This enum helps you change variables by environment
- Set
EnvTypein the Defaults [D] class and add conditions based onenvType - For example:
enum EnvType {
development, // normal debug and development
developmentWithPrint, // useful to print logs in running release app
production, // set this for play store deploy bundle
;
bool get isDevelopment => this == EnvType.development;
bool get isDevelopmentWithPrint => this == EnvType.developmentWithPrint;
bool get isProduction => this == EnvType.production;
}
- Now you can use the
envTypevariable like:
static const stagingBaseUrl = "https://staging-example.com";
static const liveBaseUrl = "https://example.com";
static String get baseUrl => D.envType.isProduction ? liveBaseUrl : stagingBaseUrl;
Theme Atoms #
See folder: lib/config/theme/atoms
Useful for maintaining consistent layouts
Customize as per your needs
- Text:
lib/config/theme/atoms/text.dart
/// How to use text instead of Text widget
"This is text".regular16.build(
textAlign: TextAlign.start,
maxLines: 20,
fontSize: 1.4.t,
color: textColor ?? Colors.white,
fontWeight: FontWeight.w500,
)
/// Available types:
// titleX
// title32
// title24
// title18
// regular16
// regular14
// small13
// tiny12
// appbar
// button
- Gap:
lib/config/theme/atoms/padding.dart - Also includes padding extensions for ease of coding
Gap.size16.paddingHorizontal
(4.w, 1.h).paddingHV
- VGap/HGap Spacing widgets:
lib/config/theme/atoms/spacing.dart - Use these instead of SizedBox for spacing in columns and rows (UI)
VGap.default1,
VGap(5), // This equals SizedBox(height: 5.h); here .h is an extension used for dynamic height (5% of screen height)
- Theme Extensions - Some theme extensions:
// Basically used to toggle theme mode (light to dark and vice versa)
context.toggleThemeMode();
// Reset theme mode to system mode
context.resetThemeMode();
// This theme colors index is used for multiple themes in the app
// There is a list of themes, users can select their favorite theme
// We store the index of this theme and save it
final index = themeColorsOptions.indexOf(value);
final i = index >= 0 ? index : null;
context.setThemeColorsIndex(i);
Localizations (Multiple Languages in App) #
- Wrap the MainApp widget with
AppLocalization - Localization configuration is located under the path:
lib/config/lang - There is also a
LangSelectorDropdownwidget that provides a UI to change language
Local JSON Files #
- Requires
<lang-code>.jsonfiles - Folder path:
lib/config/lang/jsons(if you change this path, also change the path in pubspec.yaml file) - Add a list of
LanguageModelin thelanguages.dartfile - If you don't have an API for localization and the app depends on local JSON files, then remove the
lang_from_apifolder and related errors (as we set up for API now, they might throw errors)
// languages.dart
List<LanguageModel> languages = [
LanguageModel(code: "en", language: "English", displayName: "English", image: "🇬🇧"),
LanguageModel(code: "de", language: "German", displayName: "Deutsch", image: "🇩🇪"),
];
Localizations from API (for Dynamic Changes) #
- Requires
TranslationApiModeltype of response (see the toJson method of this class; however, you can change it as per your response) - Folder path:
lib/config/lang/src/lang_from_api(this is a dummy API explanation; modify as per your requirements) - For temporary checking, enable/disable the
fetchLanguagesFromApivariable in theDummyLangApiclass - If you don't have an API for localization and the app depends on local JSON files, then remove the
lang_from_apifolder and related errors (as we set up for API now, they might throw errors)
Routing #
- We use
flutter_easy_routing(https://pub.flutter-io.cn/packages/flutter_easy_routing) for easy routing in the app - See this on pub.flutter-io.cn: https://pub.flutter-io.cn/packages/flutter_easy_routing
Callbacks #
-
LayerKit contains callbacks that make lengthy functions shorter and improve logging
-
Examples of callbacks:
widgetBinding((_) {
//code
})
onDebugOnly((){
// code
});
onTap: () async => await safeRun(
name: "Feature Button",
isEnabled: true,
logEnabled: true,
tryBlock: () {
/// TODO: implement onClick logic
showSnackbar("Feature Button Clicked");
devlog("Feature Button Clicked");
},
errorHandler: (e) {
/// TODO: handle error
devlogError("Error occurred on Feature Button click - $e");
},
)),
- Other callbacks used in API calling in repo
repoCallbackand providerapiCallback(main purpose is logging and identifying which API has errors)
Extensions #
LayerKit includes several useful extensions:
// Border radius extension
D.defaultRadius.borderRadiusCircular
20.borderRadiusTop
20.borderRadiusBottom
20.borderRadiusRight
// String casing
"hello world".toTitleCase // "Hello World"
"example".firstUpperCased // "Example"
// Time formatting
120.toMMSS // "02 : 00"
3725.toHHMMSS // "01 : 02 : 05"
See extensions under the core folder for more.
License #
LayerKit is available under the MIT license. See the LICENSE file for more info.
Created by Nayan Parmar © 2025