Flutter ile Covid19 İstatistik Uygulaması Geliştirmek


Giriş


Akıllı saatlerin bile internetten veri alıp yollayabildiği günümüzde, internete bağlanmayan mobil uygulamalar yok denecek kadar azdır. Bütün bu cihazların internete bağlanma amacı, başka bir cihazdan veri almak ya da başka bir cihaza veri yollamaktır. Geliştirdiğiniz mobil uygulamaların bu veri alışverişini yapabilmesi ise iki şekilde mümkündür. Ya veritabanlarına bağlanırsınız, ya da api'lar ile iletişime geçersiniz.


Bu yazıda örnek bir Covid19 uygulaması üzerinden:

  • Http modülünü kullanarak api'lerle nasıl iletişim kurabileceğinizi,

  • Json dosyalarını nasıl parse edebileceğinizi,

  • Asenkron programlama mantığını,

  • FutureBuilder kullanımını

anlatacağım. Makalenin uzamaması adına arayüz tasarımının detayına inmeyeceğim. Tasarımın orjinal hali Abu Anwar'a aittir. Detaylı implementasyonu için youtube kanalını ziyaret edebilirsiniz.



Projeye Genel Bakış


Kodlamaya başlamadan önce uygulamanın neye benzediğinden ve akışından bahsetmekte fayda var, ilerledikçe buraya atıfta bulunacağım.


Uygulama ilk açıldığında Türkiye'nin güncel covid19 verileri gözükecek, kullanıcı isterse Dropdown'daki herhangi bir ülkeyi seçip o ülkeye ait güncel verileri görebilecek.

Verileri çektiğimiz API endpoint'i şu şekilde:

https://corona-api.com/countries/<ülkenin alpha2 kodu>


Herhangi bir ülkenin verisine ulaşmak istediğimizde, o ülkenin alpha2 koduyla istekte bulunacağız. Örneğin Türkiye'nin verisine ulaşmak istersek istekte bulunacağımız endpoint şu şekilde olacak:

https://corona-api.com/countries/TR



Gelen cevap:

Farkettiyseniz "name" kısmında (8. satır) ülkenin ismi İngilizce yazıyor. Uygulamanın Türkçe olmasını istediğimiz için "ulkeler.dart" adında bir dosya oluşturup, tüm ülkelerin Türkçe isimlerini ve alpha2 kodlarını

key-value şeklinde(yani Map yapısında) countries adında bir değişkende saklayacağız.

Kurulum


Flutter ve Dart temellerini bildiğinizi ve SDK'leri kurduğunuzu varsayarak, aşağıdaki kodu terminale yazıp yeni bir proje başlatıyoruz.

Ben Visual Studio Code kullanıyorum, Flutter'ı destekleyen diğer IDE'leri de kullanabilirsiniz.



Flutter SDK proje taslağını resimdeki gibi oluşturacak.


Şimdi pubspec.yaml dosyanıza http paketini eklememiz gerekiyor.










Pubspec.yaml dosyasına paketi ekledikten sonra şöyle bir bildirim alacaksınız.


Get packages butonuna tıklarsanız Flutter sizin için paketleri indirecektir.

Yazılım projelerinin taşınılabilirliği, yönetimi ve takım çalışmasına uygunluğu için modülerlik çok önemlidir, bu yüzden dosyalarımızı işlevlerine göre farklı klasörlerde tutacağız.

  • Ülke verilerini Country adında bir sınıfta tutacağız. Bunun için models adlı klasörde Country.dart adında bir dosya açıyorum.

  • Api işlemlerini yapacağımız dosyalar için services adında bir klasör açıyorum. covid19.dart dosyasına fonksiyonlarımızı yazacağız.

  • main.dart dosyası projemizin ana noktası. Bütün parçaları bu dosyada birleştireceğiz.




Geri kalan dosyalar tasarımla ilgili olup bu makalenin kapsamının dışında olsa da kısaca bahsetmekte fayda var.

  • constant dosyasında renkler ve yazı tipleri gibi uygulama boyunca sabit kalacak değişkenleri tutuyoruz.

  • Widget klasöründe ise isminden de anlaşılacağı üzere birden fazla kez kullanacağımız widget'ları tutuyoruz. Bu sayede kendimizi tekrar etmemiş olacağız.

Bu dosyaları projenin github repo'sundan inceleyebilirsiniz.



Kodlamaya Başlayabiliriz


Ülkeleri Listeleme


ulkeler.dart dosyasındaki countries adlı Map'te ülkelerin listesini tuttuğumu söylemiştim.

  1. İlk olarak countries objesine erişmek için dosyayı import ediyoruz.

  2. Ülkeleri dropdown menüde listeleyebilmek için getAllCountries() adında bir fonksiyon oluşturup bu fonksiyonla countries adlı Map'teki bütün ülkeleri çekeceğiz.

  3. allCountries adında boş bir liste tanımlıyoruz.

  4. Daha sonra forEach ile countries map'inin içindeki key-value çiftleri üzerinde dolaşarak bütün value'ları yani ülkelerin ismini allCountries listesine ekliyoruz.

  • countries map'inde alpha2 kodları key, ülke isimleri value olarak tutuluyor. {'TR' : 'Türkiye'} gibi..


Ülke İsmini Alpha2 koduna Çevirme



  1. Kullandığımız api verileri ülkelerin alpha2 koduna göre saklıyor. Bunu sağlamak için convertAlpha adında bir fonksiyon oluşturuyoruz. Bu fonksiyon kullanıcının dropdown'da tıkladığı ülkenin ismini parametre olarak alacak (countryName) ve o ülkeye ait alpha2 kodunu döndürecek.

  2. Basit bir for döngüsüyle ulkeler.dart dosyasında oluşturduğumuz countries map'inin üzerinde iterasyon yaptırıyoruz. entries Map sınıfına ait bir özellik, Map'teki key-value çiftleri üzerinde iterasyon yapmamızı sağlar. Örneğin ilk iterasyonda entry ('AF' , 'Afganistan') olacak.

  3. Her iterasyonda value değerinin, parametre olarak verilen countryName, yani kullanıcının dropdown'da seçtiği ülke ismiyle eşleşip eşleşmediğini bir if koşuluyla kontrol ediyoruz.

  4. Eşleştiğinde ise o entry'nin key değerini, yani alpha2 kodunu döndürüyoruz.


Kullanıcının Türkiyeyi seçtiğini düşünelim. Bu durumda countryName parametresi "Türkiye" değerini tutacak. For döngüsüyle countries map'indeki entry'ler üzerinde iterasyon yapıyoruz ve "Türkiye" değeriyle eşleşen entry'nin('TR' , 'Türkiye') key değerini ('TR') döndürüyoruz.

Bu fonksiyonun döndürdüğü alpha2 koduyla api'ye istekte bulunabileceğiz.



Country Class'ı Oluşturma


Ülke ismini alpha2 koduna dönüştürecek fonksiyonu yazdığımıza göre, api'ya istekte bulunmaya hazırız. Ama bundan önce, cevap olarak gelecek verileri derli toplu bir şekilde tutabilmek için Country adında bir sınıf oluşturuyoruz.

Country.fromJson constructor'ının yapacağı iş çok basit.

  • countryName adlı parametre dropdown'da tıklanan ülkenin ismini tutacak ve Country objesinin name attribute'una atayacağız.

  • map adlı parametre ise api'den gelen json cevabının decode edilmiş, yani Dart dilindeki Map yapısına dönüştürülmüş halini tutacak ve Country objesinde karşılık gelen attribute'lara atama yapacağız.


Hatırlarsanız Api ülke isimlerini ingilizce sunuyordu. Biz ise Türkçe gözükmesini istediğimiz için getAllCountries( ) fonksiyonuyla ülkelerin Türkçe isimlerini kendi oluşturduğumuz countries map'inden çekip dropdown'da öyle gösterecektik.

Geri kalan attribute'ları(code,today vs.) api'den çekerken ülke ismini dropdown'dan çekmemizin sebebi bu.


(Bu kısım karmaşık gelmiş olabilir, ilerleyen bölümlerde anlaşılır olacaktır.)



HTTP Get İsteği & Asenkron Programlama


HTTP modülünün kullanımı oldukça basittir fakat Flutter ekibi bu modülü asenkron çalışacak şekilde tasarladı. Bundan dolayı birçok kişi çalışma mantığını anlamakta zorlanıyor.


Gelin, http modülünü ve arkasında yatan asenkron programlama kavramlarını aşağıdaki kod bloğu üzerinden anlamaya çalışalım.



Async


Standart programlama anlayışında yazdığınız kodlar senkronize bir biçimde, yukarıdan aşağıya doğru, yani sırayla çalışır. Dolayısıyla üst satırda yazdığınız fonksiyon sonuç üretmeden, arkasından gelen fonksiyonlar çalışamaz.


Fakat internetten veri çekmek gibi uzun sürebilecek işlemlerin, programın geri kalan kısmını bloke etmesini istemeyiz. Böyle durumlarda fonksiyonları asenkron çalışacak şekilde tasarlarız.


Asenkron programlama karşımıza farklı bir paradigmayla çıkar ve der ki:

Fonksiyonlar program akışından bağımsız olsun, üstteki sonuç üretmeden altındaki fonksiyon çalışabilsin. Örneğin üst satırda bir fonksiyon api'den veri çekerken, alt satırda resim indiren diğer fonksiyon api'den veri çeken fonksiyonun bir sonuç döndürmesini beklemeden çalışabilsin.


Peki fonksiyonun asenkron çalışacağını Dart'a nasıl anlatacağız?

async anahtar kelimesiyle fonksiyonumuzu asenkron tanımlayabiliriz.

Kolaymış dediğinizi duyar gibiyim. Evet kolay, fakat düşünmemiz gereken başka şeyler de var. Biz asenkron olmayan normal bir fonksiyon ile uğraşıyor olsaydık, geriye döndüreceği değeri fonksiyonu tanımlarken belirtirdik. Örneğin iki tam sayıyı toplayan bir fonksiyon tanımlıyorsak, dönecek değerin integer olacağını belirtirdik.


Bu fonksiyonun, çağırdığımız anda integer bir değer döndüreceğini biliyoruz.

Peki sizce asenkron fonksiyonlar, çağırdığımız anda mı bir değer döndürecek yoksa gelecekte mi bir değer döndürecek?


Future


Üst satırda internetten veri çeken, alt satırda resim indiren 2 tane asenkron fonksiyonun çalıştığını varsayalım. Asenkron fonksiyonlar program akışından bağımsız çalıştığı için, normal fonksiyonların aksine hangisinin önce biteceğini bilmiyoruz. Tek bildiğimiz şey, ikisinin de ürettiği sonucu gelecekte döndüreceği.

Asenkron fonksiyonlar Future tipinde değerler döndürür.

Bir fonksiyon tam sayılar içeren bir liste döndürüyorsa, List<int> diyerek bu listenin tam sayılardan oluştuğunu spesifik olarak belirtebildiğimiz gibi, fetchData( ) fonksiyonu gelecekte bir Country objesi döndüreceği için Future<Country> diyerek gelecekte dönecek değerin Country tipinde olduğunu belirtebiliriz.


Http Get Methodu


fetchData( ) fonksiyonuyla kullanıcının dropdown'da tıkladığı ülkenin ismini alıp convertAlpha() fonksiyonuna yolluyoruz. Oradan dönen sonuç ile de http paketinin içinde gelen get fonksiyonunu kullanıp api'ye istekte bulunuyoruz. Eğer dökümana bakarsanız get fonksiyonunun Future tipinde, Response adında bir obje döndürdüğünü görürsünüz.



Hatırlarsanız Country sınıfında Country.fromJson adında bir constructor tanımlamıştık. Ve bu constructor'ın map parametresinin, api'den gelen json verilerinin decode edilmiş halini alacağını söylemiştim.


Ama bir dakika!


Api'den gelen cevap Future tipinde, yani ne zaman biteceğini bilmiyoruz. Bildiğimiz tek şey gelecekte Response adında bir obje döndüreceği. Asenkron fonksiyonlar programın akışından bağımsız olduğu için, get fonksiyonunun işlemi bitmeden, api'den gelecek json verisi elimize ulaşmadan alt satırdaki constructor tetiklenecek ve düzgün çalışmayacak.


Await


Bunu await anahtar kelimesiyle engelleyebiliriz.


Future tipinde değer döndüren bir işlemin sonucu, arkasından gelen işlemler için gerekliyse await anahtar kelimesi ile o işlemin bitmesini bekleyebiliriz.


Parçaları Birleştirelim


Bundan sonraki tüm işlemleri main.dart dosyasında yapacağız.


  • Daha önce oluşturduğumuz getAllCountries( ) fonksiyonunu countries adında bir listeye atayalım. Dropdown'ı oluştururken bu listeyi kullanacağız.

  • Ekranda o an gözüken ülkeyi temsil etmesi için current adında bir değişken tanımlayalım.

  • Uygulama ilk açıldığında Türkiye'nin verileri gözüksün istiyorduk, initState( ) methodunun içinde current'a Türkiye'yi atarsak bunu sağlamış oluruz.


initState( ) methodu ekran ilk kez açıldığında tetiklenir, ondan sonraki tüm değişimlerde setState( ) methodu tetiklenecektir.

initState( ) methodunda current değişkenine Türkiye'yi atadık. Kullanıcı bir ülkeyi seçtiğinde de setState( ) methodunun içinde current değişkenini, kullanıcının seçtiği ülke ile güncelleyip, veriyi çektiğimiz fonksiyona da current değişkenini parametre olarak verirsek:

fetchData(current) 

Geriye sadece api'den gelen cevap ile bir UI oluşturmak kalacak fakat yine sıkıntı var. Çünkü UI uygulama çalışır çalışmaz oluşurken, veriyi çektiğimiz fetchData( ) fonksiyonu hemen değil, api'den veri çekme işlemi bittiğinde sonuç döndürecek. Hatırlayın, Future<Country> döndürüyordu.


Bu uyumsuzluğu düşünmeden UI hazırlarsanız, geceleriniz stackoverflow'da harap olacaktır.


Uygulamayı her açtığınızda yandakine benzer birçok hata alırsınız. fetchData()

fonksiyonu bir Country objesi döndürüyor, fakat bu objenin oluşması da get methodunun bitmesine bağlı. Hatırlarsanız bu yüzden await demiştik.

Uygulama açılır açılmaz UI oluşuyor fakat api'den henüz cevap gelmemiş, haliyle Country objesi oluşturulamamış oluyor.

Biz UI'da, henüz oluşmamış Country objesine ait name attribute'unu kullandığımız için de uygulama patlıyor.


Bu kısım, dropdown kodunu anlattığımda daha anlaşılır olacaktır.

Eğer arayüzünüz bu projede olduğu gibi Future sonuç döndüren bir fonksiyona bağlıysa, arayüzü de Future olarak tasarlamamız gerekiyor. Örneğin veri gelene kadar yukarıdaki kırmızı hata ekranı yerine, "yükleniyor..." yazsa hiç fena olmazdı değil mi?


Flutter ekibi bunu düşünüp, Future objelerinin durumlarındaki değişimleri kontrolümüzde tutacağımız bir widget tasarlamışlar.


FutureBuilder

Bu widget, Future objelerinin anlık durumuna göre farklı işlemler yapmamıza olanak sağlar.


Örneğin veri tamamen gelene kadar CircularProgressIndicator gösterip, veri geldiğinde ise normal arayüzümüzü gösterebiliriz.


Eğer bir hata durumu varsa yine farklı bir arayüz gösterebiliriz.


(Yükleme göstergeleri için Spinkit kütüphanesini kullanıyorum)


FutureBuilder diğer widget'lar gibi bir widget, dolayısıyla herhangi bir widget'ın içinde kullanabilirsiniz ve sizden future ve builder adında 2 parametre bekler.

  • future: Future tipinde bir nesne bekler.

  • builder: Future nesnesinden bir sonuç gelir gelmez, içinde tanımlanmış olan anonim fonksiyon tetiklenir. Builder da iki parametreye sahiptir. BuildContext ve AsyncSnapshot.


AsyncSnapshot bir çok özelliğe sahiptir ve akışı takip etmemizi 3 farklı durumla sağlar:

  1. AsyncSnapshot.connectionState.none: Bu durumda future null'dır.

  2. AsyncSnapshot.connectionState.waiting: Bu durumda future null değildir fakat henüz bitmemiştir. Ve AsyncSnapshot.hasData özelliği true olur.

  3. AsyncSnapshot.connectionState.done: future sonuç üretmiştir. Eğer başarılı bir şekilde tamamlandıysa AsyncSnapshot.data artık future'dan gelen veriyi tutar. Hata ile tamamlandıysa AsyncSnapshot.hasError özelliği true olur, böylece AsyncSnapshot.error ile gelen hatayı da yakalayabiliriz.



  • FutureBuilder'ı Scaffold'a ekliyoruz.

  • Hatırlarsanız fetchData( ) fonksiyonu Future objesi döndürüyordu ve arayüzümüzün oluşması bu fonksiyona bağlıydı. Bu yüzden future parametresine atıyoruz.

  • Eğer connectionState done değerine eşitse verileri sunacağımız arayüzü döndürüyoruz.

  • Aksi takdirde bir yükleme göstergesi döndürüyoruz.Ben spinkit kütüphanesini kullanıyorum, dilerseniz built-in gelen CircularProgressIndicator gibi widget'ları da kullanabilirsiniz.


Dropdown


Dropdown'ı oluşturmaya başlayabiliriz. Her bir özelliğin ne işe yaradığını, kod içerisinde yorum satırında anlattım.

Burada, kafa karıştırıcı olan items özelliğinin üzerinde duracağım.(6. satır)

Hatırlarsanız, getAllCountries( ) methoduyla ülkelerin listesini çekip, countries değişkenine atamıştık. Peki countries listesine uyguladığımız map methodu ne ola?

- map, Liste sınıfına ait bir methoddur. Listedeki elemanlar üzerinde iterasyon yapıp her elemana bir fonksiyon uygulamamızı sağlar. Yukarıdaki kod bloğunda, listenin her elemanı için bir DropdownMenuItem widget'ı oluşturuyoruz.


Aşağıdaki görselde kesikli mavi çizgilerin her biri, ayrı bir DropDownMenuItem'ı temsil ediyor.

Gelen Veriyi Arayüzde Gösterelim


Arayüz kodu çok uzun, konu bağlamından kopmamak için ufak bir bölümden örnek göstereceğim. Geri kalanında yaptığımız işlem tamamen aynı, projenin github reposundan inceleyebilirsiniz.



snapshot.data future'dan gelen objeyi tutuyordu. Biz future'a fetchData( ) fonksiyonunu atadık ve fetchData( ) fonksiyonu bir Country objesi döndürüyordu. Dolayısıyla Country objesinde tanımlı olan bütün özelliklere erişebiliriz.

Örneğin, snapshot.data.today['confirmed'] dediğimizde Country objesinde tanımladığımız today adlı Map'e erişiyoruz.


Kafanıza takılan soruları yorum bölümünden sorabilirsiniz.


Projenin github reposu:

https://github.com/arifavc/Flutter-Covid19-App

Tasarım:

https://www.uplabs.com/posts/coronavirus-information-concept

https://github.com/abuanwar072/Covid-19-Flutter-UI


Kaynaklar:

https://pub.dev/packages/http

https://api.flutter.dev

https://dart.dev/guides


#flutterr #covid19 #async #await #future #dropdown #FutureBuilder

0 yorum

Komünite

Platform

Mobiler.dev Anasayfa
  • Twitter
  • Instagram
  • development_düzenlendi_düzenlendi
  • Youtube
  • slack-icon-black_edited_edited_edited
  • Gri LinkedIn Simge
imageedit_2_9667998092.png
JetBrains Hakkında Detaylı Bilgi Alın

© 2020 by mobiler.dev

Kurumsal Yazar Hesapları

adesso.png
mobilerdevLogo.jpg
Yazarlık Başvurusu Hakkında Bilgi Alın, Başvuru Yapın.
Topluluk Yazarlarını Tanıyın