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.
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
lib
directory, you will lose them as this command overwrites thelib
folder with the package template. To avoid data loss, take a ZIP backup of yourlib
folder 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
src
folder - 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.dev/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
AppColors
where 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
ThemeSelectorDropdown
widget 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
num
that returndouble
values .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
EnvType
in 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
envType
variable 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
LangSelectorDropdown
widget that provides a UI to change language
Local JSON Files
- Requires
<lang-code>.json
files - Folder path:
lib/config/lang/jsons
(if you change this path, also change the path in pubspec.yaml file) - Add a list of
LanguageModel
in thelanguages.dart
file - If you don't have an API for localization and the app depends on local JSON files, then remove the
lang_from_api
folder 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
TranslationApiModel
type 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
fetchLanguagesFromApi
variable in theDummyLangApi
class - If you don't have an API for localization and the app depends on local JSON files, then remove the
lang_from_api
folder and related errors (as we set up for API now, they might throw errors)
Routing
- We use
flutter_easy_routing
(https://pub.dev/packages/flutter_easy_routing) for easy routing in the app - See this on pub.dev: https://pub.dev/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
repoCallback
and 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
Libraries
- core/base/base_viewmodel
- core/error/exceptions
- core/error/failures
- core/extensions/extensions
- core/extensions/int_time_formatting_extension
- core/extensions/int_util_extensions
- core/extensions/string_casing_extension
- core/extensions/widget_util_extensions
- core/utils/http_overrides
- core/utils/layerkit_config_provider
- core/utils/layerkit_initializer
- core/utils/stretch_scroll_behaviour
- core/utils/utils
- generator/feature_generator
- generator/project_generator
- layer_kit