Flutter Sayaç
Bu eğitimde, Bloc kütüphanesini kullanarak Flutter ile bir Sayaç uygulaması oluşturacağız.

Önemli Konular
Section titled “Önemli Konular”- BlocObserver ile durum değişikliklerini gözlemleme.
- BlocProvider, alt widget'larına bir bloc sağlayan Flutter widget'ı.
- BlocBuilder, yeni durumlara karşılık widget'ın yeniden oluşturulmasını yöneten Flutter widget'ı.
- Bloc yerine Cubit kullanma. Aralarındaki fark nedir?
- context.read ile olay ekleme.
Kurulum
Section titled “Kurulum”Yepyeni bir Flutter projesi oluşturarak başlayacağız
flutter create flutter_counterArdından pubspec.yaml dosyasının içeriğini şununla değiştirebiliriz
name: flutter_counterdescription: A new Flutter project.version: 1.0.0+1publish_to: none
environment: sdk: ^3.12.0
dependencies: bloc: ^9.0.0 flutter: sdk: flutter flutter_bloc: ^9.1.0
dev_dependencies: bloc_lint: ^0.3.0 bloc_test: ^10.0.0 flutter_test: sdk: flutter integration_test: sdk: flutter mocktail: ^1.0.0
flutter: uses-material-design: trueve sonrasında tüm bağımlılıklarımızı kuralım
flutter pub getProje Yapısı
Section titled “Proje Yapısı”├── lib│ ├── app.dart│ ├── counter│ │ ├── counter.dart│ │ ├── cubit│ │ │ └── counter_cubit.dart│ │ └── view│ │ ├── counter_page.dart│ │ ├── counter_view.dart│ │ └── view.dart│ ├── counter_observer.dart│ └── main.dart├── pubspec.lock├── pubspec.yamlUygulama, özellik odaklı bir dizin yapısı kullanır. Bu proje yapısı, kendi içinde bağımsız özelliklere sahip olarak projeyi ölçeklendirmemizi sağlar. Bu örnekte yalnızca tek bir özelliğimiz olacak (sayacın kendisi), ancak daha karmaşık uygulamalarda yüzlerce farklı özelliğimiz olabilir.
BlocObserver
Section titled “BlocObserver”İlk inceleyeceğimiz şey, uygulamadaki tüm durum değişikliklerini gözlemlememize
yardımcı olacak bir BlocObserver'ın nasıl oluşturulacağıdır.
lib/counter_observer.dart dosyasını oluşturalım:
import 'package:bloc/bloc.dart';
/// {@template counter_observer}/// [BlocObserver] for the counter application which/// observes all state changes./// {@endtemplate}class CounterObserver extends BlocObserver { /// {@macro counter_observer} const CounterObserver();
@override void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) { super.onChange(bloc, change); // ignore: avoid_print print('${bloc.runtimeType} $change'); }}Bu durumda, gerçekleşen tüm durum değişikliklerini görmek için yalnızca
onChange metodunu geçersiz kılıyoruz.
main.dart
Section titled “main.dart”Sırada, lib/main.dart dosyasının içeriğini şununla değiştirelim:
import 'package:bloc/bloc.dart';import 'package:flutter/widgets.dart';import 'package:flutter_counter/app.dart';import 'package:flutter_counter/counter_observer.dart';
void main() { Bloc.observer = const CounterObserver(); runApp(const CounterApp());}Az önce oluşturduğumuz CounterObserver'ı başlatıyor ve birazdan
inceleyeceğimiz CounterApp widget'ı ile runApp'i çağırıyoruz.
Counter App
Section titled “Counter App”lib/app.dart dosyasını oluşturalım:
CounterApp bir MaterialApp olacak ve home olarak CounterPage'i
belirtecektir.
import 'package:flutter/material.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_app}/// A [MaterialApp] which sets the `home` to [CounterPage]./// {@endtemplate}class CounterApp extends MaterialApp { /// {@macro counter_app} const CounterApp({super.key}) : super(home: const CounterPage());}Sırada CounterPage'e bakalım!
Counter Page
Section titled “Counter Page”lib/counter/view/counter_page.dart dosyasını oluşturalım:
CounterPage widget'ı, bir CounterCubit (birazdan inceleyeceğiz)
oluşturmaktan ve bunu CounterView'a sağlamaktan sorumludur.
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_page}/// A [StatelessWidget] which is responsible for providing a/// [CounterCubit] instance to the [CounterView]./// {@endtemplate}class CounterPage extends StatelessWidget { /// {@macro counter_page} const CounterPage({super.key});
@override Widget build(BuildContext context) { return BlocProvider( create: (_) => CounterCubit(), child: const CounterView(), ); }}Counter Cubit
Section titled “Counter Cubit”lib/counter/cubit/counter_cubit.dart dosyasını oluşturalım:
CounterCubit sınıfı iki metot sunacak:
increment: mevcut duruma 1 eklerdecrement: mevcut durumdan 1 çıkarır
CounterCubit'in yönettiği durumun türü yalnızca bir int'tir ve başlangıç
durumu 0'dır.
import 'package:bloc/bloc.dart';
/// {@template counter_cubit}/// A [Cubit] which manages an [int] as its state./// {@endtemplate}class CounterCubit extends Cubit<int> { /// {@macro counter_cubit} CounterCubit() : super(0);
/// Add 1 to the current state. void increment() => emit(state + 1);
/// Subtract 1 from the current state. void decrement() => emit(state - 1);}Sırada, durumu tüketmekten ve CounterCubit ile etkileşim kurmaktan sorumlu
olacak CounterView'a bakalım.
Counter View
Section titled “Counter View”lib/counter/view/counter_view.dart dosyasını oluşturalım:
CounterView, mevcut sayacı görüntülemekten ve sayacı artırmak/azaltmak için
iki FloatingActionButton oluşturmaktan sorumludur.
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_view}/// A [StatelessWidget] which reacts to the provided/// [CounterCubit] state and notifies it in response to user input./// {@endtemplate}class CounterView extends StatelessWidget { /// {@macro counter_view} const CounterView({super.key});
@override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Scaffold( body: Center( child: BlocBuilder<CounterCubit, int>( builder: (context, state) { return Text('$state', style: textTheme.displayMedium); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: <Widget>[ FloatingActionButton( key: const Key('counterView_increment_floatingActionButton'), child: const Icon(Icons.add), onPressed: () => context.read<CounterCubit>().increment(), ), const SizedBox(height: 8), FloatingActionButton( key: const Key('counterView_decrement_floatingActionButton'), child: const Icon(Icons.remove), onPressed: () => context.read<CounterCubit>().decrement(), ), ], ), ); }}CounterCubit durumu her değiştiğinde metni güncellemek için Text widget'ı
bir BlocBuilder ile sarmalanmıştır. Ek olarak, en yakın CounterCubit
örneğini bulmak için context.read<CounterCubit>() kullanılır.
Barrel
Section titled “Barrel”lib/counter/view/view.dart dosyasını oluşturun:
Counter view'in tüm dışa açık parçalarını dışa aktarmak için view.dart
dosyasını ekleyin.
export 'counter_page.dart';export 'counter_view.dart';lib/counter/counter.dart dosyasını oluşturalım:
Counter özelliğinin tüm dışa açık parçalarını dışa aktarmak için counter.dart
dosyasını ekleyin.
export 'cubit/counter_cubit.dart';export 'view/view.dart';İşte bu kadar! Sunum katmanını iş mantığı katmanından ayırdık. CounterView,
bir kullanıcı bir düğmeye bastığında ne olacağını bilmez; sadece
CounterCubit'i bilgilendirir. Üstelik CounterCubit de durumla (sayaç değeri)
ne olduğundan habersizdir; çağrılan metotlara karşılık olarak yalnızca yeni
durumlar yayar.
Uygulamamızı flutter run ile çalıştırabilir ve cihazımızda veya
simülatör/emülatörde görüntüleyebiliriz.
Bu örneğin tam kaynak kodu (birim ve widget testleri dahil) buradan ulaşılabilir.