Hallo! Dieses Dokument enthält meine persönlichen Empfehlungen für Richtlinien, Verfahren und Standards hinsichtlich der Durchführung der Entwicklung.
Der Projektcode sollte mit einer Mischung aus Ordner- und Ordner-by-Typ strukturiert sein.
Bedeutung auf der Stammebene haben Sie Ordner, die Funktionen definieren. Zum Beispiel:
|-- common/
|-- authentication/
|-- settings/
Common/Core -Funktion enthält Klassen, Funktionen oder Widgets, die standardmäßig in allen Funktionen verfügbar sein sollten.
Die Idee ist also, Funktionen Ihrer Anwendung durch die Ordner auf oberster Ebene geteilt zu haben.
Stellen Sie sich alle Ordner auf höchster Ebene als separates DART/Flutter -Paket, Abhängigkeiten oder Zugriffsgrenze Ihrer Anwendung vor.
Wenn Sie also dem gemeinsamen Ordner etwas hinzufügen, müssen Sie sich fragen, werden 99.% Funktionen unserer Anwendung das benötigen?
Wenn Sie es als Abhängigkeit betrachten, wird die gemeinsame Abhängigkeit immer in Ihrem Pubspec.yaml deklariert.
Fragen Sie sich vor dem Hinzufügen einer Klasse, Funktion oder einem Widget in den gemeinsamen Ordner: Fragen Sie sich:
In jedem Feature -Ordner haben Sie Ordner, die nach Typ definiert sind.
Hier sind die Ordnertypen, die Teil eines Feature -Ordners sind.
|-- ui/
|-- cubits/
|-- domain_objects/
|-- exceptions/
|-- use_cases
|-- services/
|-- repositories/
|-- data_sources/
|-- dtos/
Wenn es mehr als eine Datei gibt, die mit einer einzelnen Datei bezogen wird, können Sie sie in einem Ordner gruppieren, z. B. generierten Code für eine Klasse in einer Datei. Zum Beispiel:
|-- authentication/
|---- cubits/
|------ login/
|-------- login_cubit.dart
|-------- login_cubit.freezed.dart
|-------- login_cubit.g.dart
Code, der sich auf die Geräteschnittstelle des Benutzers bezieht, z.
Ein Bildschirm ist eine Benutzeroberflächenkomponente, die die gesamte Geräteanzeige füllt und der Container der Benutzeroberflächenansicht oder -komponente ist (Schaltfläche, Kontrollkästchen, Bilder und ECT).
Außerdem ist ein Bildschirm ein bestimmtes Anwendungsnavigationsziel.
Richtlinie:
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) {}
}Eine Ansicht ist eine Benutzeroberflächenkomponente, die nicht die gesamte Geräteanzeige füllt und der Container der Benutzeroberfläche oder -komponente ist (Schaltfläche, Kontrollkästchen, Bilder und ECT).
Eine Ansicht ist auch kein Anwendungsnavigationsziel.
Richtlinie:
Ellen sind Klassen, die Geschäftslogik für Ihre Benutzeroberfläche enthalten, Kubit ist an einen Zustand gebunden, der den UI -Zustand darstellt.
Richtlinienempfehlungen:
Future < String ?> bookAppointment ( BookingData booking);
Future < bool > login ( String username, String password);Eine Kubitstaatenklasse repräsentiert den Zustand der Benutzeroberfläche, der eine bestimmte Zeit daran begrenzt ist.
Richtlinienempfehlungen:
class LoginState {
bool logginSuceeded // avoid this.
}Klassen, in denen ein einzelner Geschäftsbetrieb/Ziel/Job/Aufgabe in Ihrer Domain erstellt wurde.
Richtlinien:
class GetCurrentUserUseCase , class SignInUseCase ;UseCase haben;call() haben, wobei der Rückgabetyp das Ergebnis der Ausführung des Anwendungsfalls ist.Repositories , Services oder andere high level coordinators haben.DataSource noch Cubit haben oder mit DTOs oder einem anderen Objekt auf niedriger Ebene interagieren.Cubit oder in anderen UseCases verwendet werden.Beispiele:
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,
);
});
}
}Klassen, die Zugriff auf Daten bieten, die nur CRUD -Operationen für eine bestimmte Funktion oder einen Umfang einer Funktion verwenden, z. B.:
abstract class BookingRepository {
Futute < Booking > getBookingById ( String id);
Future < List < Booking >> getBookings ();
Future < void > deleteBookingById ( String id);
Future < void > saveBooking ( Booking booking);
}Richtlinienempfehlungen:
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...
}Schnittstelle
abstract class BookingRepository {}Implementierungen
class DefaultBookingRepository implements BookingRepository {}
class AnonymousBookingRepository implements BookingRepository {}
class PremiumBookingRepository implements BookingRepository {}Klassen, die Zugriff auf Funktionen für eine bestimmte Funktion oder einen Umfang einer Funktion ermöglichen, zum Beispiel:
abstract class AuthenticationService {
Future < void > authenticate ( String username, String password);
//...other functionalities...
}
abstract class AppointmentService {
Future < void > register ( Appointment appointment);
//...other functionalities...
}Richtlinienempfehlungen.
Klassen, die den Zugriff auf Daten implementieren, die lokal oder ferngestattet sind. Beispiel
abstract class LocalBookingDataSource {
Futture < void > saveBooking ( Booking booking);
//...other functionalities...
}
abstract class RemoteBookingDataSource {
Future <booking> saveBooking ();
}Richtlinienempfehlungen:
class SQLiteBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}
class RestApiBookingDataSource implements RemoteBookingDataSource {
//...............Implementation................
}
class MemoryBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}Datenübertragungsobjekte - Wird verwendet, um Daten von einem System auf ein anderes zu übertragen. Beispiel
class ApiRequest {
//...fields...
}
class ApiResponse {
//...fields...
} class SQLiteTableDefinition {
//...fields...
}Richtlinien:
class ApiRequest {
//...fields...
factory ApiRequest . fromDO ( DOObject doObject) {
//...mapper code...
}
DOObject toDO () {
//...mapper code...
}
}Domain -Objekt sind Datenklassen Darstellung eines Teils des Geschäfts oder eines darin enthaltenen Elements. Sie werden verwendet, um die Geschäftslogik unabhängig von Plattform, Bibliothek oder Tools auszuführen.
Ein Domain -Objekt kann beispielsweise eine Person, einen Ort, ein Ereignis, einen Geschäftsprozess oder eine Konzept und Rechnung, ein Produkt, eine Transaktion oder sogar Details einer Person darstellen.
Richtlinie:
Das gleiche wie für die Projektcodestruktur.
Zuerst der Feature -Name, dann der Ordner assets , der Dateien enthält.
Beispiel:
|-- authentication/
|---- assets/
|------ password_hidden_icon.svg
|------ forget_password_icon.svg
|------ background.png