Neden BLoC ve Temiz Mimari?
Flutter ile uygulama geliştirirken, projenin karmaşıklığı arttıkça kod tabanını yönetmek, yeni özellikler eklemek ve hataları ayıklamak giderek zorlaşır. Bu noktada, BLoC (Business Logic Component) ve Temiz Mimari (Clean Architecture) gibi yapılandırılmış bir yaklaşım benimsemek, sadece bir tercih değil, stratejik bir gerekliliktir. Bu mimari desen, uygulamanın farklı bölümlerini birbirinden net sınırlarla ayırarak ölçeklenebilir, sürdürülebilir ve kolayca test edilebilir sistemler oluşturmanın temelini atar.
Bu yaklaşımın temel hedefleri üç ana başlıkta toplanabilir:
-
Sorumlulukların Ayrılması (Separation of Concerns): İş mantığını (business logic), kullanıcı arayüzünden (UI) ve veri kaynaklarından (API, veritabanı vb.) tamamen soyutlar. Bu sayede, UI’da yapılacak bir değişiklik iş mantığını, iş mantığındaki bir değişiklik de UI’ı etkilemez.
-
Test Edilebilirlik (Testability): İş mantığı, UI veya veri katmanından bağımsız hale geldiği için, onu izole bir şekilde test etmek son derece kolaylaşır. Bu, uygulamanızın güvenilirliğini ve kararlılığını artıran anlamlı birim testleri yazmanızı sağlar.
-
Sürdürülebilirlik ve Ölçeklenebilirlik (Maintainability & Scalability): Sorumlulukların net bir şekilde ayrılması, kod tabanının büyük bölümlerini yeniden düzenlemeye gerek kalmadan yeni özellikler eklemeyi veya mevcut özellikleri güncellemeyi kolaylaştırır. Uygulama büyüdükçe bu yapı, karmaşıklığın kontrol altında tutulmasına yardımcı olur.
Bu mimarinin temelini oluşturan BLoC deseninin ana bileşenleri, uygulamanızdaki veri akışını yönetmek için sağlam bir yapı sunar.
BLoC Mimarisi’nin Temel Kavramları
BLoC, sunum mantığını (presentation logic) iş mantığından (business logic) ayırmak için tasarlanmış, olay (event) odaklı bir durum yönetimi desenidir. Temel olarak, UI katmanından gelen olayları dinler, bu olaylara göre gerekli işlemleri yapar ve UI’ın kendini güncellemesi için yeni durumlar (state) yayınlar. Bu tek yönlü veri akışı, uygulamanın durumunu öngörülebilir ve yönetilebilir kılar.

Temiz BLoC Mimarisi: Katmanların Ayrıştırılması
Robert C. Martin (“Uncle Bob”) tarafından popüler hale getirilen Temiz Mimari, yazılım sistemlerini katmanlara ayırarak sorumlulukların net bir şekilde ayrılmasını hedefler. Temel ilke, iş mantığının UI, veritabanı veya ağ istekleri gibi dış bağımlılıklardan tamamen izole edilmesidir. Bu rehberde, bu ilkeyi BLoC ile birleştirerek uygulamayı üç ana katmana ayıracağız: Sunum, İş Mantığı ve Veri.
Sunum Katmanı (Presentation Layer)
Bu katman, kullanıcının gördüğü ve etkileşimde bulunduğu her şeyden sorumludur. Görevi, iş mantığı katmanından gelen durumları alıp ekranda göstermek ve kullanıcı etkileşimlerini iş mantığı katmanına olay olarak iletmektir.
-
Ekranlar ve Widget’lar: Scaffold, Text, Button gibi Flutter widget’ları ile kullanıcı arayüzünü oluşturur.
-
BLoC Etkileşimi: BlocBuilder, BlocListener ve BlocConsumer gibi widget’lar aracılığıyla BLoC’un durum değişikliklerini dinler. Durum değiştikçe UI’ı günceller ve kullanıcı eylemlerine yanıt olarak BLoC’a yeni olaylar (add) gönderir.
BLoC / İş Mantığı Katmanı (Business Logic Layer)
Bu katman, uygulamanın beynidir ve Sunum Katmanı ile Veri Katmanı arasında bir köprü görevi görür. UI’dan tamamen bağımsızdır ve uygulamanın nasıl davranacağını belirleyen kuralları içerir. BLoC sınıflarının kendisi bu katmanda yer alır. Sunum katmanından gelen olayları işler, Veri Katmanı’ndaki Repository’ler aracılığıyla gerekli verileri talep eder, bu verileri işler ve sonuç olarak UI’ın tepki vermesi için yeni durumlar yayınlar.
BLoC’ları presentation katmanında konumlandırmak, özellikle doğrudan UI durumunu yönettikleri için yaygın ve pragmatik bir yaklaşımdır. Ancak, UI’dan tamamen bağımsız, karmaşık ve yeniden kullanılabilir iş kuralları için bu mantığı domain/usecases içinde tutmak daha doğru bir mimari tercihtir. Bu senaryoda BLoC, bu usecase’leri çağırarak sadece sunum ve iş mantığı arasında bir aracı görevi görür.
Veri Katmanı (Data Layer)
Bu katman, uygulamanın ihtiyaç duyduğu tüm verileri sağlamak ve yönetmekten sorumludur. Verinin nereden geldiğini (API, yerel veritabanı, önbellek vb.) uygulamanın geri kalanından soyutlar. Bu katman, Bağımlılıkların Tersine Çevrilmesi Prensibi’ni (Dependency Inversion Principle) uygular.
- Repository’ler (Repositories): İş mantığı katmanının veriyle nasıl etkileşime gireceğini tanımlayan soyut sözleşmelerdir (abstract class). Örneğin, domain katmanındaki abstract class UserRepository, getUser(String id) gibi bir metot tanımlar. İş mantığı katmanı, bu soyut sınıfa bağımlıdır, somut uygulamasına değil.
- Veri Kaynakları (Data Sources): Verinin somut olarak alındığı yerlerdir. RemoteDataSource (API istekleri için) veya LocalDataSource (SQLite, SharedPreferences için) gibi sınıfları içerir. Ham veriyi (örn. JSON) almaktan sorumludur.
- Modeller (Models): Veri kaynaklarından gelen ham veriyi (örn. JSON) Dart nesnelerine dönüştüren sınıflardır. Genellikle fromJson gibi fabrika metotları içerirler.
- Repository Implementasyonları (Repository Implementations): data katmanında bulunan bu sınıflar, domain katmanındaki soyut Repository arayüzünü uygular. DataSource’u kullanarak ham veriyi alır, bunu Model nesnelerine dönüştürür ve son olarak iş mantığı katmanının kullanması için Entity nesnelerine eşleyerek (map) döndürür.
Örnek Proje Yapısı
lib/
├── presentation/
│ ├── bloc/
│ ├── screens/
│ └── widgets/
├── domain/
│ ├── entities/
│ └── repositories/
├── data/
│ ├── datasources/
│ ├── models/
│ └── repositories_impl/
└── main.dart
Bu mimari içindeki temel iletişim birimleri olan Olay ve Durumların nasıl etkili bir şekilde tanımlanacağını bir sonraki bölümde inceleyeceğiz.
Etkili Olay (Event) ve Durum (State) Yönetimi
İyi tanımlanmış Olay (Event) ve Durum (State) sınıfları, öngörülebilir, sürdürülebilir ve hata ayıklaması kolay bir BLoC mimarisi için hayati öneme sahiptir. Bu sınıflar, BLoC ile UI arasındaki iletişimin sözleşmesini oluşturur.
Belirli bir BLoC tarafından işlenecek tüm olayları ortak bir çatı altında toplamak en iyi uygulamadır. Bu, sealed veya abstract bir üst sınıf kullanılarak sağlanır. Her bir olay, bu üst sınıftan türeyen ayrı ve final bir sınıf olarak tanımlanır. Bu yaklaşım, BLoC’un on
part of 'stopwatch_bloc.dart';
abstract class StopwatchEvent extends Equatable {
const StopwatchEvent();
@override
List<Object> get props => [];
}
class StopwatchStarted extends StopwatchEvent {
const StopwatchStarted();
}
class StopwatchPaused extends StopwatchEvent {
const StopwatchPaused();
}
class StopwatchReset extends StopwatchEvent {
const StopwatchReset();
}
Açık Durum Tanımlamaları
Uygulamanın farklı anlarını temsil etmek için belirsizliğe yer bırakmayan, açık ve net durum sınıfları oluşturmak kritiktir. Initial, Loading, Success, Failure gibi durumlar için ayrı sınıflar kullanmak, UI katmanında if (state is UserLoaded) gibi kontrollerle kodu daha okunabilir ve güvenli hale getirir.
- Equatable Kullanımı: Durum sınıflarınızı Equatable’dan türetmek, nesnelerin içeriği aynı kaldığında gereksiz UI yeniden çizimlerini önler. BLoC, yeni bir durum emit ettiğinde, önceki durumla aynı olup olmadığını Equatable’ın props listesine göre kontrol eder. Eğer aynıysa, BlocBuilder gibi widget’lar tetiklenmez.
- copyWith ve Değişmezlik (Immutability): Durum nesneleri değişmez (immutable) olmalıdır. Bu, bir durum nesnesi oluşturulduktan sonra içeriğinin değiştirilemeyeceği anlamına gelir. Bir durumu güncellemek için mevcut nesneyi değiştirmek yerine, copyWith metodunu kullanarak mevcut durumun değerlerini içeren yeni bir kopya oluştururuz. Bu, her durumun ayrı ve öngörülebilir bir anlık görüntü olmasını sağlar, bu da hata ayıklamayı ve durum geçişlerinin güvenilirliğini büyük ölçüde artırır.
class StopwatchState extends Equatable {
const StopwatchState({
this.ticks = 0,
this.status = StopwatchStatus.initial,
});
final int ticks;
final StopwatchStatus status;
@override
List<Object> get props => [ticks, status];
StopwatchState copyWith({
int? ticks,
StopwatchStatus? status,
}) {
return StopwatchState(
ticks: ticks ?? this.ticks,
status: status ?? this.status,
);
}
}
Hata Yönetimi (Error Handling)
Hataları yönetmek için UserLoadFailure veya WeatherFailure gibi özel bir hata durumu sınıfı oluşturmak en iyi stratejidir. Bu sınıf, UI’a gösterilecek hata mesajı (String message) gibi ilgili bilgileri içermelidir. BLoC’un olay işleyicileri (on< Event>) içinde, veri katmanından gelebilecek istisnaları yakalamak için try-catch blokları kullanılmalıdır. Bir istisna yakalandığında, BLoC ilgili hata durumunu (FailureState) emit ederek UI’ın kullanıcıya anlamlı bir geri bildirim göstermesini sağlar.
Olay ve Durumlar tanımlandıktan sonra, bir sonraki adım bu mantık bileşenlerini Flutter widget ağacına doğru bir şekilde sağlamak ve kullanmaktır.
Bağımlılık Enjeksiyonu (Dependency Injection) Stratejileri
Bağımlılık Enjeksiyonu (Dependency Injection — DI), bir nesnenin ihtiyaç duyduğu diğer nesneleri (bağımlılıkları) dışarıdan alması prensibidir. Temiz BLoC mimarisinde DI, katmanlar arasındaki gevşek bağlılığı (loose coupling) sağlamak için kritik bir rol oynar. Bu sayede Repository’leri BLoC’lara, BLoC’ları ise UI katmanına kolayca sağlayabilir ve testlerde sahte (mock) bağımlılıklar kullanabiliriz. flutter_bloc paketi, bu işlemi widget ağacı üzerinden yönetmek için yerleşik çözümler sunar.
Katmanlar Arası Bağımlılıklar
- RepositoryProvider: Bu widget, bir Repository örneğini oluşturur ve altındaki widget ağacının erişimine sunar. Genellikle MaterialApp’in üstünde veya belirli bir özelliğin kök widget’ında kullanılır. Bir BLoC’un bir Repository’ye ihtiyacı olduğunda, RepositoryProvider’ın BlocProvider’ı sarmalaması gerekir.
- BlocProvider: Bir BLoC örneği oluşturmak ve onu altındaki widget’lara sağlamak için kullanılan en temel widget’tır. create metodu içinde, context.read< MyRepository>() kullanarak üst katmanda sağlanan Repository’ye erişebilir ve bunu BLoC’un yapıcısına (constructor) geçirebilir.
- MultiRepositoryProvider ve MultiBlocProvider: Uygulamanızda birden fazla Repository veya BLoC sağlamanız gerektiğinde, iç içe geçmiş sağlayıcıların oluşturduğu “widget cehennemi”nden kaçınmak için bu widget’lar kullanılır. Tek bir widget içinde temiz bir liste halinde birden fazla bağımlılık sağlayabilirsiniz.
Bloc’lara Erişmek
Bir widget içinden, BlocProvider aracılığıyla sağlanan bir BLoC örneğine erişmenin birkaç yolu vardır:
- context.read< MyBloc>()
- BLoC örneğine tek seferlik erişim sağlar. Durum değişikliklerini dinlemez. Genellikle onPressed gibi geri çağırma (callback) fonksiyonları içinde BLoC’a bir olay (add) göndermek için kullanılır.
- context.watch< MyBloc>()
- BLoC’un durumunu sürekli olarak dinler. Durum her değiştiğinde, bu metodu çağıran widget’ın yeniden oluşturulmasını tetikler. Yalnızca durum değişikliğine göre UI’ın güncellenmesi gereken build metotları içinde kullanılmalıdır.
- BlocProvider.of< MyBloc>(context)
- flutter_bloc paketinin eski versiyonlarından kalma geleneksel bir yöntemdir. context.read() ile aynı işlevi görür ve tek seferlik erişim sağlar. Genellikle daha kısa ve modern olan read ve watch uzantıları tercih edilir.
BLoC’lar widget ağacına sağlandıktan sonra, bir sonraki adım UI’ın bu BLoC’ların durum değişikliklerine nasıl tepki vereceğini ve kendini nasıl güncelleyeceğini belirlemektir.
UI Etkileşim Desenleri: Builder, Listener ve Consumer
flutter_bloc paketi, BLoC’un durum akışını UI katmanına verimli ve temiz bir şekilde bağlamak için özel olarak tasarlanmış üç temel widget sunar: BlocBuilder, BlocListener ve BlocConsumer. Her birinin farklı bir kullanım amacı vardır ve doğru widget’ı doğru senaryoda kullanmak, hem kod okunabilirliğini artırır hem de performansı optimize eder.
Ne Zaman Hangisini Kullanmalı?
| İsim | Kullanım Amacı | Önemli Özellik |
|---|---|---|
| BlocBuilder | Yalnızca durum değişikliklerine yanıt olarak UI’ın bir bölümünü yeniden oluşturmak (rebuild) için kullanılır. Örneğin, bir sayaç durumundaki sayı değiştiğinde bir Text widget’ını güncellemek için idealdir. | Performans optimizasyonu için isteğe bağlı olarak bir buildWhen koşulu alabilir. Bu koşul, önceki ve mevcut durumu karşılaştırarak widget’ın yalnızca belirli durumlarda yeniden çizilmesini sağlar, böylece gereksiz yeniden oluşturmaların önüne geçilir. |
| BlocListener | Durum değişikliklerine yanıt olarak SnackBar gösterme, bir diyalog penceresi açma veya başka bir sayfaya yönlendirme (Navigator.push) gibi “tek seferlik” yan etkileri (side effects) tetiklemek için kullanılır. | listener fonksiyonu bir widget döndürmez (void dönüş tipine sahiptir) ve UI’ı yeniden oluşturmaz. Bu nedenle, yeniden çizim gerektirmeyen eylemler için mükemmeldir. |
| BlocConsumer | Hem UI’ı yeniden oluşturmanız (BlocBuilder gibi) hem de bir yan etkiyi tetiklemeniz (BlocListener gibi) gereken durumlarda kullanılır. | Bu widget, BlocBuilder ve BlocListener’ı tek bir widget’ta birleştirir. İki widget’ı iç içe kullanmak yerine BlocConsumer kullanmak, kodun daha temiz ve daha az karmaşık olmasını sağlar. |
UI’ı durum değişikliklerine göre oluşturduktan sonra, bu akışı yöneten BLoC içindeki iş mantığının doğruluğundan emin olmak için güçlü test stratejileri uygulamak gerekir.
Küçük Bir Örnek
BLoC’un temel prensiplerini anlamak için bir sayaç uygulaması yapacağız.

- Proje kurulumu ve paketlerin eklenmesi
- Event ve State Sınıflarının Tanımlanması
- CounterBLoC Sınıfının Oluşturulması
- UI’ın BLoC ile Bağlanması
ADIM 1
öncelikle yeni bir Flutter projesi oluşturup ‘flutter_bloc’ paketini ekleyelim
flutter create bloc_counter_app
cd block_counter_app
flutter pub add flutter_bloc
flutter pub get
ADIM 2
BLoC’umuzun anlayacağı komutları (Event) ve üreteceği sonuçları (State) tanımlayalım.
// Event: Ne olmasını istediğimiz
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {} // Sayacı artır
class DecrementEvent extends CounterEvent {} // Sayacı azalt
class ResetEvent extends CounterEvent {} // Sayacı sıfırla
State’leri tanımlayalım
// State: O anki durumu temsil eder
abstract class CounterState {
final int counterValue;
const CounterState(this.counterValue);
}
class CounterInitialState extends CounterState {
CounterInitialState() : super(0); // Başlangıç değeri 0
}
class CounterUpdatedState extends CounterState {
const CounterUpdatedState(int counterValue) : super(counterValue);
}
ADIM 3
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitialState()) {
on<IncrementEvent>((event, emit) {
emit(CounterUpdatedState(state.counterValue + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterUpdatedState(state.counterValue - 1));
});
on<ResetEvent>((event, emit) {
emit(CounterInitialState());
});
}
}
✅ Böylece:
- Event: Kullanıcının tetiklediği eylemler (IncrementEvent, DecrementEvent, ResetEvent)
- State: Sayacın mevcut değeri (CounterInitialState, CounterUpdatedState)
ADIM 4
bu uygulama BLoC kullanacak ve UI’da butonlara basınca sayacı güncelleyecek.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// BLoC Event
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}
// BLoC State
abstract class CounterState {
final int counterValue;
const CounterState(this.counterValue);
}
class CounterInitialState extends CounterState {
CounterInitialState() : super(0);
}
class CounterUpdatedState extends CounterState {
const CounterUpdatedState(int counterValue) : super(counterValue);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitialState()) {
on<IncrementEvent>((event, emit) {
emit(CounterUpdatedState(state.counterValue + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterUpdatedState(state.counterValue - 1));
});
on<ResetEvent>((event, emit) {
emit(CounterInitialState());
});
}
}
// UI
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => CounterBloc(),
child: CounterScreen(),
),
);
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('BLoC Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.counterValue}',
style: TextStyle(fontSize: 50),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'increment',
child: Icon(Icons.add),
onPressed: () => counterBloc.add(IncrementEvent()),
),
SizedBox(height: 10),
FloatingActionButton(
heroTag: 'decrement',
child: Icon(Icons.remove),
onPressed: () => counterBloc.add(DecrementEvent()),
),
SizedBox(height: 10),
FloatingActionButton(
heroTag: 'reset',
child: Icon(Icons.refresh),
onPressed: () => counterBloc.add(ResetEvent()),
),
],
),
);
}
}
BlocProvider ile BLoC’u UI’ya verdik.
BlocBuilder ile state değişikliklerini dinleyip ekrana yazdırıyoruz.
3 adet FloatingActionButton ile arttırma, azaltma ve sıfırlama işlemlerini tetikliyoruz.

Güçlü Test Stratejileri
BLoC mimarisinin en büyük avantajlarından biri, iş mantığının UI’dan tamamen ayrılması sayesinde sunduğu yüksek test edilebilirliktir. bloc_test paketi, BLoC’ların davranışını test etmek için bildirimsel ve anlaşılır bir API sunar. Bağımlılıkları (örneğin Repository’ler) izole etmek için ise mocktail gibi bir sahte (mocking) kütüphanesi kullanılır.
Bir Bloc Testinin Anatomisi
bloc_test fonksiyonu, bir BLoC’un belirli bir olay dizisine nasıl tepki verdiğini ve hangi durumları yayınladığını doğrulamak için yapılandırılmış bir yol sunar. Bir testin temel bölümleri şunlardır:
-
description: Test senaryosunu açıklayan bir metin (örn. ‘başarılı veri yüklemesinde Loading ve Success durumlarını yayınlar’).
-
build: Test edilecek BLoC’un bir örneğini döndürür. Bağımlılıklar burada sahte (mock) nesnelerle enjekte edilir.
-
setUp (isteğe bağlı): Test çalışmadan önce sahte nesnelerin davranışlarını yapılandırmak için kullanılır (örn. mock repository’nin belirli bir veri döndürmesini sağlamak).
-
act: BLoC’a test edilecek olayları ekleyen bir fonksiyondur.
-
expect: act bloğu çalıştıktan sonra BLoC’un yayınlaması beklenen durumların bir listesini içerir. Sıra önemlidir.
-
verify (isteğe bağlı): BLoC’un bağımlılıklarıyla beklenen şekilde etkileşime girip girmediğini doğrulamak için kullanılır (örn. repository’nin getUser metodunun tam olarak bir kez çağrıldığını kontrol etmek).
Test Senaryoları
Her BLoC için hem başarı hem de hata durumlarını kapsayan testler yazmak kritik öneme sahiptir.
-
Başarı Durumu (Happy Path): Bu senaryoda, BLoC’a bir olay (FetchData) gönderilir ve bağımlılıkların (repository) başarıyla veri döndürdüğü varsayılır. Test, BLoC’un önce bir Loading durumu, ardından veriyi içeren bir Success durumu yayınlayacağını doğrular.
-
Hata Durumu (Failure Path): Bu senaryoda, mock’lanmış repository, bir istisna (Exception) fırlatacak şekilde yapılandırılır. BLoC’a aynı olay gönderildiğinde, test BLoC’un önce bir Loading durumu, ardından hata mesajını içeren bir Failure durumu yayınlayacağını bekler.
Sağlam testler, iş mantığınızın güvenilirliğini garanti altına alır ve gelecekteki değişikliklerin mevcut işlevselliği bozmamasını sağlar.
İleri Seviye Konular ve Diğer En İyi Uygulamalar
Bu bölümde, temel BLoC mimarisini kullanırken geliştirici deneyimini iyileştiren ve uygulama sağlamlığını artıran ek en iyi uygulamalar ve araçlar ele alınmaktadır. Bu teknikler, kodunuzu daha temiz, daha yönetilebilir ve hata ayıklaması daha kolay hale getirecektir.
-
Hata Ayıklama için BlocObserver BlocObserver, uygulamanızdaki tüm BLoC ve Cubit’lerde meydana gelen her türlü değişikliği izlemek için merkezi bir mekanizma sunar. Bir BlocObserver sınıfı oluşturarak onEvent, onChange, onError ve onTransition gibi metotları override edebilirsiniz. Bu, hangi olayın hangi durum geçişine neden olduğunu veya nerede bir hata oluştuğunu görmek için geliştirme sürecinde paha biçilmez bir araçtır. Gözlemciyi etkinleştirmek için main.dart dosyanızda Bloc.observer özelliğine kendi BlocObserver örneğinizi atamanız yeterlidir.
-
Bellek Sızıntılarını Önleme BLoC’lar, özellikle StreamSubscription gibi kaynakları yönetiyorsa, bu kaynakların düzgün bir şekilde temizlenmesi önemlidir. Bir BLoC örneği artık kullanılmadığında close() metodu çağrılır. Bu metodu override ederek, BLoC’un yaşam döngüsü boyunca oluşturulan tüm StreamSubscription’ları veya diğer denetleyicileri iptal etmelisiniz (subscription.cancel()). Bu, uygulamanızda bellek sızıntılarını ve beklenmedik davranışları önler.
-
Olay Dönüşümleri (Event Transformers) BLoC’un en güçlü ve Cubit’ten ayrılan özelliklerinden biri, gelen olay akışını yönetme yeteneğidir. EventTransformer kullanarak, olayların nasıl işleneceğini özelleştirebilirsiniz. Örneğin, bloc_concurrency paketinden gelen debounce veya throttle gibi transformatörler, hızlı bir şekilde art arda tetiklenen olayları yönetmek için kullanılır. debounce, bir olayın yalnızca belirli bir süre boyunca başka bir olay gelmediğinde işlenmesini sağlar ve bu, gerçek zamanlı arama gibi özellikler için idealdir.
Bu mimari desenleri, en iyi uygulamaları ve araçları birleştirerek, geliştiriciler Flutter ile yüksek kaliteli, sağlam ve ölçeklenebilir uygulamalar oluşturmak için iyi donanımlı hale gelir.

BLoC ve Temiz Mimari’yi birleştirerek uygulamanızın farklı katmanlarını net bir şekilde ayırabilir, iş mantığını UI’dan bağımsız hale getirebilir ve yüksek test edilebilirlik ile sürdürülebilir bir yapı oluşturabilirsiniz. Bu sayede Flutter projeleriniz, büyüdükçe bile yönetilebilir, ölçeklenebilir ve kullanıcı deneyimi açısından tutarlı kalır.