The world's leading Sports Digital Asset Exchange, focused on Sports, SportsFi, Athletes, and Athlete performance.
Learn more at our website: AthleteX.io
Layering our code is very important and helps us iterate quickly and with confidence. Each layer has a single responsibility and can be used and tested in isolation. This allows us to keep changes contained to a specific layer in order to minimize the impact on the entire application.
For example:
This layer is the lowest layer and is responsible for retrieving raw data from external sources(database, REST API, GraphQL backend) or device APIs. Usually packages in this layer will expose clients
(e.g.: storage_client
, location_client
, auth0_api_client
, etc...).
Note: This layer can be considered the "engineering" layer because it focuses on how to process/transform data in a performant way.
This layer is a compositional layer meaning that it composes one or more data clients and applies "business rules" to the data. We call each component in this layer a repository
(e.g.: user_repository
, weather_repository
, payments_repository
, etc...).
Note: This layer can be considered the "product" layer. The business/product owner will determine the rules/acceptance criteria for how to combine data from one or more data providers into a unit that brings value to the customer.
This layer composes one or more repositories and contains logic for how to surface the business rules via a specific feature or use-case. This layer uses the bloc package to manage the logic associated with each feature. We call each component in this layer a bloc
(e.g.: login_bloc
, weather_forecast_bloc
, settings_bloc
, etc...).
Note: This layer can be considered the "feature" layer. Design and product will determine the rules for how a particular feature will function.
This layer takes the state from the business logic layer and renders a UI for the customer to interact with. This layer uses the flutter_bloc package to render widgets based on the bloc's state and to allow the user to interact with the bloc through events.
Note: This layer can be considered the "design" layer. Designers will determine the user interface in order to provide the best possible experience for the customer.
The project should adhere to the Multimodule Monorepo structure. This is an approach that compliments the layered architecture described above. It allows you to maintain a single repository(git) with multiple submodules. In some cases, data clients can be open-sourced and may eventually not be included in the project. Several benefits of maintaining a single project with multiple submodules are:
The application should use a feature-driven directory structure. This project structure enables us to scale the project by having self-contained features and allows developers to work on different features in parallel.
Developers should make use of barrel files to export necessary files from any directory(except for bloc
/cubit
which are generated using vscode bloc extensions and make use of part
and part of
).
An example of a Multimodule Monorepo directory structure is below:
βββ lib
β βββ app
β β βββ bloc
β β βββ extensions
β β βββ models
β β βββ view
β βββ home
β β βββ view
β βββ l10n
β β βββ arb
β βββ login
β β βββ bloc
β β βββ view
β β βββ widgets
β βββ sign_up
β β βββ bloc
β β βββ view
β β βββ widgets
βββ packages
β βββ meta_weather_api_client
β βββ user_repository
β βββ weather_repository
Code formatters fix style, spacing, line jumps, comments, which helps enforce programming and formatting rules that can be easily automated. This helps reduce future code diffs by delegating formatting concerns to an automatic tool rather than individual developers.
flutter format --set-exit-if-changed lib test
Code linters analyze code statically to flag programming errors, catch bugs, stylistic errors, and suspicious constructs.
flutter analyze lib test
analysis_options.yaml
file including the very_good_analysis
.///
) should be used for all public members and public APIs; enforcing is done by very_good_analysis through public_member_api_docs and package_api_docs lint rules.
Note: Additional comments should be added when the code itself is not clear enough or presents high levels of complexity.$ flutter test --coverage --test-randomize-ordering-seed random
globs
to describe file patterns) and to use a minimum coverage percentage threshold.Note: It's a good practice to aim at a code coverage as close to 100% as possible. Files/folders deemed to be unimportant can be excluded from the coverage so they don't affect it.
The project should contain an application package as a submodule having the role of an UI toolkit. This package is usually named app_ui
or {appName}_ui
and should contain the assets
folder, reusable widgets, UI related helpers and classes for layout, navigation, platform, typography, theme, colors, etc...
An example of a class storing colors is below:
abstract class AppColors {
static const Color black = Color(0xFF202124);
static const Color white = Color(0xFFFFFFFF);
}
An example of the app_ui
directory structure is below:
βββ lib
β βββ app
β βββ l10n
βββ packages
β βββ app_ui
β β βββ assets
β β β βββ fonts
β β β βββ images
β β βββ lib
β β β βββ src
β β β β βββ helpers
β β β β βββ layout
β β β β βββ navigation
β β β β βββ platform
β β β β βββ theme
β β β β βββ widgets
β βββ meta_weather_api_client
β βββ user_repository
β βββ weather_repository
very_good_cli can be used to easily create dart/flutter packages or even a full flutter application.
Note: Colors inside the app should be configured as much as possible through ColorScheme
.
This project should rely on flutter_localizations and follow the official internationalization guide for Flutter. This approach is recommended even if there's only a locale needed.
app_en.arb
file at lib/l10n/arb/app_en.arb
.{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"helloWorld": "Hello World",
"@helloWorld": {
"description": "Hello World Text"
}
}
import 'package:mindspotter/l10n/l10n.dart';
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(l10n.helloWorld);
}
Update the CFBundleLocalizations
array in the Info.plist
at ios/Runner/Info.plist
to include the new locale.
...
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
...
lib/l10n/arb
.βββ l10n
β βββ arb
β β βββ app_en.arb
β β βββ app_es.arb
.arb
file:app_en.arb
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
app_es.arb
{
"@@locale": "es",
"counterAppBarTitle": "Contador",
"@counterAppBarTitle": {
"description": "Texto mostrado en la AppBar de la pΓ‘gina del contador"
}
}