Flutter
Flutter'da BLoC ile Ölçeklenebilir Uygulamalar
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 ve Temiz Mimari gibi yapılandırılmış bir yaklaşım benimsemek yalnızca bir tercih değil, stratejik bir gerekliliktir. Bu mimari desen, uygulamanın farklı bölümlerini net sınırlarla ayırarak ölçeklenebilir, sürdürülebilir ve kolay test edilebilir sistemler kurmanın temelini atar.
Bu yaklaşımın temel hedefleri üç başlıkta toplanabilir:
- Sorumlulukların ayrılması: İş mantığını kullanıcı arayüzünden ve veri kaynaklarından soyutlar.
- Test edilebilirlik: İş mantığını izole test etmeyi kolaylaştırır.
- Sürdürülebilirlik ve ölçeklenebilirlik: Yeni özellikler eklerken karmaşıklığı kontrol altında tutar.
BLoC Mimarisi’nin Temel Kavramları
BLoC, sunum mantığını iş mantığından ayırmak için tasarlanmış olay odaklı bir durum yönetimi desenidir. UI katmanından gelen olayları dinler, gerekli işlemleri yapar ve UI’ın kendini güncellemesi için yeni durumlar yayınlar. Bu tek yönlü veri akışı, uygulamanın durumunu daha öngörülebilir hale getirir.

Temiz BLoC Mimarisi: Katmanların Ayrıştırılması
Temiz Mimari, yazılım sistemlerini katmanlara ayırarak sorumlulukların net şekilde ayrılmasını hedefler. Temel ilke, iş mantığının UI, veritabanı veya ağ istekleri gibi dış bağımlılıklardan izole olmasıdır. Bu rehberde bu ilkeyi BLoC ile birleştirerek uygulamayı sunum, iş mantığı ve veri katmanlarına ayırıyoruz.
Sunum Katmanı
Bu katman, kullanıcının gördüğü ve etkileşimde bulunduğu her şeyden sorumludur.
- Flutter widget’ları ile arayüzü oluşturur.
BlocBuilder,BlocListenerveBlocConsumergibi yapılarla BLoC durumlarını dinler.- Kullanıcı etkileşimlerini olay olarak BLoC’a iletir.
İş Mantığı Katmanı
Bu katman uygulamanın beynidir. Sunum katmanından gelen olayları işler, repository’ler üzerinden veri talep eder ve sonuç olarak yeni durumlar yayınlar.
Karmaşık ve yeniden kullanılabilir iş kuralları için mantığı domain/usecases içinde tutmak daha doğru bir tercihtir. Bu senaryoda BLoC, use case’leri çağıran bir aracı gibi davranır.
Veri Katmanı
Bu katman, uygulamanın ihtiyaç duyduğu tüm verileri sağlar ve yönetir.
- Repository’ler: İş mantığının veriyle nasıl konuşacağını tanımlayan soyut sözleşmelerdir.
- Veri kaynakları: API, yerel veritabanı veya önbellek gibi somut veri giriş noktalarıdır.
- Modeller: Ham veriyi Dart nesnelerine dönüştürür.
- Repository implementasyonları: Veri kaynaklarından gelen çıktıyı iş mantığının kullanacağı yapılara dönüştürür.
Örnek Proje Yapısı
lib/
├── presentation/
│ ├── bloc/
│ ├── screens/
│ └── widgets/
├── domain/
│ ├── entities/
│ └── repositories/
├── data/
│ ├── datasources/
│ ├── models/
│ └── repositories_impl/
└── main.dart
Etkili Olay ve Durum Yönetimi
İyi tanımlanmış olay ve durum sınıfları, öngörülebilir ve sürdürülebilir bir BLoC mimarisi için kritiktir.
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();
}
Durum tarafında Equatable, copyWith ve immutability kullanmak gereksiz yeniden çizimleri azaltır ve durum geçişlerini daha güvenilir hale getirir.
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
Hata durumları için özel state sınıfları oluşturmak en temiz yaklaşımdır. BLoC olay işleyicileri içinde try-catch kullanarak veri katmanından gelen hataları yakalayabilir ve UI’a anlamlı geri bildirim gösterebilirsiniz.
Bağımlılık Enjeksiyonu Stratejileri
Bağımlılık enjeksiyonu, katmanlar arasındaki gevşek bağlılığı korumak için kritik rol oynar.
RepositoryProviderrepository örneklerini sağlar.BlocProviderBLoC örneklerini widget ağacına verir.MultiRepositoryProviderveMultiBlocProviderdaha temiz bir kurulum sunar.
UI Etkileşim Desenleri
flutter_bloc paketi üç temel yapı sunar:
| İsim | Kullanım amacı | Önemli özellik |
|---|---|---|
BlocBuilder | Durum değiştikçe UI’ın ilgili kısmını yeniden oluşturur. | buildWhen ile optimizasyon yapılabilir. |
BlocListener | SnackBar, yönlendirme veya diyalog gibi tek seferlik yan etkiler için kullanılır. | UI yeniden oluşturmaz. |
BlocConsumer | Hem yeniden çizim hem de yan etki gerektiğinde kullanılır. | BlocBuilder ve BlocListener davranışını birleştirir. |
Küçük Bir Örnek
BLoC’un temel prensiplerini göstermek için küçük bir sayaç uygulaması:

- Yeni proje oluşturup
flutter_blocpaketini ekleyin. - Event ve state sınıflarını tanımlayın.
CounterBlocsınıfını oluşturun.- UI’ı BLoC ile bağlayın.
flutter create bloc_counter_app
cd block_counter_app
flutter pub add flutter_bloc
flutter pub get
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}
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);
}
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());
});
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}
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);
}
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());
});
}
}
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()),
),
],
),
);
}
}
Test Stratejileri
bloc_test ve mocktail gibi araçlarla başarı ve hata senaryolarını kapsayan testler yazmak, iş mantığının güvenilirliğini ciddi biçimde artırır.
Sonuç
BLoC ve Temiz Mimari birlikte kullanıldığında, uygulamanın farklı katmanlarını net şekilde ayırabilir, iş mantığını UI’dan bağımsız tutabilir ve uzun vadede daha sürdürülebilir Flutter projeleri geliştirebilirsiniz.