Niyə arxitektura: 1000 sətrlik widget-in bədəli
Bir ekran sorğu atır, JSON-u açır, qiyməti hesablayır, xətanı göstərir və hamısını bir faylda edir. Bu işləyir — ikinci ekrana eyni məlumat lazım olana qədər.
Problem estetik deyil, tamamilə praktikdir. Belə bir widget-də:
- Testi yoxdur. Məntiqi yoxlamaq üçün widget-i
pumpWidgetilə qaldırmalı və real şəbəkəyə çıxmalısan. - Təkrar var. İkinci ekran eyni sorğunu, eyni parse-ı, eyni xəta mesajını yenidən yazır.
- Dəyişiklik yayılır. API
price_cents-iprice-a dəyişəndə düzəliş bir yerdə deyil, JSON açarını oxuyan hər faylda edilir. - Qərar verə bilmirsən. "Cache əlavə edək" qərarı bir sinifdə deyil, bütün ekranlarda tətbiq olunmalıdır.
Arxitektura bu problemlərin cavabıdır: hansı kodun harada yaşadığı və kimə müraciət edə biləcəyi barədə qaydalar. Qovluqlar bu qaydaların nəticəsidir, məqsədi deyil.
class ProductsPage extends StatefulWidget {
const ProductsPage({super.key});
@override
State<ProductsPage> createState() => _ProductsPageState();
}
class _ProductsPageState extends State<ProductsPage> {
List<dynamic> _items = [];
bool _loading = true;
String? _error;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
try {
// Endpoint və token widget-in içində.
final response = await http.get(
Uri.parse('https://api.example.com/v1/products?active=true'),
headers: {'Authorization': 'Bearer $kToken'},
);
if (response.statusCode != 200) {
setState(() {
_error = 'Xəta: ${response.statusCode}';
_loading = false;
});
return;
}
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
setState(() {
_items = decoded['data'] as List<dynamic>;
_loading = false;
});
} catch (e) {
setState(() {
_error = e.toString(); // istifadəçi "SocketException" görür
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
if (_loading) return const Center(child: CircularProgressIndicator());
if (_error != null) return Center(child: Text(_error!));
return ListView.builder(
itemCount: _items.length,
itemBuilder: (context, i) {
final raw = _items[i] as Map<String, dynamic>;
// Biznes qaydası widget-in içində: qəpik → manat.
final price = (raw['price_cents'] as int) / 100;
return ListTile(
title: Text(raw['title'] as String),
subtitle: Text('$price AZN'),
);
},
);
}
}"Hər şey bir yerdə" ekranı. Diqqət et: URL, token, JSON açarları, biznes qaydası (qəpik → manat) və xəta mətni — hamısı widget-in içindədir.
| Simptom | Əsl səbəb | Hansı qat həll edir |
|---|---|---|
| İkinci ekrana eyni məlumat lazımdır və kod kopyalanır | Məlumat yükləmə məntiqi widget-in içindədir | Repository — bir dəfə yazılır, hər iki ekran istifadə edir |
| Testdə real API-yə sorğu gedir | Widget HTTP client-i özü yaradır | Service + DI — testdə fake implementasiya ötürülür |
| API bir açarı dəyişdi, 6 fayl sındı | JSON açarları birbaşa UI-də oxunur | DTO + mapper — dəyişiklik bir faylda qalır |
| Qiymət iki ekranda fərqli formatlanır | Biznes qaydası widget-də təkrarlanır | Domain modeli — qayda modelin özündə yaşayır |
| Offline cache əlavə etmək üçün hər ekranı açmaq lazımdır | Məlumatın mənbəyi UI-ya bağlıdır | Repository — cache qərarı bir yerdə verilir |
Diqqət yetir: yuxarıdaki cədvəldə heç bir sətir "kod gözəl görünmür" deyil. Hər sətir ölçülə bilən bir bədəldir — yazılan sətirlərin sayı, sınan faylların sayı, testin işləmə müddəti.
Bu, arxitektura söhbətinin düzgün çərçivəsidir. "Clean architecture" bir moda deyil, konkret suallara cavabdır:
- Bu məlumatı ikinci dəfə kim istəyəcək?
- Bu məntiqi şəbəkə olmadan test edə bilirəmmi?
- Bu dəyişiklik neçə faylı açmağa məcbur edir?
Əks tərəf də var: hər sinif üçün üç qat yazmaq da bədəldir. Bu branch boyu hər qatın qazancını və qiymətini yanaşı göstərəcəyik — sonuncu mərhələdə isə ayrıca "nə vaxt sadələşdirmək lazımdır" mövzusu var.
Praktika. Öz layihəni (ya da tanış bir açıq kod layihəsini) aç, ən böyük widget faylını tap və yuxarıdaki cədvəlin 5 simptomundan hansılarının orada olduğunu bir-bir yaz. Hər simptom üçün bir sətir: "simptom → hansı qat həll edərdi". Hazır sayılır: əlində ən azı 3 sətirlik siyahı var və hər sətirdə konkret fayl adı göstərilib.
📚 Mənbələr və sənədlər
- Arxitektura: girişrəsmidocs.flutter.dev
Flutter komandasının arxitektura bölməsinin başlanğıc səhifəsi — burada bütün alt səhifələrin xəritəsi var.
- Arxitektura konseptlərirəsmidocs.flutter.dev
Separation of concerns, qatlar və vahid həqiqət mənbəyi kimi terminlərin rəsmi izahı.
- Arxitektura tövsiyələrirəsmidocs.flutter.dev
"Data və UI qatlarını aydın ayırın" tövsiyəsi burada, ən yüksək prioritetlə (strongly recommend) verilib.