Bonjour! Ce document contient mes recommandations personnelles pour les politiques, les procédures et les normes concernant la façon dont le développement doit être mené.
Le code du projet doit être structuré avec un mélange de dossier par fonction et de dossier par type .
Signification au niveau racine dont vous avez des dossiers définissant les fonctionnalités. Par exemple:
|-- common/
|-- authentication/
|-- settings/
La fonction Common / Core contient des classes, des fonctions ou des widgets qui, par défaut, doivent être disponibles dans toutes les fonctionnalités.
L'idée est donc d'avoir des fonctionnalités de votre application divisées par des dossiers de haut niveau.
Considérez tous les dossiers de haut niveau comme un package de fléchettes / flutter distinct, des dépendances ou une limite d'accès de votre application.
Donc, si vous ajoutez quelque chose au dossier commun, vous devez vous demander, 99.% Les fonctionnalités de notre application en auront besoin?
Si vous le considérez comme une dépendance, la dépendance commune est toujours déclarée dans votre pubspec.yaml.
Donc, avant d'ajouter une classe, une fonction ou un widget au dossier commun, demandez-vous:
À l'intérieur de chaque dossier de fonctionnalité, vous avez des dossiers définis par type.
Voici les types de dossiers qui feraient partie d'un dossier de fonctionnalité.
|-- ui/
|-- cubits/
|-- domain_objects/
|-- exceptions/
|-- use_cases
|-- services/
|-- repositories/
|-- data_sources/
|-- dtos/
S'il y a plus d'un fichier lié à un seul fichier, vous pouvez les regrouper dans un dossier, par exemple le code généré pour une classe dans un fichier. Par exemple:
|-- authentication/
|---- cubits/
|------ login/
|-------- login_cubit.dart
|-------- login_cubit.freezed.dart
|-------- login_cubit.g.dart
Code qui est lié à l'interface de l'appareil de l'utilisateur, par exemple: UI PageBuilders, écrans d'interface utilisateur, vue d'interface utilisateur, composants d'interface utilisateur.
Un écran est un composant d'interface utilisateur qui remplit l'affichage de l'appareil entier et est le conteneur de la vue ou du composant d'interface utilisateur (bouton, case à cocher, images et ect).
De plus, un écran est une destination de navigation d'application spécifique.
Ligne directrice:
class LoginScreen extends StatelessWidget {
//...fields and constructor...
@override
Widget build ( BuildContext context) {
return Column (
children : < Widget > [
_LoginLogo ,
_LoginForm ,
_LoginActions ,
_LoginFooter
],
);
}
}
// extracted by use case.
class _LoginLogo extends StatelessWidget {}
// extracted by update area.
class _LoginForm extends StatefullWidget {}
// extracted by update area.
class _LoginActions extends StatelesssWidget {}
// extracted by update area.
class _LoginFooter extends StatelessWidget {} login_screen.dart
part 'login_screen_components.dart' ;
class LoginScreen extends StatelessWidget {
//...fields and constructor...
@override
Widget build ( BuildContext context) {
return Column (
children : < Widget > [
_LoginLogo ,
_LoginForm ,
_LoginActions ,
_LoginFooter
],
);
}
}
// Is kept here because it's does not break the 400 max line rule.
class _LoginLogo extends StatelessWidget {} login_screen_components.dart
part of 'login_screen.dart' ;
/// [LoginScreen] 's fields.
class _LoginForm extends StatelessWidget {}
// ************************ Footer ************************
/// [LoginScreen] 's footer.
class _LoginFooter extends StatelessWidget {}
// ************************* ACTIONS *********************************
/// [LoginScreen] 's actions.
class _LoginActions extends StatelessWidget {} class LoginScreen extends StatelessWidget {
@override
Widget build ( BuildContext context) {
return BlocBuilder < LoginCubit , LoginCubitState >(
// Here cubit is not specified either.
builder : ( BuildContext context, LoginCubitState state) {},
);
}
} class SomeWidget extends StatelessWidget {
@override
Widget build ( BuildContext context) {
//...
}
}
class _SubTree1 extends StatelessWidget {}
class _SubTree2 extends StatelessWidget {}
class _SubTree3 extends StatelessWidget {}
class _SubTree4 extends StatelessWidget {} class LoginScreen extends StatelessWidget {
@override
Widget build ( BuildContext context) {
return Column (
childreen : < Widget > [
Button1 (onClick : _openRegisterUser),
Button2 (onClick : _openLogin),
Field (onTextChanged : _onUserNameTextChanged),
],
);
}
void _openRegisterUser () {}
void _openLogin () {}
void _onUserNameTextChanged ( String newText) {}
}Une vue est un composant d'interface utilisateur qui ne remplit pas l'affichage de l'appareil entier et est le conteneur de la vue ou du composant de l'interface utilisateur (bouton, case à cocher, images et ect).
De plus, une vue n'est pas une destination de navigation d'application.
Ligne directrice:
Les cubes sont des classes qui contient une logique métier pour votre interface utilisateur, la cube est liée à un état, qui représente l'état de l'interface utilisateur.
Recommandations des lignes directrices:
Future < String ?> bookAppointment ( BookingData booking);
Future < bool > login ( String username, String password);Une classe d'état de coudées représente l'état de l'interface utilisateur délimitée à un moment donné.
Recommandations des lignes directrices:
class LoginState {
bool logginSuceeded // avoid this.
}Classes qui utilisaient pour effectuer une seule opération / objectif / travail / tâche dans votre domaine.
Lignes directrices:
class GetCurrentUserUseCase , class SignInUseCase ;UseCase ;call() , où le type de retour est le résultat de l'exécution du cas d'utilisation;Repositories , Services ou à tout autre high level coordinators ;DataSource ou Cubit ou interagir avec DTOS ou tout autre objet de bas niveau;Cubit ou dans d'autres UseCases .Exemples:
class GetCurrentUserUseCase {
final UserRepository _repository;
const GetCurrentUserUseCase ( this ._repository);
Future < User ?> call () async {
await _repository. getCurrentUser ();
}
} class AuthenticateMemberUseCase {
/// Create a [AuthenticateMemberUseCase] .
const AuthenticateMemberUseCase (
/* constructor arguments */
);
/* ... fields ...*/
/// Execute the use case.
Future < TegTask < void >> call ( MemberAuthenticationCredentials credentials) {
return runTaskSafelyAsync < void >(() async {
final bool isEmailValid = credentials.email.isEmail;
if ( ! isEmailValid) {
throw const TegEmailInvalidException ();
}
final bool isConnected = await _hostDeviceInternetService. isConnected ();
if ( ! isConnected) {
throw const TegInternetUnavailableException ();
}
final String ? deviceId = await _hostDeviceInfoRepository. getDeviceId ();
if (deviceId == null ) {
throw const TegDeviceIdUnavailableException ();
}
final PublicPrivateKeyPair keyPair = await _keyGenerator. generate ();
final Member member = await _authService. signIn (
email : credentials.email,
password : credentials.password,
deviceId : deviceId,
publicKey : keyPair.publicKey,
);
await _updateCurrentMemberUseCase (
member : member,
memberPrivateKey : keyPair.privateKey,
deviceId : deviceId,
);
await _saveMemberAuthenticationCredentialsUseCase (credentials);
final TegTask < List < Account >> memberAccountsTask = await _getMemberAccountsUseCase ();
if (memberAccountsTask.failed) {
throw memberAccountsTask.exception;
}
await _updateMemberCurrentAccountUseCase (
member : member,
deviceId : deviceId,
memberPrivateKey : keyPair.privateKey,
account : memberAccountsTask.result.first,
);
});
}
}Classes qui donnent accès aux données en utilisant uniquement des opérations CRUD pour une fonctionnalité spécifique ou une portée d'une fonctionnalité, par exemple:
abstract class BookingRepository {
Futute < Booking > getBookingById ( String id);
Future < List < Booking >> getBookings ();
Future < void > deleteBookingById ( String id);
Future < void > saveBooking ( Booking booking);
}Recommandations des lignes directrices:
class DefaultBookingRepository implements BookingRepository {
final LocalBookingDataSource local;
final RemoteBookingDataSource remote;
Future < Booking ?> getBookingById ( String id) async {
Booking ? savedBooking = await local. getBookingById (id);
if (savedBooking == null ) {
Booking ? remoteBooking = await remote. getBookingById (id);
if (remoteBooking != null ) {
await local. saveBooking (remoteBooking);
}
return remoteBooking;
}
return savedBooking;
}
//...other operations...
}Interface
abstract class BookingRepository {}Implémentations
class DefaultBookingRepository implements BookingRepository {}
class AnonymousBookingRepository implements BookingRepository {}
class PremiumBookingRepository implements BookingRepository {}Des classes qui donnent accès aux fonctionnalités pour une fonctionnalité spécifique ou une portée d'une fonctionnalité, par exemple:
abstract class AuthenticationService {
Future < void > authenticate ( String username, String password);
//...other functionalities...
}
abstract class AppointmentService {
Future < void > register ( Appointment appointment);
//...other functionalities...
}Recommandations de lignes directrices.
Classes qui implémentent l'accès aux données situées localement ou à distance. Exemple
abstract class LocalBookingDataSource {
Futture < void > saveBooking ( Booking booking);
//...other functionalities...
}
abstract class RemoteBookingDataSource {
Future <booking> saveBooking ();
}Recommandations des lignes directrices:
class SQLiteBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}
class RestApiBookingDataSource implements RemoteBookingDataSource {
//...............Implementation................
}
class MemoryBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}Objets de transfert de données - utilisés pour transférer les données d'un système à un autre. Exemple
class ApiRequest {
//...fields...
}
class ApiResponse {
//...fields...
} class SQLiteTableDefinition {
//...fields...
}Lignes directrices:
class ApiRequest {
//...fields...
factory ApiRequest . fromDO ( DOObject doObject) {
//...mapper code...
}
DOObject toDO () {
//...mapper code...
}
}L'objet de domaine est la représentation des classes de données d'une partie de l'entreprise ou d'un élément à l'intérieur, elles sont utilisées pour exécuter la logique commerciale indépendamment de la plate-forme, de la bibliothèque ou des outils.
Un objet de domaine peut représenter, par exemple, une personne, un lieu, un événement, un processus métier ou un concept et une facture, un produit, une transaction ou même des détails d'une personne .
Ligne directrice:
La même chose que pour la structure du code de projet.
D'abord le nom de fonction, puis le dossier assets contenant des fichiers.
Exemple:
|-- authentication/
|---- assets/
|------ password_hidden_icon.svg
|------ forget_password_icon.svg
|------ background.png