Flutter-in rəsmi arxitekturası: UI və data qatları
Flutter komandasının rəsmi arxitektura bələdçisi iki əsas qat verir və üçüncüsünü opsional sayır.
UI qatı
- View — widget kompozisiyası. Burada yalnız göstərmə məntiqi qalır.
- View model — repository-dən gələn məlumatı ekran üçün state-ə çevirir, view rebuild olarkən state-i saxlayır və view-a callback-lər (command-lar) açır.
Data qatı
- Repository — model məlumatı üçün vahid həqiqət mənbəyi: cache, təkrar cəhd, xəta idarəsi, xam məlumatın domain modelinə çevrilməsi.
- Service — xarici API-ı (REST, platforma, fayl sistemi, baza) örtür və state saxlamır.
Domain qatı (opsional) — use-case/interactor-lar. Bələdçi onu yalnız view model-lər çox mürəkkəbləşdikdə tövsiyə edir.
Bələdçi bu quruluşu MVVM ilə eyniləşdirir. Vacib nüans: konkret state management library-si tələb olunmur — prinsiplər Provider, Riverpod və BLoC ilə eyni dərəcədə işləyir.
| Rəsmi tövsiyə | Prioritet | Praktik oxunuşu |
|---|---|---|
| Data və UI qatlarını aydın ayırmaq | Ən yüksək | Bu, siyahının təməlidir — separation of concerns ən vacib prinsip adlandırılır |
| Data qatında repository pattern-i | Ən yüksək | Hər məlumat növü üçün bir repository; UI birbaşa API-yə çıxmır |
| UI qatında view + view model (MVVM) | Ən yüksək | Widget "axmaq" qalır, UI məntiqi ayrı sinifdə test olunur |
| Widget-də məntiq saxlamamaq | Ən yüksək | İcazə verilən istisnalar: göstər/gizlət şərti, animasiya, cihaza görə layout, sadə routing |
| Tək istiqamətli məlumat axını | Ən yüksək | Məlumat data → UI istiqamətində axır; istifadəçi hərəkətləri əks istiqamətdə hadisə kimi gedir |
| Immutable modellər | Ən yüksək | UI qatı məlumatı təsadüfən dəyişə bilmir; dəyişiklik yalnız öz yerində baş verir |
| Dependency injection | Ən yüksək | Qlobal obyektlərdən qaçmaq; bələdçi `provider` paketini nümunə göstərir |
| Abstract repository sinifləri | Ən yüksək | Müxtəlif mühit üçün müxtəlif implementasiya (remote, local, fake) |
| Test üçün fake-lər yazmaq | Ən yüksək | Fake giriş/çıxışa fokuslanır və interfeysi sadə saxlamağa məcbur edir |
| Command pattern-i | Orta | UI-dan gələn hadisələri standartlaşdırır, ikiqat basılmanı və render xətalarını azaldır |
| `freezed` və ya `built_value` | Orta | Immutability, `copyWith`, `==` və JSON — kod generasiyası ilə; böyük layihədə build vaxtı artır |
| Domain qatı (use-case-lər) | Şərtli | Yalnız view model-ləri sıxışdıran mürəkkəb məntiq olduqda; əksər tətbiqdə artıq yükdür |
| Ayrı API və domain modelləri | Şərtli | Böyük tətbiqlərdə tövsiyə olunur; kiçikdə əlavə söz-söhbət yaradır |
Rəsmi bələdçinin nümunələri ChangeNotifier və package:provider üzərində yazılıb. Sənin stack-in Riverpod-dursa, uyğunluq bir-birədir — dəyişən yalnız sintaksisdir:
- View model →
Notifier/AsyncNotifier(kod generasiyası ilə@riverpod class ... extends _$...). - Dependency injection → provider-lərin özü. Repository və service adi Dart sinifləridir; onları qaytaran provider-lər asılılıq qrafını qurur.
- Command → notifier-in metodu (
refresh(),submit()), view isə yalnız onu çağırır. - Loading / error state →
AsyncValue(Riverpod 3-dəsealed, yəniswitchtam yoxlanılır).
Bir vacib fərq: rəsmi bələdçidə view model BuildContext vasitəsilə tapılır, Riverpod-da isə ref vasitəsilə. Praktik nəticə — notifier-in testi widget qaldırmadan mümkündür.
// ══ 1. data/services/product_api_client.dart ══ (state saxlamır)
class ProductApiClient {
ProductApiClient({required http.Client client}) : _client = client;
final http.Client _client;
Future<List<ProductDto>> getActiveProducts() async {
final response = await _client.get(
Uri.parse('https://api.example.com/v1/products?active=true'),
);
if (response.statusCode != 200) {
throw HttpException('status ${response.statusCode}');
}
final list = jsonDecode(response.body)['data'] as List<dynamic>;
return list
.map((e) => ProductDto.fromJson(e as Map<String, dynamic>))
.toList();
}
}
// ══ 2. data/repositories/product_repository_remote.dart ══ (həqiqətin mənbəyi)
class ProductRepositoryRemote implements ProductRepository {
ProductRepositoryRemote({required ProductApiClient apiClient})
: _apiClient = apiClient;
final ProductApiClient _apiClient;
@override
Future<List<Product>> fetchActive() async {
final dtos = await _apiClient.getActiveProducts();
return dtos.map((dto) => dto.toDomain()).toList(); // DTO → domain
}
}
// ══ 3. presentation/products/product_list_notifier.dart ══ (view model)
part 'product_list_notifier.g.dart';
@riverpod
ProductApiClient productApiClient(Ref ref) =>
ProductApiClient(client: http.Client());
@riverpod
ProductRepository productRepository(Ref ref) =>
ProductRepositoryRemote(apiClient: ref.watch(productApiClientProvider));
@riverpod
class ProductList extends _$ProductList {
@override
Future<List<Product>> build() =>
ref.watch(productRepositoryProvider).fetchActive();
// "Command": view yalnız bunu çağırır, necə işlədiyini bilmir.
Future<void> refresh() => ref.refresh(productListProvider.future);
}
// ══ 4. presentation/products/products_page.dart ══ (view)
class ProductsPage extends ConsumerWidget {
const ProductsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(productListProvider);
// AsyncValue Riverpod 3-də sealed-dir: switch tam yoxlanılır.
return switch (state) {
AsyncData(:final value) => RefreshIndicator(
onRefresh: () =>
ref.read(productListProvider.notifier).refresh(),
child: ListView.builder(
itemCount: value.length,
itemBuilder: (_, i) => ProductTile(product: value[i]),
),
),
AsyncError(:final error) => ErrorView(error: error),
_ => const Center(child: CircularProgressIndicator()),
};
}
}Bir feature, dörd fayl: service → repository → notifier → view. Hər fayl yalnız bir qatın işini görür.
Praktika. Yuxarıdaki dörd faylı öz layihəndə (ya da yeni flutter create layihəsində) real bir açıq API üzərində qur — məsələn pulsuz bir REST endpoint. ProductDto-nu hələlik əl ilə yaz (fromJson), freezed sonraki mərhələdədir.
Hazır sayılır: ekran məlumatı göstərir, products_page.dart faylında http, jsonDecode və heç bir JSON açarı görünmür, RefreshIndicator işləyir.
📚 Mənbələr və sənədlər
- Flutter arxitektura bələdçisirəsmidocs.flutter.dev
View, view model, repository, service və opsional domain qatının rəsmi təsviri.
- Arxitektura tövsiyələrirəsmidocs.flutter.dev
Yuxarıdaki cədvəlin mənbəyi — hər tövsiyənin prioriteti ilə birlikdə.
- Case study: UI qatırəsmidocs.flutter.dev
View və view model-in real kodda necə göründüyü, `ChangeNotifier` nümunəsi ilə.
- Riverpod: başlanğıcrəsmiriverpod.dev
`flutter_riverpod`, `riverpod_annotation`, `riverpod_generator` quraşdırılması və `@riverpod` sintaksisi.