Halo! Dokumen ini berisi rekomendasi pribadi saya untuk kebijakan, prosedur, dan standar mengenai bagaimana pengembangan harus dilakukan.
Kode proyek harus disusun dengan campuran folder-by-feature dan folder-by-type .
Artinya di level root Anda memiliki folder yang mendefinisikan fitur. Misalnya:
|-- common/
|-- authentication/
|-- settings/
Fitur umum/inti berisi kelas, fungsi atau widget yang secara default harus tersedia di fitur apa pun.
Jadi idenya adalah untuk memiliki fitur aplikasi Anda yang dibagi berdasarkan folder tingkat atas.
Pikirkan setiap folder tingkat atas sebagai paket DART/Flutter terpisah, dependensi atau batas akses aplikasi Anda.
Jadi, jika Anda menambahkan sesuatu ke folder umum, Anda harus bertanya pada diri sendiri, akankah 99.% fitur aplikasi kami membutuhkannya?
Jika Anda menganggapnya sebagai ketergantungan, ketergantungan umum selalu dinyatakan dalam pubspec.yaml Anda.
Jadi sebelum menambahkan kelas, fungsi atau widget ke folder umum, tanyakan pada diri sendiri:
Di dalam setiap folder fitur Anda memiliki folder yang ditentukan berdasarkan jenis.
Berikut adalah jenis folder yang akan menjadi bagian dari folder fitur.
|-- ui/
|-- cubits/
|-- domain_objects/
|-- exceptions/
|-- use_cases
|-- services/
|-- repositories/
|-- data_sources/
|-- dtos/
Jika ada lebih dari satu file yang terkait dengan satu file, Anda dapat mengelompokkannya di folder, misalnya kode yang dihasilkan untuk kelas dalam file. Misalnya:
|-- authentication/
|---- cubits/
|------ login/
|-------- login_cubit.dart
|-------- login_cubit.freezed.dart
|-------- login_cubit.g.dart
Kode yang terkait dengan antarmuka perangkat pengguna, misalnya: UI PageBuilders, layar UI, tampilan UI, komponen UI.
Layar adalah komponen antarmuka pengguna yang mengisi seluruh tampilan perangkat dan merupakan wadah tampilan atau komponen antarmuka pengguna (tombol, kotak centang, gambar dan ECT).
Juga, layar adalah tujuan navigasi aplikasi tertentu.
Pedoman:
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) {}
}Tampilan adalah komponen antarmuka pengguna yang tidak mengisi seluruh tampilan perangkat dan merupakan wadah tampilan atau komponen antarmuka pengguna (tombol, kotak centang, gambar dan ECT).
Juga, tampilan bukanlah tujuan navigasi aplikasi.
Pedoman:
Cubit adalah kelas yang berisi logika bisnis untuk UI Anda, Cubit terikat pada suatu negara, yang mewakili negara UI.
Rekomendasi Pedoman:
Future < String ?> bookAppointment ( BookingData booking);
Future < bool > login ( String username, String password);Kelas negara bagian Cubit mewakili keadaan UI yang terikat pada waktu tertentu.
Rekomendasi Pedoman:
class LoginState {
bool logginSuceeded // avoid this.
}Kelas yang digunakan untuk membuat operasi bisnis tunggal/tujuan/pekerjaan/tugas di domain Anda.
Pedoman:
class GetCurrentUserUseCase , class SignInUseCase ;UseCase ;call() , di mana jenis pengembalian adalah hasil dari menjalankan kasus penggunaan;Repositories , Services atau high level coordinators lainnya;DataSource atau Cubit atau berinteraksi dengan DTO atau objek tingkat rendah lainnya;Cubit atau di UseCases lainnya.Contoh:
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,
);
});
}
}Kelas yang menyediakan akses ke data hanya menggunakan operasi CRUD untuk fitur tertentu atau ruang lingkup fitur, misalnya:
abstract class BookingRepository {
Futute < Booking > getBookingById ( String id);
Future < List < Booking >> getBookings ();
Future < void > deleteBookingById ( String id);
Future < void > saveBooking ( Booking booking);
}Rekomendasi Pedoman:
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...
}Antarmuka
abstract class BookingRepository {}Implementasi
class DefaultBookingRepository implements BookingRepository {}
class AnonymousBookingRepository implements BookingRepository {}
class PremiumBookingRepository implements BookingRepository {}Kelas yang menyediakan akses ke fungsionalitas untuk fitur tertentu atau ruang lingkup fitur, misalnya:
abstract class AuthenticationService {
Future < void > authenticate ( String username, String password);
//...other functionalities...
}
abstract class AppointmentService {
Future < void > register ( Appointment appointment);
//...other functionalities...
}Rekomendasi Pedoman.
Kelas yang menerapkan akses ke data yang berlokasi secara lokal atau jarak jauh. Contoh
abstract class LocalBookingDataSource {
Futture < void > saveBooking ( Booking booking);
//...other functionalities...
}
abstract class RemoteBookingDataSource {
Future <booking> saveBooking ();
}Rekomendasi Pedoman:
class SQLiteBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}
class RestApiBookingDataSource implements RemoteBookingDataSource {
//...............Implementation................
}
class MemoryBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}Objek transfer data - digunakan untuk mentransfer data dari satu sistem ke sistem lainnya. Contoh
class ApiRequest {
//...fields...
}
class ApiResponse {
//...fields...
} class SQLiteTableDefinition {
//...fields...
}Pedoman:
class ApiRequest {
//...fields...
factory ApiRequest . fromDO ( DOObject doObject) {
//...mapper code...
}
DOObject toDO () {
//...mapper code...
}
}Objek domain adalah representasi kelas data dari bagian dari bisnis atau item di dalamnya, mereka digunakan untuk menjalankan logika bisnis secara independen dari platform, perpustakaan, atau alat.
Objek domain dapat mewakili, misalnya, seseorang, tempat, acara, proses bisnis, atau konsep dan faktur, produk, transaksi atau bahkan perincian seseorang .
Pedoman:
Sama seperti untuk struktur kode proyek.
Pertama nama fitur, lalu folder assets yang berisi file.
Contoh:
|-- authentication/
|---- assets/
|------ password_hidden_icon.svg
|------ forget_password_icon.svg
|------ background.png