Infinite Scrolling with MobX in Flutter

Abdulsamet İLERİ
HardwareAndro
Published in
3 min readFeb 11, 2021

Bu yazımızda MobX state management pattern kullanarak Flutter da infinite scroll (bir nevi pagination) nasıl yapılır, örnek bir uygulama ile işleyeceğiz.

TL;DR: Kaynak kod.

Yukarıda örneğini koyduğum sayfayı yapacağız, buradaki verileri https://picsum.photos/v2/list url’den 5 er 5 er çekeceğiz.

State management olarak mobx ve mobx_codegen paketlerini, API’den gelen json modelini deserializable edebilmek için json_serializable paketini, network isteği atabilmek için de dio’yu kullanacağız. Ayrıca mobx ve json_serializable paketlerinin codegen işlemini yapabilmeleri için build_runner’ı yüklüyoruz. (Versiyon numaraları farklı olabilir.)

json_serializable ve mobx için boilerplate kod yazmak gerekiyor, bunu da kendinize özel snippet yazarak çözebilirsiniz.

Öncelikle API’mizden gelen json modelimize bir bakalım. Page ve limit query parametreleriyle gelen veriyi sınırlayabiliyoruz.

https://picsum.photos/v2/list?page=1&limit=5

Buradaki json arrayini https://javiercbk.github.io/json_to_dart/ adresine yapıştırıp, json modelimizi dart classına çeviriyoruz (generate edilen tüm kodları kullanmıyorum sadece değişken ve constructorı alıyorum.) ve query parametrelerini de kullanmak için PaginationModel adlı bir class oluşturuyoruz.

@JsonSerializable(fieldRename: FieldRename.snake) diyerek apiden gelen snake_case i ben camelCase olarak kullanacağım, oluşturacağın generate file’da bunu dikkate al diyoruz. (download_url mesela).

API isteğini atacağımız servis classına bakalım. Bu class bir interfaceden türeyecek. Interfaceden türemesi daha kolay mocklanabilmesi demek. Amacımız bu olmasa bile elimizin alışması iyi olur.

Burada dio instance’mizi singleton vs. olarak oluşturup kullanabilirdik veya mocklamak için constructordan dio instancesini alabilirdik vs. fakat makelemizin amacı bu değil.

fetchCards metodumuza bakarsak bu metod, Card Modelimizi liste tipinde dönecek diyoruz. Pagination limitlerimizi veriyoruz ve dio ya gerekli bilgileri geçiriyoruz.

Gelen sonucumuzu List<dynamic> olarak dio bize veriyor, biz bunu kendi kullanacağımız modelimize çevirmeliyiz ki property’lerini (url, width, height vs.) kullanabilelim. Bunun içinde response’mizi bir mapten geçiriyoruz. Responsedeki her bir itemimizi factory metodumuzu(CardModel.fromJson) kullanarak çeviriyoruz ve bu listede olan tüm itemlerin hepsinin de CardModel tipine çevir diyoruz. (cast<CardModel)>)

Şimdi view model ve view imizi oluşturalım.

View modelimizde API’yi çağıracak servisimizi oluşturuyoruz. Dikkat ederseniz interface tipinde tanımlayıp init fonksiyonun içinde somut tipe dönüştürüyoruz.

ObservableList<CardModel> ile API’den gelen verileri bu liste içerisinde toplayacağız. ObservableList özel bir liste türü. Herhangi bir ekleme, çıkarma vs. olduğu zaman observerlar tetikleniyor.

isFetchData bool değişkenimiz ile API’ye birden fazla çağrı gitmesini engellemiş oluyoruz. _page ve _size değişkenleri ile query parametrelerimizi ayarlıyoruz. Eğer veri varsa veriyi ekledikten sonra sayfamızı bir artıyoruz.

Gelelim view imize.

Widgetimizi stateful olarak tanımladık ki initState lifecycle’ını kullanabilelim. Burada view modelimizi oluşturup onun init metodunu tetikliyoruz ve view modelimizde olan service değişkenimize somut bir veri atanıyor.

fetchCards diyerek sayfa yüklenirken verilerimizi almak için çağrı yapıyoruz.

build metodumuza bakarsak, Scaffoldun bodysini Observer widgeti ile sarmaladığımızı görüyoruz. Bu özel bir widget, herhangi bir değişiklik olduğu zaman observer widgeti tetikleniyor ve içerisindeki fonksiyon yeniden çağrılıyor. ObservableList olarak tanımladığımız cards listesinde ekleme, çıkarma vs. olduğu zaman bu Observer widgetimiz tetikleniyor.

Eğer henüz veri gelmemişse yani vm.cards.isEmpty ise progress indicatorümüzü göster diyoruz. Eğer verimiz geldiyse bunu ListView.builder adlı factory metoduyla UI’mızı oluşturuyoruz. (ListView.builder ekranda görünen widgetleri çizer bir nevi lazy item durumu oluşturur bundan dolayı kullanılması performansa artış sağlar.)

ListView itemcountuna _vm.cards.length + 1 değeri veriyoruz, burada + 1 değeri yeni bir page için istek atıldığında CircularProgressIndicator göstermesi için veriyoruz. (ListViewimize böyle bir durum için ekstradan 1 tane widgetimiz var demiş oluyoruz.)

Eğer kullanıcı ekranın sonuna gelmişse, var olan son item de çizilmişse index == _vm.cards.length içine girilecek ve biz yine bir çağrı atacağız ve bu sırada progress indicatorumuzu göstereceğiz.

Bunun direkt itemBuilder metodunda değil de scrollController tanımlayarak ona listener atıp bir if ile de kontrol edilip sağlanabiliyor. Bu tamamen tercih meselesi. Bu yöntem bana daha sağlıklı geliyor.

Sonuç.

--

--