Flutter’da Clean Architecture Ve Folder Structure
Merhaba değerli geliştirici arkadaşlar, yeni başlayan arkadaşlar bu başlığı okuyunca hayretler içinde kalmış olabilir.. Ama bu yazımda amacımız temiz bir mimari ve klasör yapısı üzerine konuşmak.
Bu konuda binlerce makale var ancak benim yazma nedenim kavramları, mimariyi bilmeden ve anlamadan yapay zeka kullanarak uygulama geliştirmeye çalışmak. Cursor editörünü açıp, yeni bir flutter projesi oluşturup yapay zeka modeline; “ Bu flutter uygulamamda firebase authentication özelliğini kurmanı/yapmanı istiyorum. Bunu clean code ilkelerine uyarak yap” şeklinde prompt giren çok fazla insan var. -Bu çalışır çalışmaz değil meselemiz- Yapay zekanın çıkarttığı sonucu bilmeyenler hemen accept butonuna basarak kabul ediyor. Ancak bu yaklaşımın doğru olmadığını vurgulamak isterim. Yapay zekaya sihirli değnek gibi bakmak doğru değil arkadaşlar. O yüzden lütfen terimleri, felsefeleri hiç yoksa giriş seviyesinde temel seviyede öğreninki yapay zekayı kullanabiliyorum diyebilin. Emin olun en çok ben istiyorum yapay zeka ne yazarsak yazalım en harika şekilde bize çıktı versin ancak günümüz itibariyle ne yazık ki bu mümkün değil..
Etkili promtp makalemde de bu konulara değinmiştim. Yapay zeka sizin yönlendirmeniz olmazsa en kestirme en kolay en işine gelen kısma kaçar. Onu yönlendiremezseniz bir stajyer gibi bir acemi gibi davranır. Ancak onu yönlendirebilirseniz usta bir yardımcı, özel kalem, sekreter artık adına siz ne derseniz yaratabilirsiniz.
Sözü çok uzattım ancak giriş kısmıda ana konu kadar önemliydi bence…
Bu yazımda Temiz Kod ve Klasör Yapısı konusunda temel seviye olarak kavramları göreceksiniz. Bu kavramları örnek bir uygulama geliştirerek sizlere anlatıyor olacağım. Sizlerde bu kodları uygulayarak, deftere, word vb yapılara not olarak daha iyi ve akılda kalıcı şekilde öğrenebilirsiniz..
Clean Code Nedir
Flutter ile harika uygulamalar yapıyorsun ama kodun büyüdükçe her şeyin birbirine karıştığı, “spagetti koda” dönüştüğü bir kabus yaşıyor musun? Bir butona dokunduğunda çalışan kodun, hem arayüzü güncelleyip hem internetten veri çekip hem de o veriyi telefona kaydettiği o meşhur “tek dosya” cehennemi… İşte Clean Architecture, bu kaosu önlemek için var.
Amacı çok basit: Sorumlulukları Ayırmak.
Tıpkı profesyonel bir restoran mutfağı gibi. Herkesin bir görevi vardır. Bulaşıkçı, şefin yemeğini nasıl yaptığını bilmez. Şef, etin hangi kasaptan geldiğini bilmek zorunda değildir. Garson, mutfaktaki tarif defterini okumaz. Herkes kendi işini en iyi şekilde yapar ve sistem tıkır tıkır işler.
Uygulamamızı bir restoran olarak düşünelim:
- Presentation Katmanı (Restoran Salonu): Müşterinin (kullanıcının) oturduğu, menüye baktığı ve sipariş verdiği yer. Burası bizim UI (Arayüz) katmanımızdır. Garsonlar (State Management — BLoC, Provider vb.) müşteriden siparişi alıp mutfağa iletir.
- Domain Katmanı (Mutfak & Şef): Restoranın kalbi. Burada tarifler (İş Kuralları) ve şefin kendisi (Usecase’ler) bulunur. Bu katman, dış dünyadan tamamen soyutlanmıştır. Şef, sadece “bana 2 adet domates getir” der. Domatesin tarladan mı, marketten mi, yoksa depodan mı geldiğiyle ilgilenmez. Bu, uygulamanın beynidir ve hiçbir teknolojiye (Firebase, API, Flutter UI) bağımlı değildir. Saf Dart kodudur.
- Data Katmanı (Kiler, Tedarikçiler, Kasap): Malzemelerin geldiği ve saklandığı yer. Şefin “domates getir” emrini alıp, hangi tedarikçiden (API, Veritabanı) alacağını bilen ve domatesi mutfağa getiren çıraklar burada çalışır.
Altın Kural (The Dependency Rule): Bağımlılıklar daima dışarıdan içeriye doğrudur.
- Presentation -> Domain’i bilir. (Garson, Şef’in varlığını bilir.)
- Data -> Domain’i bilir. (Tedarikçi, Şef’in ne istediğini bilir.)
- AMA Domain (Şef), ne Presentation’ı (Garsonu) ne de Data’yı (Tedarikçiyi) bilir! Şef evrenseldir. Başka bir restorana gitse yine aynı tariflerle yemek yapabilir
Folder Structure Nedir
Bu kavram aslında Clean Architecture kavramının içinde yer alır. Klasörleri ve dosyaları nasıl organize ettiğimizle ilgilidir.
Bunu evinizdeki bir “her şeyin atıldığı çekmece” ile düzenli bir kütüphane arasındaki fark gibi düşünebilirsiniz:
- Kötü Klasör Yapısı (Çekmece): Her şey (main.dart, UI dosyaları, veri modelleri, servisler) lib klasörünün içine rastgele atılmıştır. Bir dosyayı bulmak için her seferinde tüm çekmeceyi karıştırmanız gerekir. Başkası sizin projenize baktığında hiçbir şey anlayamaz.
- İyi Klasör Yapısı (Kütüphane): Her dosyanın mantıksal bir yeri vardır. Romanlar bir rafta, tarih kitapları başka bir rafta, bilim dergileri ise kendi bölümündedir. Aradığınızı kolayca bulursunuz. Projeye yeni katılan biri bile, nereye bakacağını sezgisel olarak anlar.
Kısacası, klasör yapısı projenizin iskeletidir. Sadece dosyaları bir arada tutmakla kalmaz, aynı zamanda projenin mantıksal mimarisini de yansıtır.
Bu yapının yani klasör ve dosya organize etmenin önemi büyük. Hem okuma anlamında hem proje büyüdüğünde iyi bir klasör yapısının avantajları çok. Test konusunu söylemeye gerek bile yok…
Buraya kadar çok temel olarak kavramları öğrendik. Neden gerekli sorusuna yanıt aldık diye düşünüyorum. Artık biraz daha derinlerine inme vakti.
Gelin örnek bir uygulama geliştirerek işi daha iyi kavramaya çalışalım.
Önce klasör yapımızı hazırlayalım. Klasör yapımız Feature-First yaklaşımı şeklinde olacaktır. Bu konuda da makalem olduğu için buradan okuyabilirsiniz. Her masada varız tabi ne sandınız benim Flutter Sevdamı!
Klasör Yapısının Oluşturulması
lib/
├── core/
│ ├── error/
│ │ ├── exceptions.dart # (örn: ServerException, CacheException)
│ │ └── failure.dart # (örn: ServerFailure, CacheFailure abstract sınıfları)
│ ├── network/
│ │ └── network_info.dart # (İnternet bağlantısını kontrol eden sınıf)
│ ├── usecases/
│ │ └── usecase.dart # (Tüm usecase'ler için temel bir arayüz, örn: Usecase<Type, Params>)
│ └── util/
│ └── constants.dart # (Uygulama genelindeki sabitler)
│
├── features/
│ └── todo/
│ ├── data/
│ │ ├── datasources/
│ │ │ └── todo_remote_data_source.dart
│ │ ├── models/
│ │ │ └── todo_model.dart
│ │ └── repositories/
│ │ └── todo_repository_impl.dart
│ │
│ ├── domain/
│ │ ├── entities/
│ │ │ └── todo_entity.dart
│ │ ├── repositories/
│ │ │ └── todo_repository.dart (Abstract Sınıf)
│ │ └── usecases/
│ │ └── get_todos_usecase.dart
│ │
│ └── presentation/
│ ├── provider/ (veya bloc/, cubit/, controller/)
│ │ └── todo_provider.dart
│ ├── pages/
│ │ └── todo_page.dart
│ └── widgets/
│ └── todo_list_item.dart (Tek bir todo öğesini gösteren özel widget)
│
├── injection_container.dart # (Bağımlılıkları (dependencies) yöneteceğimiz yer, get_it vb.)
└── main.dart # (Uygulamanın başlangıç noktası)
İlk kez görenler haho diyor olabilir ama korkmayın şimdi gelin ne ne için burada yer alıyor onu açıklayayım. Gerçi makalemde açıklamıştım tembeller için yine açıklayayım..
lib/core/
Bu klasör, herhangi bir özelliğe özel olmayan, uygulama genelinde tekrar tekrar kullanılabilecek kodları barındırır.
- error/: Hata yönetimini merkezileştirmek için. Failure sınıfları, UI’a ne tür bir hata olduğunu (örn: “İnternet bağlantısı yok”, “Sunucu hatası”) temiz bir şekilde bildirmemizi sağlar. Exception’lar ise Data katmanında kalır.
- network/: Cihazın internete bağlı olup olmadığını kontrol etmek gibi ağ ile ilgili genel yardımcı sınıflar için.
- usecases/: Tüm usecase’ler için ortak bir yapı oluşturmak istersen (örneğin parametre almayan bir usecase, parametre alan bir usecase gibi) buraya bir temel sınıf ekleyebilirsin.
- util/: Genel yardımcı fonksiyonlar, sabitler, temalar gibi dosyalar için.
lib/features/
Uygulamanın tüm özelliklerinin bulunduğu ana klasör.
- todo/: Bizim “Yapılacaklar” özelliğimizin kök klasörü. Eğer uygulamaya bir kullanıcı girişi (auth) özelliği eklemek istersen, features altına auth adında yeni bir klasör açıp aynı data, domain, presentation yapısını onun içine de kurarsın. Bu sayede özellikler birbirinden tamamen izole olur.
Uygulamanın beyni. Dış dünyaya hiçbir bağımlılığı yok.
- entities/: İş mantığının temel veri yapıları (TodoEntity).
- repositories/: Veri katmanının uyması gereken sözleşmeler, yani abstract sınıflar (TodoRepository).
- usecases/: Tek bir işi yapan sınıflar (GetTodosUsecase).
Verinin geldiği ve işlendiği katman. domain katmanındaki sözleşmelere uyar.
- models/: API veya veritabanından gelen ham veriye karşılık gelen sınıflar (TodoModel). JSON’a dönüştürme işlemleri burada yapılır.
- datasources/: Veri kaynaklarıyla (API, Firebase, local DB) doğrudan konuşan sınıflar.
- repositories/: domain katmanındaki abstract repository’yi implements eden ve datasource’u kullanarak veriyi alıp entity’e çeviren somut sınıflar.
Kullanıcının gördüğü ve etkileşimde bulunduğu her şey.
- provider/ (veya bloc/ vb.): State management sınıfları. UI ile domain (Usecase’ler aracılığıyla) arasındaki köprüdür.
- pages/ (veya screens/): Tam bir ekranı temsil eden ana widget’lar.
- widgets/: O özelliğe ait, tekrar kullanılabilir daha küçük widget’lar (bir liste elemanı, özel bir buton vb.)
lib/injection_container.dart
Uygulamanın main.dart dosyasını temiz tutmak için tüm bağımlılıkları (repository, usecase, provider vb.) oluşturduğumuz ve kaydettiğimiz yer. Genellikle get_it gibi bir Service Locator paketi ile kullanılır. main() fonksiyonu içinde bu dosyadaki bir init() fonksiyonunu çağırarak tüm sistemi ayağa kaldırırsın. Bu konuyla ilgilide bir makalem var onuda buradan okuyabilirsiniz. Evet öğrenecek ne çok şey var..
lib/main.dart
Uygulamanın giriş noktası. Genellikle sadece injection_container’ı başlatır ve runApp() metodunu çağırır. Çok temiz ve kısa kalmalıdır.
Artık klasör yapımızı biliyoruz. Şimdi geldik gırnatanın bizi halaya kaldırdığı yere..
Şimdi klasik bir To-Do app yaparak bu klasör yapısını daha anlamlı bir hale getirelim..
To Do App Örneği Geliştirme
Senaryomuz: İnternetten bir yapılacaklar listesi (To-Do List) çekeceğiz ve ekranda göstereceğiz. Bu makalem giriş seviyesi ve temel kazanımı olduğu için her detayı kafa karıştırmaması adına paylaşmıyorum.
Adım 1: Domain Katmanı (Şef ve Tarifleri Hazırlıyoruz)
Önce uygulamanın beynini, kurallarını yazacağız. Unutma, burada flutter, http veya firebase gibi hiçbir paket yok. Sadece saf Dart.
Klasör yapımız: lib/features/todo/domain/
a) Entity (Malzemenin Kendisi)
Bu, işimizin temel nesnesidir. Bir “Yapılacak” nedir?
lib/features/todo/domain/entities/todo_entity.dart
// Hiçbir pakete bağımlı değil. Sadece saf veri tutar.
// Bu bizim tarifteki "domates" gibi.
class TodoEntity {
final int id;
final String title;
final bool isCompleted;
TodoEntity({
required this.id,
required this.title,
required this.isCompleted,
});
}
b) Repository (Şef’in Emri — “Sözleşme”)
Bu, şefin mutfak çırağına verdiği emirdir. “Bana yapılacaklar listesini getir!” der. Nasıl ve nereden getirileceğini söylemez, sadece ne istediğini söyler. Bunu bir “sözleşme” (abstract class) olarak yazarız.
lib/features/todo/domain/repositories/todo_repository.dart
import '../entities/todo_entity.dart';
// Bu bir SÖZLEŞME'dir. Bunu kim uygularsa uygulasın,
// bu metodu sağlamak ZORUNDADIR.
abstract class TodoRepository {
// Başarılı olursa bir liste dönecek, başarısız olursa bir hata.
// Bu yapıyı Either gibi paketlerle daha şık yönetebiliriz ama şimdilik basit tutalım.
Future<List<TodoEntity>> getTodos();
}
c) Usecase (Tarif Defteri)
Bu, uygulamanın yapabileceği tek bir spesifik iştir. “Yapılacakları Getir” işlemi. Bu tarif, “Yapılacaklar Listesi Getir” emrini (Repository) kullanır.
lib/features/todo/domain/usecases/get_todos_usecase.dart
import '../entities/todo_entity.dart';
import '../repositories/todo_repository.dart';
// Bu bizim "Omlet Yap" tarifimiz. Sadece tek bir işi var.
class GetTodosUsecase {
// Tarifin hangi emre (sözleşmeye) ihtiyacı var? TodoRepository'e.
final TodoRepository repository;
GetTodosUsecase(this.repository);
// Bu Usecase'i çağırdığımızda çalışacak olan kod.
// Repository'deki metodu çağırır. Burada ek iş kuralları da olabilir.
// (Örn: Sadece tamamlanmamışları getir, alfabetik sırala vs.)
Future<List<TodoEntity>> call() async {
return await repository.getTodos();
}
}
Domain katmanı tamam! Artık uygulamamızın bir beyni var. Bu beyin, arayüzden veya veritabanından tamamen bağımsız.
Adım 2: Data Katmanı (Tedarikçi ve Kiler)
Şimdi şefin emirlerini yerine getirecek altyapıyı kuruyoruz. Veriyi internetten çekeceğiz.
Klasör yapımız: lib/features/todo/data/
a) Model (Tedarikçiden Gelen Koli)
API’den gelen veri (JSON), bizim TodoEntitymiz ile birebir aynı olmayabilir. Farklı alan adları veya fazladan alanlar içerebilir. Model, API’den gelen ham veriyi temsil eder ve Entitye nasıl dönüştürüleceğini bilir.
lib/features/todo/data/models/todo_model.dart
import '../../domain/entities/todo_entity.dart';
// Bu sınıf, API'den gelen JSON'a özeldir.
class TodoModel extends TodoEntity {
TodoModel({
required int id,
required String title,
required bool isCompleted,
}) : super(id: id, title: title, isCompleted: isCompleted);
// JSON'dan TodoModel oluşturan metot.
factory TodoModel.fromJson(Map<String, dynamic> json) {
return TodoModel(
id: json['id'],
title: json['title'],
isCompleted: json['completed'], // API'de alan adı farklı olabilir!
);
}
// Model'i, Domain'in anladığı Entity'e dönüştüren metot.
// Bu örnekte aynılar ama farklı olabilirlerdi.
TodoEntity toEntity() => TodoEntity(id: id, title: title, isCompleted: isCompleted);
}
b) Data Source (Tedarikçinin Kendisi)
İnternetle, veritabanıyla, yani dış dünyayla doğrudan konuşan sınıf budur.
lib/features/todo/data/datasources/todo_remote_data_source.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/todo_model.dart';
abstract class TodoRemoteDataSource {
Future<List<TodoModel>> getTodos();
}
class TodoRemoteDataSourceImpl implements TodoRemoteDataSource {
final http.Client client;
TodoRemoteDataSourceImpl({required this.client});
@override
Future<List<TodoModel>> getTodos() async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/todos'));
if (response.statusCode == 200) {
final List<dynamic> jsonList = json.decode(response.body);
return jsonList.map((json) => TodoModel.fromJson(json)).toList();
} else {
throw Exception('Failed to load todos');
}
}
}
c) Repository Implementation (Mutfak Çırağı)
İşte sihrin gerçekleştiği yer! Domain’deki o “sözleşmeyi” (TodoRepository) alıp, onu DataSource ile hayata geçiren sınıftır.
lib/features/todo/data/repositories/todo_repository_impl.dart
import '../../domain/entities/todo_entity.dart';
import '../../domain/repositories/todo_repository.dart';
import '../datasources/todo_remote_data_source.dart';
// Bu sınıf, Şef'in emrini (TodoRepository sözleşmesi) yerine getirir.
class TodoRepositoryImpl implements TodoRepository {
final TodoRemoteDataSource remoteDataSource;
TodoRepositoryImpl({required this.remoteDataSource});
@override
Future<List<TodoEntity>> getTodos() async {
try {
// 1. Tedarikçiden (DataSource) ham veriyi (Model) al.
final List<TodoModel> todoModels = await remoteDataSource.getTodos();
// 2. Ham veriyi, Şef'in anlayacağı dile (Entity) çevir ve ona ver.
return todoModels.map((model) => model.toEntity()).toList();
} catch (e) {
// Hata olursa yönet.
throw Exception('Network Error');
}
}
}
Bu katmanımızda tamam sıradaki katmana geçelim
Adım 3: Presentation Katmanı (Restoran Salonu ve Garson)
Son olarak, kullanıcının göreceği ekranı ve bu ekranın durumunu yönetecek “garsonu” yazalım. Ben basitlik için Provider kullanacağım.
Klasör yapımız: lib/features/todo/presentation/
a) State Management / Provider (Garson)
Bu sınıf, UI’dan gelen istekleri alır (müşteriden siparişi alır), Usecase’i çağırır (siparişi şefe iletir) ve gelen sonucu UI’a bildirir (yemeği masaya servis eder).
lib/features/todo/presentation/provider/todo_provider.dart
import 'package:flutter/material.dart';
import '../../domain/entities/todo_entity.dart';
import '../../domain/usecases/get_todos_usecase.dart';
class TodoProvider extends ChangeNotifier {
final GetTodosUsecase getTodosUsecase;
TodoProvider({required this.getTodosUsecase});
bool _isLoading = false;
bool get isLoading => _isLoading;
List<TodoEntity> _todos = [];
List<TodoEntity> get todos => _todos;
// UI bu metodu çağıracak (Müşteri sipariş verdi)
Future<void> fetchTodos() async {
_isLoading = true;
notifyListeners(); // Arayüze "Yükleniyor..." bilgisini gönder
// Usecase'i çağır (Siparişi Şefe ilet)
_todos = await getTodosUsecase.call();
_isLoading = false;
notifyListeners(); // Arayüze yeni veriyi ("yemek hazır" bilgisini) gönder
}
}
b) UI / View (Ekran)
Kullanıcının gördüğü son ürün. Provider’ı dinler ve durumuna göre kendini günceller.
lib/features/todo/presentation/pages/todo_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/todo_provider.dart';
class TodoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Clean Todos')),
body: Consumer<TodoProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: provider.todos.length,
itemBuilder: (context, index) {
final todo = provider.todos[index];
return ListTile(
title: Text(todo.title),
leading: Icon(
todo.isCompleted ? Icons.check_box : Icons.check_box_outline_blank,
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Garsona "Siparişi getir" diyoruz.
context.read<TodoProvider>().fetchTodos();
},
child: Icon(Icons.refresh),
),
);
}
}
Peki Tüm Bu Parçalar Nasıl Birleşiyor?
Bu kadar ayrı parçayı nasıl birbirine bağlayacağız? TodoPage’in TodoProvider’a, TodoProvider’ın GetTodosUsecase’e… ihtiyacı var. Bu işleme Dependency Injection (Bağımlılık Enjeksiyonu) denir. Genellikle get_it gibi bir paketle veya Provider/Riverpod’ın kendi yetenekleriyle yapılır.
main.dart dosyasında tüm bu “servisleri” hazırlayıp uygulamaya sunarız:
// main.dart (çok basitleştirilmiş hali)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import 'features/todo/data/datasources/todo_remote_data_source.dart';
import 'features/todo/data/repositories/todo_repository_impl.dart';
import 'features/todo/domain/usecases/get_todos_usecase.dart';
import 'features/todo/presentation/pages/todo_page.dart';
import 'features/todo/presentation/provider/todo_provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// 1. Provider'ı oluştur.
create: (_) => TodoProvider(
// 2. Provider'ın istediği Usecase'i ver.
getTodosUsecase: GetTodosUsecase(
// 3. Usecase'in istediği Repository'nin Impl'ini ver.
TodoRepositoryImpl(
// 4. Repository'nin istediği DataSource'u ver.
remoteDataSource: TodoRemoteDataSourceImpl(
// 5. DataSource'un istediği http client'ı ver.
client: http.Client(),
),
),
),
),
child: MaterialApp(
title: 'Clean Architecture Demo',
home: TodoPage(),
),
);
}
}
İlk başta çok fazla dosya ve “boilerplate” kod gibi görünebilir. Küçük bir uygulama için aşırı gelebilir. Ama projen büyüdüğünde:
- Bir hata mı var? Nereye bakacağını bilirsin. Arayüz hatasıysa Presentation, iş kuralı hatasıysa Domain, veri gelmiyorsa Data katmanına bakarsın.
- API değişti mi? Sadece DataSource ve Model’i güncellersin. Diğer hiçbir şeye dokunmazsın.
- Uygulamanı test etmek mi istiyorsun? Her katman bağımsız olduğu için kolayca “sahte” (mock) versiyonlarını oluşturup test edebilirsin. Şefin tarifini test etmek için gerçek bir restorana ihtiyacın yok!
Artık yapay zekaya, “Bana clean code ile Firebase auth yap” gibi genel bir komut vermek yerine, çok daha spesifik ve güçlü komutlar verebilirsin:
“Bir AuthRepository arayüzü oluştur. İçinde Either<Failure, User> döndüren bir signIn metodu olsun.”
“Bu AuthRepository arayüzünü implemente eden bir AuthRepositoryImpl yaz. FirebaseAuthDataSource’a bağımlı olsun ve try-catch ile FirebaseAuthException’ları yakalayıp Left(ServerFailure()) olarak dönsün.”
Bu makalede elimden geldiğince ilgili felsefeyi flutter tarafında temel ve basit tutarak anlatmaya çalıştım. Bu konuda değinilebilecek, üzerine konuşabileceğimiz çok fazla konu var. Flutter tarafında ekleyebileceğimiz, kurabileceğimiz çok fazla yapı var ama temelleri atması benden gerisi sizden arkadaşlar.. Artık bu temelleri öğrendikten sonra gidip yapay zekaya;
Bana clean code Firebase Authentication uygulaması yap
demek yerine
Merhaba. Sen senior bir Flutter developer’sın ve Clean Architecture konusunda uzmansın. Projem için Firebase Authentication özelliğini, daha önce oluşturduğum features/auth/ klasör yapısına göre inşa etmeni istiyorum.
Lütfen aşağıdaki katmanları ve sorumlulukları dikkate alarak kodu oluştur:
Domain Katmanı:
Bir UserEntity oluştur.
signIn, signUp, signOut gibi metotları içeren bir AuthRepository arayüzü (abstract class) tanımla. Bu katman Firebase’den tamamen habersiz olsun.
Bu repository’yi kullanan SignInUsecase, SignUpUsecase gibi usecase’ler oluştur.
2. Data Katmanı:
Domain’deki AuthRepository sözleşmesini uygulayan bir AuthRepositoryImpl sınıfı oluştur.
Bu implementasyon, FirebaseAuth paketini kullanan bir AuthRemoteDataSource’a bağımlı olsun.
Hata yönetimini (try-catch) bu katmanda yap ve anlaşılabilecek hatalara (Failures) dönüştür.
3. Presentation Katmanı:
Kullanıcı arayüzü için bir LoginPage ve RegisterPage oluştur.
State management için BLoC kullanarak bir AuthBloc oluştur. Bu BLoC, Domain katmanındaki usecase’leri çağırsın ve UI’ın durumunu (loading, authenticated, error vb.) yönetsin.
Lütfen tüm bu yapıyı oluştur ve son olarak injection_container.dart dosyasında bu yeni sınıfların bağımlılıklarını birbirine bağla.
dersiniz…
Unutmamak gerek değerli arkadaşlar; Zaman içerisinde belirli seviyelere geleceksiniz. Yeterki çalışmayı, azmetmeyi, çalışkan olmayı, pes etmemeyi ve iyi insan olmayı hiç bir zaman bırakmayalım…
Umarım bu makale ilgilisi ve meraklısı için faydalı olmuştur. Bir makalenin daha sonuna geldik. Aklınıza takılan kısımlar olursa yorumlar kısmından sorabilirsiniz.
Sonraki makalelerde görüşmek üzere..
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t