This repository provides a comprehensive boilerplate template for building Flutter applications with all the essential features pre-configured. Whether you are a beginner or an experienced Flutter developer, this template aims to kickstart your project with a solid foundation, following best practices and industry standards.
-
State Management using Flutter Bloc: Flutter Bloc is a robust state management library that helps you manage the flow of data in your application. This boilerplate template utilizes the Bloc pattern, which separates the UI from the business logic, resulting in more organized and testable code. With Flutter Bloc, you can handle complex state changes, such as loading, success, and error states, efficiently. The template provides examples of how to create and use Blocs, making it easier for you to handle application state seamlessly. But the boilerplate itself does not force developers to use bloc. There are opportunities to use any kind of state management.
-
Localization Support using json Files: Providing a localized experience to your app users is crucial for a global audience. This boilerplate template provides localization support using json files using slang. The json files allow you to store translations in key-value pairs for different locales. With the provided json files, you can easily add, update, or remove translations for various languages, enabling a personalized user experience. Our boilerplate provides abstraction for localization. So if someone does not want to use slang for localization, there are opportunities to implement a different localization and use it in the app. The main app will not know the difference.
-
Network Calls with Dio: Networking is an integral part of modern applications, and Dio is a powerful HTTP client for making network calls. The boilerplate template integrates Dio, allowing you to interact with RESTful APIs efficiently. It demonstrates best practices for handling network requests, such as error handling, request cancellation, and API client creation. With Dio, you can build robust and reliable apps that communicate with backend services seamlessly. Our boilerplate provides abstraction so that if someone does not want to use
dio
, they can implement their own network calls by implementing the abstraction. -
Local Storage with Shared Preferences: Local storage is essential for saving small amounts of data locally in the app. The boilerplate template uses the Shared Preferences plugin to handle local storage operations efficiently. You can store user preferences, settings, or cached data locally, improving app performance and user experience. In the boilerplate, abstraction provided to implement any other kind of mechanism for storing data locally.
-
Dependency Injection using GetIt: Managing dependencies is essential for writing clean, testable, and maintainable code. This template incorporates GetIt, a simple yet powerful service locator, to manage dependencies effectively. With GetIt, you can access dependencies throughout the app, decoupling the code and making it easier to swap implementations during testing or future updates.
-
Clean Architecture: Clean Architecture is an architectural pattern that emphasizes separation of concerns and layers in your codebase. The boilerplate template follows Clean Architecture principles, which include separating the app into three main layers:
data
,domain
, andpresentation
. Thedata
layer handles data sources and repositories, thedomain
layer contains business logic and entities, while thepresentation
layer handles the UI and user interactions. Clean Architecture promotes code reusability, maintainability, and testability, making your app scalable and easier to maintain as it grows. -
Logger and Tracking manager The logger provides functionality to record and display various levels of messages (debug, info, warning, error) during the execution the application. It helps in monitoring application behavior and diagnosing issues. Our boilerplate provides both abstraction and implementation using
logging
library. But if someone wants to implement custom logger then there are oppertunity to do so. The tracking manager facilitates integration with analytics services to monitor user interactions, events, and performance metrics within the app. It supports tracking custom events and parameters to gain insights into user behavior. Our boilerplate provides both abstraction and implementation withsentry tracking manager
with the help of ourlogger
library. If there are need to use any other proccesses, please implement the abstraction. -
Theme & Styling In mobile apps, a theme dictates the overall visual style, including colors, typography, and design elements, ensuring a consistent and appealing user interface that reflects the app's brand and enhances user experience. Our boilerplate provide abstraction to incorporate custom theme by providing
colorScheme
orseedColor
. It also comes with widgets and blocs for incorporating theme changes throughout the application. In Styles, We can define how visual attributes like fonts, colors, and spacing are applied across widgets, ensuring consistent and customizable UI designs throughout the project. -
Alerts & Loading In a mobile application, Alerts and loadings are a common features to have. It is essential to have consistant alert and loading throughout the application. In our boilerplate, we provide abstraction of many types of alerts, there is also abstraction for loading. When using, implement the needed alerts and loader to use throught the application.
-
Utilities, extensions & validators Utilities, extensions and validators make our life as a developer much easier to develop applications. In our boilerplate, we tried to create an idea on where to define these features and how to use them consistantly throughout the application.
-
Common Services When developing using microfrontend architecture, there can be a need to use same classes, entities, layout, widgets throughout all the micro apps. In common services library, we tried to incorporate those requirements. As we are using clean architecture, there can be need of common models and abstract classes in
data
layer, common entities and abstraction for use cases in thedomain
layer. There can be a need for common layouts and common widget gallery forpresentation
layer. In common services, we tried to give an idea on how we can structure them and use them consistently throughout all the micro frontends. -
Build Environments In full development lifecycle, there can be a need to have multiple build enviroments in multiple stages. There can be a need for
development
environment for developing stages of the application, a need forstaging
environment for UAT and a need forproduction
enviroment for rolling out the application. In our boilerplate, we tried to incorporate these environment and flavours to use in different stages of the development lifecycle. Of course there are opportunities to incorporate other enviroment if needed through provided abstraction. -
Routing and navigation Routing and navigation is an essential feature to have in application development. We used
go_router
, A declarative routing package for Flutter that uses the Router API to provide a convenient, url-based API for navigating between different screens. You can define URL patterns, navigate using a URL, handle deep links, and a number of other navigation-related scenarios. In microfrontend development, we can separate navigation between different micro apps throughNavigator
widget androuteDelegate
ingo_router
to generate different route.
To start using this boilerplate template, follow these steps:
-
Clone the Repository: Clone this repository to your local development environment using Git.
-
Install Dependencies: Ensure you have Flutter and Dart SDK installed on your machine. Run
flutter doctor
to verify the installation. Then, install the required dependencies usingflutter pub get
. -
Explore and Customize: Familiarize yourself with the project structure and files. Customize the template according to your project requirements, such as renaming files, adding Blocs, creating API clients, registering dependencies, and updating localization files for different locales.
-
Build Your App: Leverage the provided features and architecture to build your Flutter app efficiently. Utilize the state management, localization, error handling, network calls, local storage, and dependency injection features to deliver a high-quality and user-friendly application.
Now lets delve deeply into the usage of our developed common microfrontends. Lets discuss how we can use them in our applications and incorporate them consistently.
We have an abstract Alert
class, which has initially success
, error
, warning
, info
and confirmation
functionalities. Please implement those functionalities per your requirement needs. Feel free to add other alerts here as per your need.
class SuccessAlert extends Alert {
final BuildContext context;
final String title;
final String content;
final String confirmText;
final VoidCallback onConfirm;
const SuccessAlert({
required this.context,
required this.title,
required this.content,
this.confirmText = "OK",
required this.onConfirm,
});
@override
void show() {
throw UnimplementedError();
}
}
Also we have an abstract Loading
class with show
and hide
functionalilites. Feel free to add the implementation as per your requirements and needs.
class ShowLoading extends Loading {
final BuildContext context;
String message;
ShowLoading({
required this.context,
this.message = "Loading...",
});
@override
void execute() {
throw UnimplementedError();
}
}
Api Base library provides abstract ApiBase
class with get
, post
, path
, delete
functionalities. It is currently implemented using dio in the abstract class DioApiBase
. Please implement them for each of your apis.
class LoginApi
extends DioApiBase<LoginRequest, AuthTokenResponse, AuthErrorResponse> {
LoginApi() : super(Links.loginUrl);
@override
AuthErrorResponse mapError({String? message, Map<String, dynamic>? json}) {
throw UnimplementedError();
}
@override
AuthTokenResponse mapResponse(Map<String, dynamic> json) {
throw UnimplementedError();
}
@override
Map<String, String> get headers => throw UnimplementedError();
}
DioApiBase
uses 2 abstract class ApiRequest
and ApiResponse
to get request data and send response. When using please implement them as well
class LoginRequest extends ApiRequest {
final String accountCode;
final String password;
LoginRequest(this.accountCode, this.password);
@override
Map<String, dynamic> toJson() {
return {
"accountCode": accountCode,
"password": password,
};
}
}
class AuthTokenResponse extends ApiResponse {
final String accessToken;
final String refreshToken;
AuthTokenResponse(
this.accessToken,
this.refreshToken,
);
factory AuthTokenResponse.fromJson(Map<String, dynamic> json) {
return AuthTokenResponse(
json["accessToken"],
json["refreshToken"],
);
}
AuthTokens toEntity() {
return AuthTokens(accessToken: accessToken, refreshToken: refreshToken);
}
}
abstract class AuthErrorResponse<T> extends ApiResponse {
T toEntity();
}
When calling the api, it may be used as
Either<AuthTokenResponse, AuthErrorResponse> response =
await LoginApi().post(request);
If it is used like this, then every api will return a response or an error type safely
BiometricAuth
is a small but helpful library that can provide biometric authentication feature easily. It is currently implemented using local_auth
in the LocalAuthImpl
class. If needed, it can be implemented using other library as well by implementing the abstract BiometricAuth
class
abstract class BiometricAuth{
Future<bool> canCheckBiometric();
Future<bool> authenticateWithBiometric();
}
local_storage
library is used for saving data locally. It provides abstract LocalStorageBase
class for basic usecase of saving data locally. There is also an implementation using shared_preference
is provided in the SharedPreferenceBase
class.If there is a need for other library to be used, please provide implementation of LocalStorageBase
class. There is also an abstract class BaseLocalData
for saving data. Please extend this class to the data you want to save
class TokensLocalApi extends SharedPreferenceBase<AuthTokenLocalData> {
TokensLocalApi(super.preference);
@override
AuthTokenLocalData mapResponse(Map<String, dynamic> json) {
return AuthTokenLocalData.fromJson(json);
}
@override
String get key => ConstantStrings.tokenKey;
}
// the local data to be saved
class AuthTokenLocalData extends BaseLocalData<AuthTokens> {
final String accessToken;
final String refreshToken;
AuthTokenLocalData(
this.accessToken,
this.refreshToken,
);
factory AuthTokenLocalData.fromJson(Map<String, dynamic> json) {
return AuthTokenLocalData(json["accessToken"], json["refreshToken"]);
}
@override
AuthTokens toEntity() {
return AuthTokens(accessToken: accessToken, refreshToken: refreshToken);
}
@override
Map<String, dynamic> toJson() {
return {"accessToken": accessToken, "refreshToken": refreshToken};
}
}
For localization, there is an abstract class LocalizationBase
for managing localization. Currently in our boilerplate, we used slang
to implement the LocalizationBase
in the SlangLocalizationImpl
class. If you want to use other implementation for localization, please implement the LocalizationBase
class.
abstract class LocalizationBase<LocaleType, TranslationType> {
void initializeLocalization(String languageCode);
Widget localizationScope({required Widget child});
Locale currentLocale(BuildContext context);
List<Locale> get supportedLocale;
List<LocalizationsDelegate<void>> get localizationsDelegates;
void changeLocale(LocaleType locale);
TranslationType text(BuildContext context);
}
For logging there is an abstract class LoggerBase
for managing logging.
/// Logs the error to the console
void error(Object message, {Object? error, StackTrace? stackTrace});
/// Logs the warning to the console
void warning(Object message);
/// Logs the info to the console
void info(Object message);
/// Logs the debug to the console
void debug(Object message);
/// Logs the verbose to the console
void verbose(Object message);
In our boilerplate, LoggerBase
is implemented using logging
library in the Logger
class.
For tracking errors and warning, there is an abstract class called ErrorTrackingManagerBase
. In our boilerplate, We implemented ErrorTrackingManagerBase
using sentry
in the SentryTrackingManager
. Please implement ErrorTrackingManagerBase
if you want to use other custom implementation yourself.
For theme we have provided abstract ThemeBase
class. By implementing this class we can provide seedColor
or colorScheme
along with themeMode
generate AppTheme
. AppTheme
provides themeData
to be used in the application.
```dart
final class ThemeImpl extends ThemeBase {
final ThemeMode mode;
ThemeImpl({
required this.mode,
});
@override
ThemeMode get themeMode => mode;
@override
// TODO: implement colorScheme
ColorScheme? get colorScheme => throw UnimplementedError();
}
```
It also provides ThemeCubit
for handling theme change through ThemeScope
widget
In the main app level, everything is put into place to use in the application. In the application level AppStorage
class is provided to store dependencies such as other microfrontends. Feel free to add your microfrontend in the AppStorage
.
///[AppStorage] holds [themeCubit], [errorTrackingManager], [localization] to use in everywhere
///It can also be used to feed dependencies across the application
///If there are need for any other dependencies to be need. Please incorporate it in [AppStorage]
class AppStorage {
AppStorage({
required this.themeCubit,
required this.errorTrackingManager,
required this.localization,
});
final ThemeCubit themeCubit;
final ErrorTrackingManager errorTrackingManager;
final LocalizationBase localization;
}
DefaultAssetBundle(
bundle: SentryAssetBundle(),
child: RepositoryProvider<AppStorage>(
create: (context) => widget.appStorage,
child: ThemeScope(
themeCubit: widget.appStorage.themeCubit,
child: widget.appStorage.localization.localizationScope(
child: const AppContext(),
),
),
),
)
class AppContext extends StatelessWidget {
const AppContext({super.key});
// This global key is needed for [MaterialApp]
// to work properly when Widgets Inspector is enabled.
static final _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
final ThemeBase theme = ThemeScope.of(context);
final AppStorage appStorage = RepositoryProvider.of(context);
return MaterialApp.router(
theme: theme.themeData,
themeMode: theme.themeMode,
routerConfig: RouteGenerator.router,
locale: appStorage.localization.currentLocale(context),
supportedLocales: appStorage.localization.supportedLocale,
localizationsDelegates: appStorage.localization.localizationsDelegates,
// TODO: You may want to override the default text scaling behavior.
builder: (context, child) => MediaQuery.withClampedTextScaling(
key: _globalKey,
minScaleFactor: 1.0,
maxScaleFactor: 2.0,
child: child!,
),
);
}
}
- Fix bug #1
- Write unit tests
- Add documentation
- Update README.md
- Generate API documentation
- Conduct code review
- Address feedback from code review
- Perform integration testing
- Release version 1.0.0
We welcome contributions to this boilerplate template. If you find any issues, have suggestions for improvements, or want to add new features, feel free to raise an issue or submit a pull request. Your contributions will help the Flutter community benefit from this template and build better apps.
This boilerplate template is provided by Brain Station 23 PLC, a leading technology company known for its expertise in software development. We thank Brain Station 23 Ltd for creating and sharing this template with the Flutter community, helping developers get started with robust and feature-rich Flutter applications.
Happy coding!