Flutter Networking, Pagination with BLOC State Management | Part 1

Salih Can
Flutter Türkiye
Published in
7 min readDec 20, 2021

Selamlar 👋 Bu makale de hep birlikte Flutter genelinde biraz ileri seviye bilgilere değineceğiz. Bu değineceğim bilgilere başlamadan önce aşağıdaki listeden kısa bir araştırma yapmanızı tavsiye ederim. (Müsait zamanımda aşağıdaki başlıkları kendi yöntemim ile de açıklamak istiyorum)

Başlamadan önce BLOC konusunda biraz pratik yapmak ve biraz daha başlangıç seviyesinden başlamak isterseniz Göktuğ Özdemir’in yazdığı aşağıdaki makaleyi tavsiye ederim.

Ayrıca GDG Ankara Devfest’21 de anlatımda kullandığım sunum dosyasına aşağıdan erişebilirsiniz.

Evet, yukarıda ki başlıklar hakkında bilgi sahibi olduğunu varsayarak makaleye başlamak istiyorum. Öncelikle her zamanki gibi makale içeriğini yazarak başlayalım.

  • Blok Modeli Uygulaması
  • Ortak Seviye Blok Paketini Kullanma
  • Dosya Yapısı Oluşturma
  • Ağ Katmanı Oluşturma
  • Test Edilebilir Kod Yapısı Tüyoları
  • Sıfırdan Mimari Kurgulanarak Projeye Oluşturma Düşüncesi
  • Örnek Tamamlanmış Proje

Bu örnek anlatım için 3 ana katman oluşturacağız bunlar Data, Domain ve Presentation katmanları olarak ayrılıyor.

  • Data Katmanı: API bağlantısı kurulan servis yapılarının, veri sağlayıcıların yani Repository sınıflarının arayüzlerinin barındığı non-logic katmandır.
  • Domain Katmanı: Projemize özgü katmanların, kodların bulunduğu ana katmandır. Bu katmanda projeye özgü Repository sınıfları, api servis entegrasyonları, yerel depolama entegrasyonları, json model sınıflar gibi yapıların yer aldığı katmandır.
  • Presentation Katmanı: Ekranda görünen bileşenlerin(component/widget) UI(User Interface) ile ilgili işlemlerin yer aldığı katmandır. Bu katmanda mantıksal işlemler bulunmamalıdır.

Yukarıda bahsettiğim katmanlar BLOC veya State Management terimleri ile ilgisi yoktur. Bu yapı daha çok sürdürülebilir dosya yapısı olarak geçebilir. Bu yapı üzerine birden fazla geliştirici rahat şekilde çalışabilir veya dosya karmaşasının önüne geçilebilir. Aynı zamanda kullanacağımız MVVM veya Bloc pattern için en uygun dosya yapısıdır.

Proje oluşturulduğunda ilk olarak açtığım dosyaları aşağıda görebilirsiniz.

Yukarıdaki dosya yapısından app.dart ve main.dart dosyaları var. Main.dart flutter uygulamasını başlatmaktan, app.dart ise Widget ağacını oluşturmaktan sorumlu sınıflardır.

Dosya yapısını oluşturduk peki nereden başlamalıyım? Eğer mobil uygulama geliştirme sektörüne yeni adım atmaya başladıysanız, bu sorunun cevabını vermek zor olacaktır. Ben elimden geldiğince nasıl düşündüğümü yazmak istiyorum.

  1. Profesyonel olarak bir ekipte çalışıyorsanız UI/UX birimleri vardır ve sizlere tema yapısını birincil ve ikincil renk tonlarını, font ailesini, kullanılan metin boyutlarını içeren bir dökümantasyon verecektir. Bu durum çoğunlukla olmuyor, bu sebepten tema oluşturma için ayrı bir makalede bahsetmek istiyorum, ama birinci aşama tasarıma tema yapısını kurgulamak olmalıdır.
  2. Eğer uygulamanız statik verilerden ibaret değil ise yani bir uzak sunucudaki veritabanından bilgilere göre ekranı güncelliyorsanız. Data katmanıyla başlamak en doğrusudur, çünkü bu katman olmadan yaptığınız static bir yapıdan ibaret kalacaktır. Peki neden ilk UI oluşturmamalıyız? UI katmanı bu projemiz de Data katmanından gelen verilere, json modellere göre kurgulanması gerektiği için yani UI katmanı data katmanına bağımlı olduğu için öncelikle bu katmanı kurgulamalıyız.
  3. Repository sınıflarını oluşturulması, data katmaında ham verileri elde ediyoruz ancak bu verileri UI katmanının anlayabileceği şekilde ayarlanması gerekiyor, bu sebepten dolayı sıra bu katmanda.
  4. Ekranda ki state içeren bileşenleri belirlenmesi. Bizim yapmaya çalıştığımız ekranda sadece bir liste bileşeni var bu yüzden sadece ilgili model listesini tutan bir state ihtiyacımız var. Başka ne olabilirdi ? Mesela AppBar’da listeden bir item’a tıklandığında sepette ki ürün sayısını artıran bir yapı olabilirdi. Bu yapı için de ayrı bir state gerekiyor, bu da demektir ki ayrı bir BLOC kurgulanması.
  5. Kurgulayacağımız durumu(List<Model>) belirledikten sonra BLOC katmanının repository ile kurgulamamız gerekiyor.
  6. Mantık katmanı yani BLOC kurgulandıktan sonra UI katmanını oluşturabiliriz.

Bizim örnek ele aldığımız yapıyı 6 aşamalı düşünce yapısı ile kurgulamış olacağız. Son halinde ekranda aşağı indikçe yeni elemanları çeken yapımız olmuş olacak. Son halleri ile ilgili detayları aşağıda görebilirsiniz.

2. Aşamadan yani api ile iletişime geçecek olan katmanı yazmakla başlayabiliriz. Bunun için iki adet sınıf oluşturacağız. Aşağıdaki fotoğrafta görebilirsiniz.

PassengerApi sınıfımız aslında bir arayüz, içerisinde herhangi bir mantıksal işlem barındırmıyor. Bunu arayüz olarak alan sınıflara içerisindeki methodları veya değişkenleri implement etmesini mecbur kılıyor. (PassengerApiImlp sınıfında görebilirsiniz.)

Bu sayede test işlemlerinde sahte servisimizi yazabileceğiz. Test işlemleri için devam makalelerime bakabilirsiniz. Ayrıca paylaştığım kodlar ile ilgili aklınıza takılan olursa makaleye yorum bırakarak sorabilirsiniz, en kısa zamanda dönüş yapacağım 👍

Evettt. Servis ile iletişime geçecek yapımız hazır, demeden önce http isteklerini atmadan önce http paketini projeye dahil etmelisiniz. Dahil ettiğim paketleri de aşağıda bulabilirsiniz.

3. Aşama olan repository sınıflarını oluşturmak ile devam edelim. Hangi dosyaları oluşturacağımı aşağıdaki görselde görebilirsiniz.

Repository sınıfımız için arayüz sınıfı:

Mantıksal işlemleri yaptığımız PassengerRepository arayüzünü implement ettiğimiz sınıf:

  • PassengerRepositoryImpl sınıfında servisten ham veriyi alıp, json parse işlemlerini yaptıktan sonra UI katmanının anlayabileceği modele çevirip sunuyoruz.

Servisten gelen ham veriyi UI katmanının anlayabildiği model sınıfımız:

Servisten gelen cevap:

Evet, aşama aşama gidiyoruz. Aslında her şey çok modüler ve basit.

4. Aşama yani mantıksal işlemleri yürüten durum(state) yönetimi yapısını bloc ile kurgulanmasına gelelim. Bunun için oluşturacağım dosyaları aşağıdaki fotoğrafta görebilirsiniz.

Listede göstermek üzere yeni veri çekilmesini talep eden event sınıfımız:

İlgili bileşenimizde(göstereceğimiz liste) olası durumlarını(states) içeren sınıflarımızı oluşturduk.

  • PaginationListFailed: Liste yüklenme aşamalarında bir problem olduğunda UI tarafında ilgili işlemleri yapmamız için bu state yarattık. Ne gibi problemleri içeriyor? Servisten başarısız cevap gelmesi, Servise yanlış istek göndermemiz, Servisten gelen ham veriyi parse edilmemesi gibi bir çok işlem aslında bu state ile aktarılacak.

PaginationListBloc sınıfımızda ise bloc yapısını kullanarak state mantık işlemlerini ele alıp, işleyen sınıfımızı oluşturduk.

Bloc yapısını kurgularken;

  • Ekran için bir bloc kurgulamak yerine daha çok bileşen kurgulanmasını tavsiye ediyorum. Peki ne demek istiyorum?

Aşağıda eklediğim görsel üzerinden anlatmak istiyorum. Burada 3 adet kırmızı ile belirttiğim alan var, bu alanların her biri için aslında bir bloc/cubit yapısı kurgulanması gerekiyor.

  • En büyük alan aşağıdaki bottom navigation bar’ı ve gösterilecek ekranı yönetecek olan bloc
  • Yukarıdaki kırmızı alan hesap istatistiklerini yöneten bloc
  • Alt taraftada gün ve gün hesap akışını yöneten bir bloc

Şimdi dediğimin tersinde siz ekran içinde bloc yapabilirsiniz ancak yapınızın test edilebilirliği, okunabilirliği ve sürdürülebilirliğini zorlaştırmış olacaksınız. Bu yüzden bileşen bazlı bloc yapısı kurgulanmasını tavsiye ediyorum.

  • Servis işlemlerini yaparken kesinlikle try-catch blokları arasına alıp exception kontrollerini yapmanızı tavsiye ediyorum. Aşağıda örneğini görebilirsiniz.
  • Ekranda kullanacağınız state dışında kalan durumlarınızı ilgili state sınıfının içinde tutmak yerine bloc sınıfı içerisinde tutmanızı tavsiye ederim. Aşağıda ‘_passengers’, ‘_page’ gibi değişkenleri örnek olarak alabilirsiniz.
  • Satır 18 ile 25 arasında PassengerFetched eventini dinliyoruz. transformer ile 150ms içerisinde gelen 1.’den sonraki eventleri işleme almayan bir yapı oluşturdum. Çünkü listede güncel position değerini dinlerseniz aşağı indikçe birden fazla ekrana güncel pozisyon geldiğini görebilirsiniz. Eğer art arda istek gönderirse bir süre sonra main thread kilitlenecektir.
  • _onPassengerFetched method’u ile repository’den yolcu listesini talep ediyoruz. Eğer bir problem olursa Failed state’ini gönderiyoruz, eğer başarılı ise mevcut yolcu listesine gelen yolcuları ekleyip ekrana gönderiyoruz.
  • isReachedMax amacı eğer daha fazla veri gelmiyorsa servisten, listede aşağı inildiğinde yeni bir istek atılmasını engellemek için yaptım.
  • _page ise servise kaçıncı sayfayı istiyorsun sorusunun cevabını vermesi için yaptım.

Pagination nasıl işliyor? Servis sizden bir page(int) bir de limit(int)(opsiyonel) query parametreleri ister bu parametrelere göre size bir veri seti sunar. Eğer page=0, limit=10 gönderirseniz veritabanında page*limit kadar veriyi atlayıp limit kadar veriyi döner.

UI katmanını bilgilendirecek logic(bloc) katmanını bu şekilde tamamlamış olduk.

Son adım da ise ekrandaki elementleri oluşturacağız, bloc sınıfını ve repository sınıflarını widget ağacına entegre edeceğiz. Bunları yapabilmek için aşağıdaki dosyaları oluşturdum veya güncelledim.

Flutter uygulamamızı yönetecek olan MaterialApp sınıfı ile bu dosyada sarmalıyoruz. MultiRepositoryProvider ile daha birden fazla repository singleton olarak widget ağacına ekleyebiliyoruz. Burada PassengerRepository eklemiş olduk.

Bu ekranın ana bileşini içeren yani scaffold tutan bir widget. Bu bileşende PaginationListBloc logic sınıfımızı widget ağacına dahil ediyoruz. Bu şekilde liste bileşeni UI güncellemeleri için bu sınıfa ulaşabilir olacak. Sınıfın instance ‘ını oluşturduktan sonra hemen ilk verileri çekmesi için bir event gönderiyoruz. (..add ile görebilirsiniz)

Bu dosyayı satır satır açıklamak istiyorum.

  • Satır 8: BlocBuilder ile bloc’tan gelebilecek durumları dinleyen bir yapı kuruyoruz.
  • Satır 10 — 17: Listenin boş gelmesi ve Liste verileri çekilirken bir hata ile karşılaşılırsa onu ekranda göstermek için durumları ele alıyoruz.
  • satır 18–-35: Liste başarılı bir şekilde yüklendi ise liste widget’ını oluşturuyoruz.
  • satır 19: NotificationListener ile sayfadaki kaydırmaları dinliyoruz ve pixels(güncel kardırma pozisyonu piksel cinsinden) ile maxScrollExtent(en fazla kaydırılabilir piksel değeri) değerlerine göre en alta inip inmediğini kontrol ediyoruz. En alta indi ise yeni verileri çekmesi için event gönderiyoruz.

Dosya Yapısının Tamamlanmış Görünümü;

Uygulamanın Tamamlanmış Görünümü;

Her şeyden önce yapacağınız projenin planlamasına ayıracağınız vaktin ne kadar önemli olduğunu buradan da anlayabilirsiniz. Bir çok kişi için kompleks durumlar yaratabilen bu işlemler aslında aşama ve planlı gidilince ne kadar basit olduğunu görüyoruz.

Aklınıza takılan ne olursa olsun, makaleye yorum olarak sormaktan çekinmeyin. Okuduğunuz için teşekkürler 🙏

Projenin tamamlanmış haline aşağıdan ulaşabilirsiniz.

Bloc 7.3.3 sürümü ve üstünde çalışılabilirliği test edilip, makale bu sürüme göre yazılmıştır.

Bu projeyi makale serisi yaparak geliştirme düşüncem var, zamanla aşağıdaki makaleleri eklemeye çalışacağım. Birlikte orta’dan ileri seviyeye giden bir proje çıkartmış olacağız.

  • Handle Failed Service Responses| Part 2
  • Flutter Unit Testing, Mocking with Bloc State Management | Part 3
  • Creating Pagination API with NodeJS

--

--