Flutter'a Övgü

Mobil uygulama geliştirme ekibimizden Sait Banazılı, geçtiğimiz günlerde "React Native Nedir? Nasıl Çalışır?" isimli bir makale yazmıştı. Şimdi de, React Native Nedir? Nasıl Çalışır? yazısının devamı olarak düşünebileceğimiz "Flutter'a Övgü" isimli bir blog yazısı kaleme aldı.

Keyifli okumalar.

Bu yazının amacını baştan söyleyeyim: Flutter’a Övgü

Flutter, Google tarafından geliştirilen bir cross-platform uygulama geliştirme kiti ama önemli olan soru şu: “Flutter neden devrimsel?” Flutter alternatiflerinin yapmadığı bir şeyi yapıyor:

Çizim (Rendering)

Mobil uygulamalardan bahsederken client-side uygulamalardan bahsediyoruz. Yani kullanıcı arayüzü aşağı yukarı her şey. Dolayısıyla bir mobil uygulama geliştirme çözümünü ilk defa incelerken aklımıza gelen ilk soru şu olmalı:

Bu şey ekrana nasıl bir şeyler çiziyor?

Fluttter dışındaki çözümlerin popüler olanları bu işi native SDK’lere yaptırıyorlar. Örnek olarak React Native yukarıdaki yazıda açıkladığım gibi haberleşme yoluyla çizim işini native SDK’lere yaptırıyor. Kısacası birçok çözüm aynı şeyi farklı yolla yapıyor.

Flutter ise çizim işini kendi üstleniyor. Bunun için yine bir Google marifeti olan Skia adında 2 boyutlu bir grafik motoru kullanıyor.

Skia Google Chrome, Android ve Mozilla Firefox gibi ürünlerde ve Android’in yerini alacağı iddia edilen yeni işletim sistemi Fuchisa’da da kullanılan, C++ ile geliştirilmiş bir grafik motoru.

Flutter uygulamaları production aşamasında bir parçası da Skia olan Flutter Engine ile birlikte arm binary koda derleniyor. Dolayısıyla Dart ile yazdığınız uygulama, hem IOS’ta hem Android’de donanım seviyesinde çiziliyor ve çalışıyor.

Flutter Tablosu

Tek Dil, Tek Runtime, Tek SDK!

Dolayısıyla Flutter aslında donanım seviyesinde native cross-platform uygulama geliştirebileceğiniz tek çözüm.

Neden Önemli?

  • Performans: Artık mobil uygulamalarda animasyon dediğimiz şey sürekli gördüğümüz için artık farketmediğimiz ve görmediğimizde farkettiğimiz bir şey. En sıradan gördüğünüz uygulamada bile listeler, switchler, checkbox’lar, sayfa geçişleri, diyaloglar, gesture efektleri vs. derken aslında her yer animasyonla doluyor. Böylece yüksek ve stabil FPS bir mobil uygulama için kritik hale geliyor.
  • Özelleştirme Kolaylığı: Flutter’da arayüz katmanı ne native SDK’lere bağımlı, ne de onların üzerine kurulan bir takım soyutlamalara, arayüzlere, köprülere vs. bağımlı. Tek bir dille, donanım seviyesinde native, tek bir codebase’den aklınıza gelebilecek her türlü kullanıcı arayüz mümkün. Üstelik Flutter çok zengin bir hazır arayüz bileşenleri kataloğu sunuyor ve elbette açık kaynak. Editörünüze bir Go To Definition uzaklıkta.
  • Tutarlılık: Android ve IOS SDK’leri elbette birbirinden çok farklı. Cross-platform çözümlerde kodu olabildiğince paylaşmak isteriz ve bu gibi platform farklılıklarında zorlanabiliriz. Kod ayrımı yapmak ne zorluk varsa ondan en az 2 tane, hatta 3 tane demek. Flutter’da platformlar arasında ne kadar ayrım yapacağınız sizin tercihinize bağlı. Çünkü hazır arayüz bileşenleri native SDK’leri bire bir taklit edecek şekilde ayrılmış evet, ama cross-platform da çalışabiliyorlar ve daha da önemlisi kendi çözümlerinizi de üretebilirsiniz.
  • Test edilebilirlik: Nedeni anlaşıldı sanırım. Tek dil, tek runtime, tek SDK!

Bu arada tutarlılıkla bağlantılı olarak Flutter’ın native uygulamalar gibi hissettirmeyebileceği eleştirisi yapılıyor. Benim cevabım şöyle:

  1. Hissettiriyor. Hazır Flutter arayüz bileşenleri kullanılarak yapılmış bir mobil uygulamanın native olmadığnı hissettmek gerçekten çok zor. Yani hem IOS’un hem Android’in tasarım dilini takip eden çok kaliteli bir kütüphane sunuluyor.
  2. Eğer bir araç gereç (utilization) uygulaması vs. yapmıyorsanız native tasarım dillerine sıkı sıkıya bağlı kalmanız ve her yerde hazır bileşenler kullanmanız çok anlamlı değil. Yani istisnalar haricinde tasarımcılar genelde özgün işler yaparlar. Flutter tam da bu noktada cross-platform olarak sınırları kaldırdığı için devrimsel zaten.

Platform Channels

Flutter arayüz katmanında bağımsız ve donanım seviyesinde çalışıyor ama uygulama teknik olarak yine bir native uygulama kabuğunda (Runner) çalışıyor. Dolayısıyla native API’lerle iletişim kurması mümkün ve zaman zaman gerekli (kamera, cihaz bilgisi, rehber vs. gibi bir gereksinim durumunda veya native bir SDK kullanma gerekliliği olduğunda).

Flutter native api’lerle iletişim kurması gerektiğinde Platform Channels diye adlandırdığı köprüyü kullanıyor. Yani bir native SDK ile çalışıyorsanız veya kendiniz native kod yazmanız gerekiyorsa Flutter bu kanaldan haberleşiyor. React Native’in çizim dahil her şey için kullandığı köprüye benzer bir yapı bu. Hem Flutter hem React Native şimdilik native tarafla sadece asynchronus iletişim kuruyor. Başta bahsettiğim yazıda bu konuyu anlatmıştım.

Bu kadar açıklama sonrası zor bir şeyden bahsediyormuşum gibi gelebilir. Ama tabii ki Flutter ekibi bu işler için plugin dediğimiz şeyleri zaten geliştirmiş ve dökümanlarını hazırlamış. Çoğu zaman birkaç satırlık Dart kodundan bahsediyoruz yani uygulama geliştiricisi tarafında.

3.parti native SDK’ler konusundaysa, benim aradığım popüler tüm SDK’ler için ya açık kaynak topluluğu tarafından yada SDK geliştiricileri tarafından plugin’ler geliştirilmiş ve geliştirilmeye devam ediyor. Flutter’ın hızla büyüyen bir topluluğu olduğunu zaten bu kadar övgünün yanında söylemeye gerek yok.

Çekirdek Kütüphane

Flutter’ın şimdiden React Native’e göre çok daha zengin bir çekirdek kütüphanesi var. 

Örnek olarak bir mobil uygulama için en önemli konulardan birisi olan navigasyon konusunda React Native 3.partilere muhtaçken Flutter’ın bu konuda 1.parti çözümü var. Bunun dışında animasyon API’sini veya Slivers,

Hero,

AnimatedContainer,

CustomPaint,

Transform,

Draggable,

Dismissable,

LayoutBuilder,

BackDropFilter

gibi birçok ilginç Widget’ı gösterebilirim.

Bunun da temel sebebi mimarinin yol açtığı olanaklar aslında. Açık söylemek gerekirse React Native’de sıklıkla hack yapıyormuşsunuz hissine kapılıyorsunuz. Facebook bu noktada çok fazla 1. parti hack istemediği için çekirdek kütüphaneyi çok sınırlı tutmuş olabilir.

First-party pluginlerle birlikte düşünüldüğünde Flutter’ın şimdiden React Native ile kıyaslanamayacak kadar zengin bir çekirdek kütüphane sunduğunu rahatlıkla söyleyebiliriz. Bu noktada Firebase’i de anmak isterim. Firebase mobili ilgilendiren birçok servisi bir arada sunan bir Google ürünü olarak Flutter pluginleri arasında önemli bir yer tutuyor.

Ve tabii yazılım geliştirirken kutunun dışına olabildiğince az çıkmak isteriz ama Flutter henüz genç olmasına rağmen sağladığı olanakların 3.Parti Harikalar Diyarı’na yol açması da kaçınılmaz.

State Management

Flutter, tıpkı React Native’in React ile yaptığı gibi declarative programlama paradigmasını benimsiyor. Yani, geliştiricinin doğrudan muhattap olduğu yapılar (StatefulWidget, StatelessWidget, InheritedWidget, BuildContext vs.) bu mantıkla tasarlanmışlar. Ve React’taki JSX syntaxine benzer şekilde programlama dili ile arayüz geliştirmesi (ui-as-code) yapıyorsunuz.

Programlamaya başlarken daha alışık olduğumuz imperative (algoritmik de denilir) stille farkını bilmeyenler için ben şöyle özetliyorum:

Programınıza balık tutmayı adım adım anlatmak(algoritmik) yerine balık tutma sürecindeki olası eylemleri(actions) ve başına gelebilecek durumlarda(state) ne yapması gerektiğini söylüyorsunuz.

Declarative programlamayı anladıkça arayüz geliştirirken yada bir kodu okurken ne kadar faydalı olduğunu görüyorsunuz. Çünkü front-end uygulamalardaki karmaşıklık neredeyse her zaman problemin zorluğundan değil, problemin büyüklüğünden kaynaklanır. Yani veriniz karmaşıktır. Birçok veri modeli arasında birçok ilişki kurulur. Arayüz birçok bileşenden oluşur vs. Declarative programlama bu noktada veriyi ve mantığı hem merkezileştirebildiği hem de arayüze yakın tutabildiği için önemli avantajlar sağlıyor.

State yönetimi declarative stilde en önemli konu diyebiliriz. Veriyi ve state’i nasıl saklayacağınız ve farklı bileşenleri nasıl haberleştireceğinizden bahsediyoruz temel olarak.

React söz konusu olduğunda uzun zamandır Redux neredeyse bu işin standardı halini almıştır. Şahsen de beğenirim çünkü test edilebilirlik açısından çok iyidir ve declarative programlama tarzını fonksiyonel programlama ile birlikte çok iyi tarif eden bir yapısı vardır. Yıllardır kullanıldığı için Redux’la birlikte kullanabileceğiniz çok sayıda araç gereç de geliştirilmiştir.

Redux derken aslında bir pattern’den bahsettiğimiz için elbette Flutter ile de kullanılabilir. Şahsen Flutter ile Redux kullanmaya kesinlikle büyük bir itirazım olmaz ancak Flutter aslında temelde Redux ile aynı mantığı paylaşan bir çözümle gelir (Lifting State Up)

InheritedWidget

Her Flutter uygulaması bir widget ağacıdır. Widget’lar Stateful ve Stateless olarak iki kategoriye ayrılır. Davranışı bir state’e bağlı olarak değişen widget’lara StatefulWidget diyoruz. StatelessWidget ise kendisine verilen veriyi sunmak (gösterim) amacı taşır.

inherited

Dolayısıyla state yönetimi derken ağacın yaprakları arasındaki veri iletişimi ve kullanıcı etkileşiminin yönetilmesinden bahsediyoruz. Kısaca, InheritedWidget ağacın anlamlı düğümlerinde bir state deposu görevi üstlenir. Bu düğüme (ne kadar aşağıda olursa olsun) bağlı olan diğer Widget’lar state deposuna erişebilir ve etkileşime girebilir.

Örnek olarak yukarıdaki ağaçta RemoveButton’a dokunulduğunda hem ListView kendini güncelleyip item’ı listeden çıkarmalı; hem de ağacın başka bir dalında olan Cart Count Badge yeni sayıdan haberdar olmalı. Lifting State Up derken farklı yerlerde ihtiyaç duyulan state’lerin daha yüksekte bir ortak düğümde tutulmasından bahsediyoruz. O ortak düğüm genelde ağacın kökü olur. (İyi implemente edildiyse zararı olmaz) Bu state deposunda bir şey değiştiğinde hangi düğümlerin haberdar olmasını istiyorsak onlar haberdar olur.

InheritedWidget teorik olarak yeterli olsa da özellikle yeni tanışanlar için anlaşılması zor olabiliyor ve çok fazla kod yazmayı (boilerplate) gerektirebiliyor. Bunun da ilacı InheritedWidget temeliyle çalışan Provider isimli bu gibi kütüphaneler. Provider bunların en yenilerinden biri ve gördüğüm kadarıyla en başarılısı. Hatta Flutter’ın orjinal dökümanında yerini aldı. Flutter’a yeni gelenlerin doğrudan karşısına çıkarılmaya ve bir nevi standart olmaya aday gibi görünüyor.

Kısacası Flutter’da state yönetimi seçimi yapmak kafa karıştırıcıydı. Provider bence bu işi çözdü. Redux’ın avantajlarını isteyene o da var.

React Native’de state yönetimi? Çok iyi, çünkü bu React’ın uzmanlık alanı.

Geliştirici Ergonomisi

Mimariden sonra en önemli başlık bu olabilir. Dart’ın production aşamasında binary koda derlendiğini söylemiştik. Fakat Dart geliştirme aşamasında JIT derlenip Dart VM aracılığıyla çalışıyor. Bu sayede Flutter, Hot Reloading dediğimiz, koddaki değişikliği kaydettiğiniz anda etkisini gördüğünüz çok faydalı bir özelliği çok başarılı bir şekilde sunuyor. (React Native’i bıraktığım yerde Flutter kadar başarılı değildi bu konuda)

Flutter hem Android Studio hem Visual Studio Code için birçok araçtan oluşan paket pluginler sunuyor. Editör diye bildiğimiz VSCode Flutter plugini ile küçük bir IDE’ye dönüşüyor. Mesela Dart Analyzer sunulan araçlardan biri (compile-time hatalarını tespit eden ve kötü bir şey yaptığınızda uyaran arkadaş).

Ben Flutter’a VSCode ile başladım ama Android Studio’yu deneyince bazı özelliklerini daha çok beğendim. Kesinlikle tavsiye ederim. Örneğin aşağıdaki entegre Dart DevTools penceresi:

geliştirici ergonomisi

Dart DevTools gerçek zamanlı performans ölçümü, aynı cihazda IOS/Android arasında geçiş yapma, rebuild(re-rendering) haritası, layout sınırlarını gösterme vs. gibi çok faydalı özellikler sunuyor. Dart DevTools’un şu anda preview aşamasında olduğu belirtiliyor ama şimdiden sihirli bir alet çantası.

Debugging bir çok açıdan React Native’e kıyasla daha kolay Flutter’da. Sorunsuz bir şekilde breakpoint debugging yapabiliyorsunuz. 3.parti araçlar kullanmak zorunda kalmıyorsunuz. Android Studio’da aynı anda birden fazla cihazda uygulamanızı aynı anda çalıştırıp, aynı anda debug edip, hot reloading’den yararlanabiliyorsunuz.

Kısacası Flutter, Flutter CLI (Command Line Tool), Dart VM, editör pluginleri üçgeninde şimdiden geliştiriciyi çok nadiren üzen konforlu bir ortam sunuyor.

Bunun dışında Dart’ın yine Google tarafından geliştirildiğini, hatta özellikle Flutter göz önünde bulundurularak eklemeler ve iyileştirmeler yapıldığını ekleyelim. Google I/O 2019'da duyurulan Dart 2.3 değişikliklerini örnek olarak gösterebiliriz.

Dart statik tipli, compiled, OOP odaklı fakat birçok fonksiyonel programlama yapısını da destekleyen bir dil. Temel özellikleri dışında Dart’ın doğrudan arayüz geliştirmeye odaklanmış bir dil olması Flutter için bugün ve gelecekte önemli bir avantaj olarak görülüyor. Bu tartışmaya girersem çok uzar fakat bence Dart Javascript’e göre bu iş için daha uygun bir dil. (Typescript’in farkındayım. Yine uzatmamak için şu kadar söyleyeceğim: “Bence yeterli değil.”)

Pub, npm’e benzer başarılı bir paket yönetim sistemi. Geliştirme yapma ve kullanma kolaylığının yanında geliştiricileri belirli standartları takip etmeye teşvik eden puanlandırma benzeri ayrıntıları var.

Flutter gerçekten çok iyi bir API tasarımına sahip. Yani çoğu şeyi elinizle koymuş gibi buluyorsunuz. Kolay bulmanızdan daha önemlisi o şeylerin var olması. Yani neredeyse elinizi attığınız her yer hayatı kolaylaştıran, bazen kod kalitesinin düşmemesini sağlayan detaylarla dolu.

Yazılımla ilgili neredeyse genel geçer bir kural vardır. Bir kütüphane, bir framework, bir şey ne kadar güçlüyse kullanması da o kadar zordur. Çünkü soyutlamalar arttıkça esneklik azalır. High-level ve low-level diller gibi düşünebiliriz. Flutter’ın API tasarımı iki uç arasında çok iyi bir denge kuruyor ve bence hem yeterince kolay olmayı hem de çok güçlü olmayı başarıyor. Yani genelde hem soyutlamayı hem de altında yatan mekanizmayı erişime açıyor aslında.

Tüm bunları sayarken yeni ve hızla büyüyen bir framework’ten bahsettiğimizi hatırlatırım. Flutter’ın geliştirici ergonomisi açısından bir harikalar diyarına dönüşeceğini uzun zamandır iddia ediyordum ve şimdiye kadar haksız çıktığımı sanmıyorum.

React Native’de geliştirici ergonomisi? Açıkçası sadece bir kelime yazasım var: “3.parti”.

Testing

Flutter’ın temel felsefesi geliştiriciyi framework’ün dışına çıkarmamak denilebilir. Karmaşık mobil uygulamaların stabil olabilmesi için özellikle ui-test’ler çok önemli. Flutter’ın bağımsız arayüz katmanı güçlü bir E2E test kütüphanesini sunmasına da olanak tanıyor. Flutter 2 tür test kütüphanesi sunuyor:

  1. Widget Test: Amaç tek bir widget’ı izole olarak test etmek
  2. Integration Test: Amaç uygulamada gerçekleşebilecek kullanım senaryolarını test etmek. Birçok widget ve servisin bir arada çalıştığı testlerden bahsediyoruz.

React Native’de UI Testing? 3. parti ve bu kadar başarılı değil.

Mobilin Ötesi

Flutter’ın yol haritasında web ve masaüstü uygulamalarında kullanılması da var. Web’de mobildeki devrim etkisini yaratabileceğini sanmıyorum ama desktop için çok iyi bir seçenek durumuna gelebilir. Tabii burada asıl motivasyon alternatifleri yok etmek değil; tek kod tabanından mobil’e, web’e ve masaüstüne uygulama geliştirebilmek.

https://www.youtube.com/watch?v=IyFZznAk69U

React Native’e çok haksızlık yapmış olmamak için bir dipnot:

React Native yılların olgunluğuna erişmiş, cross-platform uygulama geliştirme alanında bir devrim yaratmış başarılı bir uygulama geliştirme kiti ve daha da iyi olacak. Çok uzun bir süre daha kullanılmaya devam edecek.

Fakat…

Fakat’ı anlattım. Okuduğunuz için teşekkürler.