“Gerçekten” nedir bu BLoC?

Burcu S
13 min readDec 6, 2022

--

Merhabalar, uzun bir süredir yazmaya değer bir konu bulamamam sebebiyle yazılara ara vermiş oldum. Genellikle herkes tarafından yeterince değinildiğini düşündüğüm konuları yazmayı tercih etmiyorum. Yaklaşık 2 sene önce tanıştığım BLoC konusu için ise geç kaldığımı çünkü bu süre zarfında yeterince içerik yayınlanmış olabileceğini düşünmüştüm. Ancak son zamanlarda flutter öğrenmeye çalışan arkadaşlarıma en çok zorlandıkları konuyu sorduğumda “bloc” aldığım yanıtlar arasında hatrı sayılır bir yere sahipti. İtiraf etmek gerekirse benim de flutter yolculuğumda anlamakta ve verimli bir kullanım sağlayacak kadar hakim olmakta en çok zorlandığım konu kendisiydi. Tüm bunları göz önünde bulundurduğumda bu yazıyı yazmaya girişmiş bulundum :) Kişisel olarak, bir konu anlatılırken sadece güçlü ve değerli teorik bilgilerin verilmesi mantalitesine sıcak bakamayanlardanım. Bana göre bir şeyi yapıyorsak “neden yapıyoruz” bunu bilmek gerekir ki (ya da ben fazla sorgulayıcıyım) yaptığımız şey hakkında, ilgili şeyi farklı senaryolarda da yorumlayabilecek kadar fikrimiz olsun. Ben de bu yazıda elimden geldiğince konunun mantığını, size puzzle çözer gibi hissettirmeden temiz bir şekilde aktarmak için elimden geleni yapacağım. Bunu yaparken yazılımsal kavramları yığınla kullanmaktan kaçınıp (e tabii kodlama kısmına kadar😄) konunun mantığını öz olarak sunabileceğim bir dil kullanmaya çalışacağım :)

Neden BLoC kulllanalım ki?

Bu başlık altında BLoC’ın tarihçesine değinmek gibi bir niyetim yok şimdiden söyleyeyim :D Bu konuyu öğrenmek istiyoruz, evet ama neden ki? Neden bunu yapalım? Daha önce BLoC olmadan kod yazdıysanız backend kodları ve ui kodlarının iç içe, aynı class içinde olduğu durumlara aşinasınızdır. (Flutterı henüz yeni yeni öğrenmeye çalıştığım dönemde bu durum benim kafamı oldukça karıştırmıştı) Biz de istiyoruz ki bu spaghetti kodlardan kurtulalım, ui işlemleri ve backend işlemleri birbirinden ayrı yerlerde daha derli toplu, yönetilebilir ve test edilebilir olsun. İşte BLoC dediğimiz pattern, bunu gerçekleştirmek için geliştirilmiş bir kütüphane. BLoC bir state management kütüphanesidir. Bundan ne anlamalıyız? State management/durum yönetimi kavramını şöyle yorumlayabiliriz: Uygulamamızda oluşacak bazı aksiyonlar sonucu, uygulamamızın o an bulunacağı “durum”lara hakim olup birtakım değişiklikler gerçekleştirmek. O halde BLoC bizlere birtakım “olay”lar karşısında birtakım “durum”lar üretme olanağı sunar.

Yavaş yavaş konuya girelim…

Birtakım “olay”lar karşısında, birtakım “durum”lar üreteceksek eğer bizlere olaylar, durumlar ve bunları birbiri ile ilişkilendireceğimiz, mantık akışını yaratacağımız bir alan/bağlam gerekir. (İşin özü aslında bu cümleden ibaret :)) Olaylar, durumlar ve aralarındaki ilişki… Hmm o zaman bunları biraz konuya uyarlayalım. İşte biz bu “olay, durum, mantık akışı/ilişki” kavramlarını tam olarak BLoC patterninde yer alan events, states ve bloc sınıfı ile yapıyoruz/yapacağız.

Event, States, BLoC Sınıfları

Event classları bizim olaylarımızı, state classları durumlarımızı temsil ederken BLoC sınıfımız ise işin logic kısmını üstlenip “arayüz ve backend” arasında adeta bir arabulucu görevi görecek. Bildiğimiz üzere kullanıcıların uygulamalar ile etkileşimi arayüz katmanında gerçekleşir. Biz de kullanıcının hareketlerine göre bir ‘şeyler’(!) gerçekleştiririz. O halde arayüzümüzden bloclara (ilgili bloc sınıfları) “bak user x butonuna tıkladı” gibi haberler göndereceğiz. (Tabii ki bazen kullanıcıdan bağımsız, programda farklı sınıf ve blocların haberleşmesi de söz konusu olabilir.) BLoC sınıfımız ise bu haberin geldiğini tespit ettiğinde servisler, databaseler, helperlar vs. ilgili sınıflara gidip gerekli bilgileri, aksiyonları alacak, gerekli işlemleri başlatacak ve sonrasında da bu konuyla ilgilenen kim varsa “ben gerekli işlemleri başlattım ve şu an x aşamasındayım (x durumunda/x stateinde)” diyecek (state bildirimi). Bunu duyan sınıflar da ilgili state’e göre gerekli ise bir işlem başlatacak ya da ui güncelleyecek. Konunun mantığını biraz olsun anladık varsayıyorum. (Kabaca user interaction yada app akışı-> bir event üretir -> BLoC -> state üretir -> ui, listenerlar, diğer sınıflar vs. bu bilgiyi kullanır diyebiliriz.)

E dinlemek diyoruz, haber vermek diyoruz, ui güncellemek diyoruz. Peki nasıl yapacağız bunları? Size dinlemek için listenerlar olduğunu, state/duruma göre ui oluşturmak için builderlar olduğunu söylesem konunun aslında ne kadar İngilizce/Türkçe insan dili olduğunu görebilirsiniz sanırım. BLoC patterninde konuştuğumuz her şeyin karşılığı var. Listener (dinleyici), builder (oluşturucu) vs. bu componentler, bize anlatılanları, yazılım tarafında gerçekleştirmemize yardımcı olacak. Hadi o zaman bunlara bir göz atalım.

BLoC Componentleri

BLoC öğrenirken belki de en çok zorlandığım şey componentlerin mantığını iyi oturmaktı. Neyi ne zaman kullanacağımı birilerinin kodlarını inceleyerek çözmeye çalışmak ve anladığımı sandığım yerde kendi projem için uygun bloc yapısını oluşturamamak… (Fizik hocasının çözümünde mantıklı gelen formülü, başka soruda uygulayamamak gibi bir şey…) BLoC pattern biraz da deneyimledikçe optimal kullanıma ulaştığımız bir konu aslında.

BLoC Provider/Multi Bloc Provider:

BlocProvider adı üzerinde bir sağlayıcıdır. Bloc providerlar bize ilgili blocımızı context içerisinde kullanmayı sağlarlar. Bunu bir nevi ilgili blocımızı, uygulamamızın contextine kayıtlandırıyormuşuz gibi düşünebiliriz. Sonuçta kayıtlanmamış şeylerin varlığından haberimiz olmaz ve kullanamayız. BlocProvider.of<T>(context) bize context üzerinden ilgili bloc nesnemize erişim verir. Blocımıza ait bir provider tanımlamadan o blocı ilgili context içinde kullanamayız. Burada BlocProviderın bloca, oluşturduğumuz contextin sub-treelerinde/alt ağaçlarında erişim verdiğini unutmayalım. Yeni başlayanlar bu konsepti anlamadığı için güzel hatalar alabilir :D (kod bölümünde örneklendirdim)

MultiBloc provider ise birden fazla bloc providerı kullanmaya olanak sağlayan bir widgettır. Birden fazla bloc providerın kullanılması gereken senaryolarda iç içe bloc providerlar oluşturmaktan kurtulup daha düzgün kodlar elde edebiliriz. Eğer uygulamamızda birden fazla bloc varsa birbiri içine providerlar yazmak yerine hepsini liste içinde parametre olarak alan tek bir multi bloc provider yazmamız çok daha düzgün bir kod elde etmemizi sağlayacaktır. Aynı zamanda yönetimlerinin kolaylaşması da söz konusu olur. (Ekleme, çıkarma vs.)

BLoC Listener:

Her kavramın isimlendirmesi adeta bir özet gibi. Bloc listenerlar bizim blocımızdaki “değişimleri dinlememizi” sağlayan componentlerdir. Bloc listenerlar genellikle durumlardaki değişimlerin takibi için kullanılır. Listenerların tetiklenmesi için “değişim” gerekir. Bu davranış biçimini, yazdığımız kodlarda loglama yaparak gözlemleyebiliriz. Ne gibi?

BlocListener<BlocA, BlocAState>(
listener: (context, state) {
debugPrint(state);
},
child: Container(),
)

Bu şekilde listenerlarımızın içine eklediğimiz custom loglar, başlarda nasıl davrandığını anlamak için yardımcı olabilir.

BlocListener, örneğin bir hata durumunda uyarı ekranına ya da hata ekranına yönlendirme gibi işlem yapılacaksa kullanılır. Burada işin Türkçesi şudur:

BlocA blocını sürekli dinleyen bir dinleyicimiz BlocA’nın durum değişimlerinden haberdardır. Örneğin BlocA bir başlangıç durumunda iken bir hata durumuna dönüşmüşse bu hata durumunun Bloc tarafında emit edildiği anda bunu yakalar.

Yine bu listener, dinlenen durumlar içerisinde yakalamak istediği bir x durumu olduğunda yapmak istediği bir işlem varsa bunu gerçekleştirir.

Kabaca bir örnek verelim ve açıklayalım.

BlocListener<BlocA, BlocAState>(
listener: (context, state) {
if (BlocAState is StateFail) {
_navigateToMyErrorPage();
}
},
child: Container(),
)

BlocListenerlar her blocı dinleyemez değil mi? Hangisini dinleyeceğini belirtmemiz gerekir. (generic type bildirimi) Burada şunu diyoruz: sen BlocA’yı dinliyorsun evet ve eğer bir fail durumu yakalarsan ben uygulamamın error sayfasına yönlenmesini isterim, bu sebeple bu condition/şartım gerçekleştiğinde ilgili fonksiyonumu çağır.

Şununla karıştırmayalım: Bloclarımızın hangi eventta hangi duruma geçtiğini loglamak istersek bunun için bloc observer kullanırız. Buna kod bölümünde değineceğim.

BLoC Builder:

Bloc konseptinin en çok kullanılan bileşenlerinden biri bloc builderdır. Bloc builder, içinde builder fonksiyonunu bulunduran (ki bu fonksiyon mutlaka bir widget döndürmelidir.) bir bileşendir. Bloc builder, bize state’e göre bir widget gösterme olanağı verir. Bu bileşenlerin nasıl kullanılacağı ile ilgili örnekleri vereceğimiz için şimdilik ne amaçla kullandığımızı bilmekle yetinebiliriz.

Multibloc Listener:

Eğer aynı contextte farklı birkaç bloc listenera ihtiyaç duyuyorsak, bunları multibloclistener çatısı altında tutabiliriz. (MultiBlocProvider da bu konsepte sahip) Multibloc listener, birden fazla bloc listenerı liste olarak parametre alan bir componenttir. Multibloc listenerı kullanarak iç içe bulunan bloc listener widgetlarından kurtulup daha okunulabilir bir kod parçası yazabiliriz.

BLoC Consumer:

Bloc Consumer, eğer bir bloc için hem listener hem de buildera ihtiyaç duyuyorsak ikisini bir arada kullanmamızı sağlar. “E zaten hem listener hem buildera ihtiyacım varsa ikisini de iç içe oluştururum, consumera ne gerek var?” derseniz bu da bir tercih olabilir tabii. Zaten bloc consumer da koddaki gereksiz iç içe kullanımlardan kurtarmak amacıyla kullanılan bir widgettır. Yani iç içe listener ve builder da yazabiliriz, direkt bloc consumer da kullanabiliriz. Tabii burada tercih bloc consumer olsa daha doğru bir kullanım olur 😅.

Kodlara Dökelim :)

Hadi şimdi tüm anlattıklarımızı darta dönüştürerek biraz pekiştirme yapalım… Öncelikle boş bir flutter projesi oluşturalım. Ve bloc paketimizi yaml dosyamıza ekleyelim. Şu an benim lib klasörüm bu şekilde:

Hem bloc builder kullanmanın mantıklı olacağı use caseleri barındıracak hem de birden fazla state/duruma ihtiyaç duyacak bir proje konusu ne olabilir diye düşündüğümde aklıma hava durumu uygulaması geldi. Burada api isteklerini beklerken farklı sonuç geldiğinde farklı bir ui gösterebiliriz.

En basit haliyle bir hava durumu uygulaması yaratmak için öncelikle bize bir hava durumu blocı gerekir. Aynı zamanda isteklerimizi atmak için bir servisimiz olsun. (Burada amaç blocın özünü anlatmak olduğu için konseptten kopmamak adına uygulamaya çok işlevsellik katmayacağım.) Bir de api’ye istek atıp response’u parse edebileceğimiz bir modelimiz olacak. Bloc sınıfları dışındaki sınıfların üzerine çok düşmeyeceğiz. Şunu biliyoruz: Bir bloc pattern için events, states ve bloc sınıfı olmak üzere 3 ana sınıf gerekir. O halde şimdi gerekli dosyalarımızı oluşturalım.

Bloc Yapısı için Sınıfları Oluşturmak:

Öncelikle ön hazırlık olarak apiye istek atmak için birer request, response ve apiden dönen bilgileri parse edeceğim info modeli oluşturdum. Sonrasında bu apiye istek atacağımız bir servis sınıfımız var. Şimdi bloc tarafını düşünelim. Neler gereklidir? Bizi zorlamayacak bir mimari oluşturmak için önceden potansiyel senaryoları düşünmek oldukça önemlidir.

Kendi deneyimlerimden çıkardığım bir sonucu sizlerler paylaşmak istiyorum, eğer bir işlemi bloc ile gerçekleştirirken karmaşık geliyorsa ya da süreci yönetmekte zorlanıyorsanız muhtemelen olayları, durumları kısacası mimariyi uygun şekilde oluşturmamışsınız demektir. Bu yüzden bloc pattern ile çalışırken öncelikle gerçekleşecek/gerçekleşebilecek olayları ve durumların taslağını çıkarmak önemlidir. Tabii ki sonrasında projenin ihtiyacına göre değişiklikler gerekir fakat ilk oluşturma için bu durum önem arz eder.

Events Sınıfı

Öncelikle events sınıfımızı göz önünde bulunduralım. Bunun için ilk olarak soyut bir olaylar sınıfı oluşturacağız sonrasında bundan extend edilen asıl olay sınıflarımızı ekleyeceğiz. Şu an için olayımız usera göstermek adına hava durumu bilgilerini getirmek olabilir. O halde blocımız için event sınıfımızın adı EventWeatherGetInfo olabilir. Biz bu sınıfı bloca bildirip ‘bize şehrin hava durumu bilgilerini getir’ diyeceğiz. O zaman bizim bu eventı bloca bildirirken hangi şehirden bahsettiğimizi de söylememiz gerekir değil mi?

Bu durumda bu sınıfımıza bir şehir bilgisi alanı girmeliyiz ki bu bilgiyi bloc tarafa taşıyabilelim.

İşte şimdi events sınıfımız tamamlandı. Peki neden abstract class oluşturduk ya ezbere mi yapıyorsun bunu? Yoo, hemen söyleyelim. Biz işin logic kısmını yapmak için Bloc sınıfımızı oluştururken aslında bloc paketimizde bulunan Bloc sınıfımızdan miras alacağız. Bu miras alma esnasında generic typeımızda event ve state bildirimi yapmamız gerekmekte. NEDENNN? Çünkü eğer bu generic type bildirimini yapmazsak oluşturacağımız bloc sınıfımız <dynamic, dynamic> tipe sahip bir bloc olur. Bu da bizim belirlediğimiz state ve event classlarının tipiyle uyuşmazlık yaratır ve birçok componenti kullanırken bu konuyla ilgili hata alırız.

Paket içeriğindeki bloc sınıfı

Bir event sınıfını generic type olarak vermemiz gerekiyor ama bizim bir çok eventımız var (şu an için bir tane olsa da başka projelerde birden fazla olacak) e hangi birini vereceğiz? İşte bunun için tüm olaylarımızı temsil edecek soyut bir sınıf oluşturup bunu blocımıza bildiriyoruz. Sonrasında örnekte de gördüğümüz gibi yeni bir olay sınıfı yaratmak isterken de bu abstract classımızdan miras alıyoruz ki bu generic typeımız ile bir uyuşmamazlık yaşamayalım. Aynı durum stateler için de bu şekildedir. Sanırım şimdi oldu :) Hadi devam edelim.

Stateler…

Gelelim state sınıflarımıza. Stateler, appimiz için o bloc bünyesinde yaptığımız eylemler sonucunda oluşabilecek durumları temsil eden sınıflardır. Şimdi kendi use caseimizi düşünelim. Ne yapıyoruz? Bir apiye istek atıp bazı hava durumu bilgilerini alıyoruz. Bu durumda ne olabilir ki? Birincisi başarı ile bu isteği atıp, sonucu alıp kullanıcıya bunu gösterebiliriz. İkincisi, bu isteği atarken bir hata alabiliriz ve kullanıcıya bir hata meydana geldiğine dair bilgilendirme yapabiliriz. O halde bunları göz önünde bulundurarak 2 adet durum oluşur diyebiliriz. Bir de apiden istek dönene kadar bir bekleme durumu oluşabilir. Böylece 3 adet durumumuz var diyebiliriz. Peki ya herhangi bir olayı henüz tetiklememişken bloc sınıfımızdan bir bloc nesnesi üretip kullanıma hazır hale getirirken o esnada durum ne olur? Bu durum için de bloc sınıfımıza bir başlangıç durumu atayabiliriz. Böylece artık 4 durum sınıfı oluşturmamız gerektiğini anladık. Şimdi bir de şunu düşünelim bloc sınıfımıza eventımız vasıtasıyla “kullanıcı x şehri için hava durumlarını istiyor, bunları getir” dedik. Bloc sınıfımız ise bir arabirim, haberci gibi davranarak ilgili api servisine bu isteği iletip sonucu alacak ve app bazında “artık sonuçlar hazır” ya da “apiden sonuçları getirirken hata çıktı” gibi durumlara dönüştürecek. Bu durumda, bunları bildirirken eğer istek başarılıysa bu başarılı sonucun değerlerine erişebilmeli ve bir WeatherInfo (api responseunu parse etmek için oluşturduğumuz model sınıfı) sınıfı nesnesi aracılığıyla ilgili bilgilere sahip olmalıyız. Burada yazdığımız apinin responseunu incelemekte fayda var. Örneğin bizim kullanacağımız api 7 günlük bir hava durumu bildirimi sunar. Dolayısıyla burada kullanıcıya bir List<WeatherInfo> değişkeni üzerinden bilgileri göstermemiz gerekir. Aynı şekilde bir hata durumunda kullanıcıya hata mesajımızı göstermek istiyorsak, bunun için de failed durumuna bu mesajı atayabileceğimiz bir alan vermemiz şarttır.

Bu bilgiler ışığında statelerimizin son hali:

import 'package:bloc_example/models/weather_info.dart';
abstract class StateWeather {}class StateWeatherInitialize extends StateWeather {}class StateWeatherInfoFetching extends StateWeather {}class StateWeatherInfoFetched extends StateWeather {
final List<WeatherInfo> weatherInfoList;
StateWeatherInfoFetched(this.weatherInfoList);
}
class StateWeatherFailed extends StateWeather {
final String errorMessage;
StateWeatherFailed(this.errorMessage);
}

şeklinde olur diyebiliriz.

NŞA’da kodlama yaparken naming convention benim için oldukça önemlidir. State sınıfı için ise naming conventionda isimlerin kullanılması önerilir çünkü bahsi geçen durum bir nevi o “an”ın temsilidir. Örneğin bir işlem tamamlanmışsa ProcessCompleted değil ProcessComplete olmalıdır. Ancak ben state sınıflarımı kişisel insiyatifimle bu şekilde anlandırıyorum. Lütfen siz naming conventionları buradan inceleyin. Sonrasında kendi kararınızı verin :)

Bloc Sınıfı

Gelelim işin logic kısmının döndüğü asıl sınıfımız olan Bloc sınıfımıza. Projenin akışını, eventları, stateleri vs. önceden düşünüp iskeletimizi oluşturduktan sonra bloc sınıfımızı yazmak oldukça kolay olacaktır. (en azından bu ölçütte bir proje için :D) Önceden bloc sınıfı mapEventToState adında eventları state’e dönüştüren ve stateleri yield eden streame sahipti. Burada developerlar için, stream, yield, yield*, async* gibi kavramların öğrenme yükünü azaltmak, iç içe async işlemlerde hangi durumun olacağının bazen öngörülememesi gibi sebeplerle stream yerine “event handler”ların kullanılmasına karar verildi.

Event handlerlar (on<YakalanmasınıİstediğimizEvent>) hangi olayda ne işlem yapacağımızı bildirmemize olanak sağlar. Böylece olay karşısında alınacak aksiyonun öngörülebilir olması için bir olay ile ((Event event, Emitter<State>) {} şeklinde) bir callbacki eşleştirebiliriz. Bu callbackler asenkron olabilir. Aynı zamanda callback üzerinden tetiklenen event/olaya erişebildiğimiz için o eventın field değerlerine de erişebiliriz. Ne demek istiyorum?

Bloc sınıfımız yukarıdaki gibi. Özetlemek gerekirse bloc için bir constructor tanımladık ve default olarak initialize durumu ile ilgili bloc nesnemizi başlatmaya olanak sağladık.

on<EventWeatherGetInfo>(_getWeatherInfo);

Satırı ile bir event handler tanımladık. Aslında burada, eğer EventWeatherGetInfo eventını yakalarsan _getWeatherInfo callbackini tetikle şeklinde bir tanımlama gerçekleştirdik. Şimdi bu callback değerimizi inceleyecek olursak…

Future<void> _getWeatherInfo(EventWeatherGetInfo event, emit) async {
emit(StateWeatherInfoFetching());
ApiResponse response = await WeatherApiService.fetchWeatherInfo(event.city);
if (response.success) {
emit(StateWeatherInfoFetched(response.result!));
} else {
emit(StateWeatherFailed(response.message ?? ''));
}
}

Buradaki kodun oldukça okunabilir olduğunu düşünüyorum ancak özetlemek gerekirse: Öncelikle asenkron bir işlem başlatacağımız ve bunun ne kadar süreceğini bilemediğimiz için hızlıca bir bekleme durumuna geçtiğimizi belirten statei emit ediyoruz. (Aslında bloc builderı anlatmak için biraz bahane :)) Sonrasında apiye istek atıyoruz. Sonucumuz başarılı ise başarılı olma durumuna geçip bu ilgili statein de fieldına responsedan dönen bilgimizi (hava durumları listesi) veriyoruz. Eğer başarılı değil ise de fail stateine geçip yine errorMessage olarak constructorına eklediğimiz değeri giriyoruz.

Burada eventın fieldına nasıl eriştiysek (event.city kullanımı) state sınıflarına field olarak verdiğimiz bu değerlere de ui tarafında aynı kullanım ile erişeceğiz (state.errorMessage gibi).

Artık bloc sınıfımız hazır. Umarım neyi neden yaptığımızı ve nasıl yapacağımızı anlamışsınızdır :) (Anlamadıysanız da sorun değil, buyrun iletişime geçelim). Artık anlatımımıza UI kısmıyla devam edebiliriz.

UI, BLoC Componentleri

Bloc mimarimizi oluşturduk, şimdi ise bunu projenin presentation katmanında nasıl kullanabiliriz sorusuna bakalım.

Şimdi böyle baya ince düşünülmüş bir UI’ımız olsun :D (Kodların ss olmasına takılmayalım repoyu paylaşacağım. )

Burada, butonumuza tıkladığımızda tetiklenecek bir fonksiyonumuz var, gördüğümüz üzere adı da _getWeatherInfo(). Biz bu fonksiyon içerisinden ilgili blocımıza bir event göndereceğiz. Bunu gönderirken textfieldımız içerisindeki şehir bilgisini de ileteceğiz.

Bloc tarafta bir eventı nasıl yakaladığımızı (event handlerlar ile) anlatmıştım. Peki bloc’a eventı nasıl iletiriz? Bunun için Bloc nesnesi üzerinden .add(EventSınıfımız(field: fieldDeğerimiz)) şeklinde bir kullanım yapabiliriz. Bir başka deyişle add ile bloclarımızda olay tetikleme gerçekleştiririz. O halde bizim bunu yapmak için bu bloc değerine erişimimiz gereklidir. Alt ağaçlardan bloc nesnesine erişmek için bloc provider üzerinden bir bloc sağlayıcı oluşturmamız gerekir ki biz bu blocı kullanabilelim. Ben bu providerımı, üzerinde çalıştığım PageMain’imi kapsayan main widgetta ekledim ki alt ağaçlardaki contextler üzerinden erişimimim olabilsin.

main.dart içeriği

Şimdi peki bu provider üzerinden PageMainimde nasıl bir erişim gerçekleştirebilirim? Bunu şöyle yapabiliriz. Main sınıfımızın init state’i içerisinde contextten okunup alınmak üzere bir BlocWeather tipinde nesne oluşturabiliriz. Şöyle ki:

late BlocWeather _blocWeather;
@override
void initState() {
super.initState();
_blocWeather = BlocProvider.of<BlocWeather>(context);
}

late ile sonradan değerinin atanacağını bildirip, initStateimizde bu atamayı gerçekleştirdik.

void _getWeatherInfo() {
_blocWeather.add(EventWeatherGetInfo(_textController.text));
}

İlgili butonumuzun fonksiyonu içerisinde de yukarıdaki gibi bir kullanımla olayımızı tetikleriz. Ne yapıyoruz? add ile bir nevi “sana şu olayı gönderiyorum“ mesajını blocımıza iletiyoruz. Buradaki _textController.text ifadesi ise daha önce EventWeatherGetInfo sınıfımız için oluşturduğumuz city değeridir, bunu constructorda set ediyoruz. İşte bu kadar basit.

Şu ana kadar söz konusu iletişimi nasıl yapacağımızı düşündük. Bir de şöyle bir şey gerçekleştirelim: Butonumuzun hemen altında, bize bilgi gelirken bir yükleniyor ibaresi görünsün, sonuç geldiğinde ise bu bilgileri görelim. O halde aslında burada ne oluyor? Önceden belirlediğimiz bloc statelerine göre farklı widgetlar gösterme gereksinimi duyuyoruz. Dikkat ederseniz bu use case tam olarak BlocBuilderın tanımındaki senaryoya örnektir. Bu sebeple butonumuzun hemen altına bir bloc builder eklememiz gerektiği sonucunu çıkarabiliriz.

BlocBuilder<BlocWeather, StateWeather>(builder: (context, state) {})

Uygulamamızın ilgili bölümünde, builderımız içerisinde kendi koyduğumuz state conditionlarına göre istediğimiz widgetları döndürmeliyiz. O halde yukarıda önceden tasarladığımız akışa göre return edeceğimiz widgetlarımızı ekleyelim:

Artık uygulamamızı tümüyle tamamladık.

Eğer main.dartta providerımızı tanımlamasaydık: BlocProvider.of() called with a context that does not contain a BlocWeather. şeklinde bir hata alacaktık.

Tüm yaptıklarımız sonucunda uygulamamızın son hali:

Yazı burada bitiyormuş gibi görünüyor (ben de bitti sanmıştım 😅) ama son olarak bloc kullanırken hangi olayın tetiklendiğini, hangi duruma geçildiğini kısacası blocımız içerisinde neler olup bittiğini anlamımızı sağlayacak bloc observerdan da bahsedelim.

Bloc Observer

Bloc observer sınıfı bloc paketinde bulunan ve içerisinde bloclarda gerçekleşen error, change gibi durumları alarak bazı işlemler gerçekleştirmemizi sağlayan, kısacası bloc/larımızı gözlemlemiz için kullanılan bir soyut sınıftır. Uygulamalarımız için bu sınıfın metotlarını override ederek kendi custom observer sınıfımızı yazabiliriz.

Bunun için:

import 'package:flutter_bloc/flutter_bloc.dart';

class CustomBlocObserver extends BlocObserver {}

şeklinde BlocObserverdan miras alan CustomBlocObserver sınıfı oluşturabiliriz. Burada her metodu override etmek zorunda değiliz, ihtiyaç duyduklarımız bizim için yeterli olacaktır. Ben 3 önemli metodu override ederek sınıfımızı tamamlıyorum.

import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CustomBlocObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
debugPrint('onEvent: $event');
}

@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
debugPrint('onTransition $transition');
}

@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
super.onError(bloc, error, stackTrace);
debugPrint('onError $error');
}
}

Sınıfımızı oluşturduk bir de sınıfımız ile Bloclar arasında bir bağlantı gerekir. Bunu da main metodumuz içerisinde gerçekleştirebiliriz. Böylece:

void main() {
runApp(const MyApp());
}

olan sınıfımıza

void main() {
Bloc.observer = CustomBlocObserver();
runApp(const MyApp());
}

ilgili satırı ekleyerek bu bağlantıyı tamamlamış oluruz.

Bloc observer output

Tüm sürecin sonunda artık loglarımızda ‘bloc observer output’ görselinde de görüldüğü gibi blocımızın state geçişlerini vs. daha rahat gözlemleme şansı elde etmiş olduk.

Yazımızı burada noktalayabiliriz. Umarım işin temelinin nasıl bir mantıkla işlediğini aktarabilmişimdir, teşekkürler :)

Herhangi bir soru için LinkedIn üzerinden iletişime geçebilirsiniz.

Proje repoya buradan erişebilirsiniz.

Daha fazlası için: resmi dokümandan yardım alın.

--

--

Burcu S

Flutter Developer, Lover & Learner | Computer Engineer | For contact: linkedin.com/in/burcus/