Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
Dizi · 32/61 Flutter Türkçe Tutorial
- Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
- Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
- Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
- Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
- Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
- Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
- Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
- Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
- Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
- Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
- Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
- Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
- Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
- Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
- Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
- Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
- Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
- Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
- Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
- Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
- Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
- Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
- Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
- Bölüm 6 — Konu 24: Provider Paketi
- Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
- Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
- Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
- Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
- Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
- Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
- Bölüm 7 — Konu 31: Deep Linking
- Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
- Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
- Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
- Bölüm 8 — Konu 35: Implicit Animasyonlar
- Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
- Bölüm 8 — Konu 37: Hero Animasyonları
- Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
- Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
- Bölüm 9 — Konu 40: Platform Channels
- Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
- Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
- Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
- Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
- Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
- Bölüm 10 — Konu 46: CI/CD
- Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
- Bölüm 10 — Konu 48: DevTools Profiling
- Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
- Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
- Bölüm 11 — Konu 51: Supabase Alternatifi
- Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
- Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
- Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
- Bölüm 12 — Konu 55: Flutter Web / Desktop
- Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
- Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
- Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
- Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
- Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
- Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
İçindekiler 11 başlık
- Sorun: Her Şey Karışık Olunca Ne Olur?
- Katmanlı Mimari — Üç Temel Katman
- Data Katmanı — Ham Veriye Erişim
- Repository — Data Katmanının "Soyutlanmış Arayüzü"
- Domain Katmanı — İş Mantığı (Use Case'ler)
- Presentation Katmanı — Widget'lar + State Management
- Neden Bu Kadar Katman? — Somut Faydalar
- Klasör Yapısı — Gerçek Bir Proje Organizasyonu
- Küçük Projelerde Gerekli mi?
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
Sorun: Her Şey Karışık Olunca Ne Olur?
Bölüm 5'te öğrendiğimiz HTTP istekleri, JSON serialization ve yerel depolamayı hatırlarsan — eğer bir widget'ın build() metodu içinde, doğrudan http.get(...) çağırıp, jsonDecode yapıp, Hive'a kaydediyorsan, bu kod hızla karmaşıklaşır ve test edilmesi, değiştirilmesi zorlaşır. Bu ders, bu karmaşıklığı düzenli katmanlara ayırmayı öğretiyor.
Katmanlı Mimari — Üç Temel Katman
Presentation (Sunum) → Domain (İş Mantığı) → Data (Veri)
Widget'lar İş kuralları API/DB erişimiHer katmanın tek bir sorumluluğu vardır — bu, Bölüm 3 Konu 13'te öğrendiğimiz "tek sorumluluk" prensibinin, tüm uygulama mimarisine uygulanmış hali.
Data Katmanı — Ham Veriye Erişim
// data/urun_veri_kaynagi.dart
class UrunVeriKaynagi {
final http.Client _client;
UrunVeriKaynagi(this._client);
Future<List<Map<String, dynamic>>> urunleriGetir() async {
final response = await _client.get(Uri.parse('https://api.ornek.com/urunler'));
return List<Map<String, dynamic>>.from(jsonDecode(response.body));
}
}Bu katman, Bölüm 5 Konu 20'de öğrendiğimiz http paketi kullanımını doğrudan barındırır — sadece "veriyi nereden alıyoruz" sorusuyla ilgilenir, hiçbir iş mantığı içermez.
Repository — Data Katmanının "Soyutlanmış Arayüzü"
// domain/urun_repository.dart
abstract class UrunRepository { // Dart Bölüm 7 Konu 34'ü hatırla — soyut sınıf!
Future<List<Urun>> urunleriGetir();
}// data/urun_repository_impl.dart
class UrunRepositoryImpl implements UrunRepository { // Dart Bölüm 7 Konu 35'i hatırla — implements!
final UrunVeriKaynagi _veriKaynagi;
UrunRepositoryImpl(this._veriKaynagi);
@override
Future<List<Urun>> urunleriGetir() async {
final hamVeri = await _veriKaynagi.urunleriGetir();
return hamVeri.map((json) => Urun.fromJson(json)).toList(); // Bölüm 5 Konu 21'i hatırla!
}
}Bu, Dart Bölüm 7'de öğrendiğimiz abstract class + implements kavramının en klasik gerçek dünya kullanımıdır! UrunRepository, bir "sözleşme" tanımlıyor ("bir yerden ürün listesi getirebilmeliyim"), UrunRepositoryImpl ise bunu HTTP API'den getirerek implemente ediyor.
Neden bu ayrım değerli? Dart Bölüm 8'de (Flutter müfredatı, test yazımı konusunda) göreceğimiz gibi, UrunRepository soyut olduğu için, testlerde sahte (mock/fake) bir implementasyon kullanabilirsin — gerçek bir API çağrısı yapmadan, UrunRepository'nin sözleşmesine uyan herhangi bir sahte class'ı test amaçlı kullanabilirsin. Bu, Bölüm 7 Konu 35'te (Dart) öğrendiğimiz "implements, sadece sözleşmeyi devralır" prensibinin, test edilebilirlik açısından pratik değerini gösteriyor.
Domain Katmanı — İş Mantığı (Use Case'ler)
// domain/indirimli_urunleri_getir_usecase.dart
class IndirimliUrunleriGetirUseCase {
final UrunRepository _repository;
IndirimliUrunleriGetirUseCase(this._repository);
Future<List<Urun>> call() async {
final tumUrunler = await _repository.urunleriGetir();
return tumUrunler.where((urun) => urun.indirimdeMi).toList(); // Dart Bölüm 11'i hatırla!
}
}"Use Case" (kullanım senaryosu), tek bir iş mantığı işlemini temsil eden bir class'tır — burada "indirimli ürünleri getir" gibi. Dart Bölüm 11'de öğrendiğimiz .where() metodu, burada iş mantığının (hangi ürünler "indirimli" sayılır) repository'den (veri erişiminden) ayrı tutulduğunu gösteriyor — repository, "tüm ürünleri getir" diyor; use case, "bunlardan hangileri indirimli" kararını veriyor.
Presentation Katmanı — Widget'lar + State Management
// presentation/urun_listesi_ekrani.dart
class UrunListesiEkrani extends ConsumerWidget { // Bölüm 6 Konu 25'i hatırla — Riverpod!
const UrunListesiEkrani({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final urunlerAsync = ref.watch(indirimliUrunlerProvider);
return Scaffold(
appBar: AppBar(title: Text('İndirimli Ürünler')),
body: urunlerAsync.when(
data: (urunler) => ListView.builder(
itemCount: urunler.length,
itemBuilder: (context, index) => ListTile(title: Text(urunler[index].ad)),
),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Hata: $err'),
),
);
}
}Bu katman, sadece görüntüleme ile ilgilenir — Bölüm 3'te öğrendiğimiz ListView.builder, Bölüm 6'da öğrendiğimiz Riverpod entegrasyonu burada bir araya geliyor, ama hiçbir HTTP çağrısı veya iş mantığı içermiyor — bunlar alt katmanlara devredilmiş durumda.
Neden Bu Kadar Katman? — Somut Faydalar
1. Test edilebilirlik: Her katman bağımsız olarak test edilebilir. Use case'i test etmek için, gerçek bir API'ye ihtiyacın yok — sahte bir UrunRepository (Dart Bölüm 7'de öğrendiğimiz implements ile) yeterli.
2. Değişime dayanıklılık: Yarın, veri kaynağını API'den Hive'a (Bölüm 5 Konu 22'yi hatırla) değiştirmen gerekirse, sadece UrunRepositoryImpl'i değiştirirsin — Domain ve Presentation katmanları hiç etkilenmez, çünkü onlar sadece soyut UrunRepository sözleşmesiyle çalışıyor.
3. Paralel geliştirme: Bir takım üyesi UI (Presentation) üzerinde çalışırken, başka biri API entegrasyonu (Data) üzerinde çalışabilir — aradaki soyut sözleşme (Repository), bu iki çalışmanın birbirini beklemeden ilerlemesini sağlar.
Klasör Yapısı — Gerçek Bir Proje Organizasyonu
lib/
data/
urun_veri_kaynagi.dart
urun_repository_impl.dart
domain/
urun.dart (model)
urun_repository.dart (soyut sözleşme)
indirimli_urunleri_getir_usecase.dart
presentation/
urun_listesi_ekrani.dart
urun_karti_widget.dartBu, Bölüm 1 Konu 3'te ve Bölüm 3 Konu 13'te öğrendiğimiz proje organizasyonu prensiplerinin, gerçek, ölçeklenebilir bir mimariye dönüşmüş halidir.
Küçük Projelerde Gerekli mi?
Dürüst bir not: Bu kadar katmanlı bir yapı, küçük bir prototip veya öğrenme projesi için fazla mühendislik (over-engineering) olabilir. Bölüm 5'te yazdığımız basit örnekler (widget içinde doğrudan http.get çağırmak), küçük ölçekte tamamen makuldür. Bu mimari, uygulaman büyüdükçe, birden fazla ekran aynı veriyi kullandıkça, veya test yazma ihtiyacı arttıkça değer kazanır.
🎯 Bu Dersten Çıkarılması Gerekenler
- Katmanlı mimari, uygulamayı Data (veri erişimi), Domain (iş mantığı) ve Presentation (görüntüleme) olmak üzere ayırır.
- Repository pattern,
abstract class+implements(Dart Bölüm 7) kullanarak, veri kaynağının soyut bir sözleşmesini tanımlar — gerçek implementasyon değiştirilebilir. - Use Case'ler, tek bir iş mantığı işlemini kapsüller, repository'den bağımsız olarak iş kurallarını uygular.
- Bu ayrım, test edilebilirlik, değişime dayanıklılık ve paralel geliştirme açısından değer sağlar.
- Küçük projelerde bu kadar katman gereksiz karmaşıklık olabilir — büyüklüğe göre karar verilmelidir.
📝 Ödevler
- [ ] Bölüm 5'te yazdığın bir API entegrasyonunu, data/domain/presentation katmanlarına ayırarak yeniden düzenle.
- [ ]
abstract classile bir Repository sözleşmesi tanımla, gerçek (HTTP) ve sahte (test amaçlı, sabit veri döndüren) iki farklı implementasyon yaz. - [ ] Basit bir Use Case class'ı yaz (örn. "aktif kullanıcıları filtrele"), repository'den bağımsız bir iş kuralı uygula.
- [ ] Kendi cümlelerinle, "neden Repository'yi abstract class olarak tanımlıyoruz, doğrudan somut bir class yazmıyoruz" sorusunu, test edilebilirlik bağlamında açıkla.
- [ ] Küçük bir projede bu mimariyi kullanmanın ne zaman gerekli, ne zaman "aşırı mühendislik" olacağına dair kendi kriterlerini yaz.
Sıradaki konu: Bölüm 7 — Konu 33: Dependency Injection (get_it, injectable)