Building UI with Compose

Geçen sene Google IO‘da duyurulduğundan beri Compose‘u deneyimlemek to-do listimde bekleyen maddelerden biriydi. Yıllar boyunca hep dinamik olarak view’i kodda yaratmak yerine xml’den inflate etmenin daha performanslı olduğunu duyduk, ölçümledik, uyguladık, paylaştık. UI’la ilgili konuştuğumuz konu genelde ConstraintLayout da öncesine gidecek olursak, ne zaman LinearLayout ne zaman RelativeLayout kullanacağımızdı. Ne kadar iç içe hiyararşi olursa eşit, ne noktada RelativeLayout daha performanslı çalışır gibi konulardı. Ama işin açıkçası da UI geliştirmek her Android geliştiricisi tarafından uygulamanın en keyifle geliştirilen tarafı değildi. Kendi adıma görsel olarak çıktıyı o an görmek beni çok etkilediği için UI’dan hep keyif aldım. Biraz xml’in sevilmemesinden biraz da hep optimal bir performans çıktısı olan UI geliştirebilmek için ConstraintLayout ve UI Editor‘le tanıştık. Her ne kadar başlarda UI Editor mükemmel çalışmasa da zamanla çok daha iyi bir noktaya geldi ve kolay bir şekilde performanslı çalışacak UI’ımızı geliştirmeye başladık. Compose’daysa Google şimdiye kadar olan patternlardan çok daha farklı bir şekilde karşımıza çıktı. Sadece Kotlin’le ve koddan geliştirilen bir UI. İlk anons edildiğinde Anko‘ya benzetildi, ancak Anko’yu da denemediğim için performans olarak xml’le karşılatırıdğımda nasıl bir sonuç elde ederiz bilmiyorum. O da ayrı bir blog, inceleme konusu olabilir.

developer.android‘e göre Compose’un resmi tanımı “Jetpack Compose is a modern toolkit for building native Android UI. Jetpack Compose simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin APIs.“. Aslında tanımdan da anlaşılacağı gibi, yine herşeyin çıkış noktası sadeleşme ve hızlanma. Compose geliştirilirken React, Vue.js ve Flutter‘dan esinlenilmiş. Tabi bu arada en başta söylemekte fayda var, Compose halen developer preview’de, üretim ortamı için önerilmiyor, herşeyi de yapabilir durumda değil, halen geliştiriliyor. Compose’un xml’den farklı olduğu bir nokta da declarative olması. Türkçe’ye çevirdiğimizde çok anlamlı bir kelime olmuyor ama bunu Android hiyerarşisiyle ilgili işleri delege ediyor gibi düşünebiliriz. UI’ın kullandığı data güncellendiğinde de otomatik olarak bu değişiklik UI’a yansıyor. Declarative vs Imperative konulu okuduğum yazılardan en çok sevdiğimden de bir alıntı yaparak biraz daha anlaşılabilir olmasını sağlamaya çalışacağım. Declarative Programming bir arkadaşımızdan manzara fotoğrafı çizmesini istemek gibi. Ama arkadaşımızın nasıl çizdiğiyle ilgilenmiyoruz, tamamen ona kalmış. Imperative Programming ise arkadaşımızın Bob Ross’dan nasıl manzara çizeceğini dinlemesi ve beklenen sonucu almak için Bob Ross’un verdiği step’leri adım adım takip ederek manzara fotoğrafını çizmesi gibi tanımlanabilir.

Hadi nasıl birşeyle karşılacağımızı görelim, deneyelim artık. Google Codelabs altında Compose Basics codelab’ini inceleyerek Compose’un nasıl kullanılabileceği hakkında temel bilgi sahibi olabilirsiniz. Compose’u deneyelimlemek için Android Studio min version 4.0 olması gerekli, en iyi deneyimi almak için ise son Canary Preview‘ı download edebilirsiniz. Bu versiyonu kurduğunuzda ise proje templateleri altında Compose’u da görecekseniz. Basitçe buradan bir hello world uygulaması yaratıp incelemeniz mümkün. Yine ayrıca Google’ın code örneklerinde yer alan compose’la yazılmış olan Jetnews uygulamasını incelemek de nasıl bir yapı kurulacağı hakkında daha detaylı bilgi verebilir.

UI as function: Compose’la UI fonksiyon olarak tanımlanır ve bu fonksiyon datayı view hiyerarşisine dönüştürür. Aşağıdaki kod örneğine bakarsak, input basit bir data parçası, isim; output ise bir text widget’ı. İsmi güncellemek istediğimizde, fonksiyonu yeni datayla tekrar çağırmak yeterli olacak, ui hiyerarşisi yeni bir textle text widget içermiş olacak. Kodu inflate etmek ve update etmek için iki farklı kod bloğuna da artık ihtiyacımız olmayacağı için bu kodumuzu ciddi anlamda kısaltmış olacak. Eğer bu kodu uygun bir isimle mesela “world” le çağırırsak, çalıştırdığımızda ekranda “Hello world!” göreceğiz.

Yukarıda template’den boş bir Compose kullanan boş bir Activity class oluşturabileceğimizden bahsetmiştim. Template’le oluşan Activity Class’ı aşağıdaki gibi görünecek. Bu şekilde oluşturduğumuzda ek birşey yapmadan Compose’u projemiz içerisinde kullanabiliyoruz, yani gradle dosyasında dependency’si ekli ve Android Studio’nun Compose’la çalışabilmesi için gerekli olan buildFeatures { compose true} olarak set edilmiş olacak.

setContent bloğu activity’nin layoutunu tanımlar. xml inflate etmek yerine burada composable function’ı çağırıyoruz. Jetpack Compose composable fonksiyonları uygulamanın UI elementlerine çevirebilmek için custom bir Kotlin compiler plugini kullanır. Mesela, Text() fonksiyonu Compose UI kütüphanesinde tanımlıdır, uygulamada bir text element tanımlamak için bu fonksiyonu çağırabiliriz.

Bir de daha önce Android Studio’da karşılamadığımız yeni bir feature görüyoruz, default preview. @Preview’la annotate ettiğimiz compose fonksiyonunu default preview kısmında görebiliriz. Bana da bu da biraz flutter’daki hot reload’ı anımsattı.

Peki az önce Composable Fonksiyon dedik, Composable Fonksiyon nedir dersek, Composable Fonksiyon @Composable ‘la annotate edilen bir fonskiyondur, sadece diğer composable fonksiyonların scope’unda çağrılabilir. Bu da bizim yazdığımız Composable fonksiyonun diğer @Composable fonksiyonları çağırabilmesini sağlar.

Compose Single Responsibility Principle‘ı izler. @Composable fonksiyonlar sadece tek bir fonksiyonality’den sorumludur. Mesela aşağıdaki örnekteki gibi bazı fonksiyonlara background vermek istersek Surface Composable fonksiyonunu kullanmamız gerekir. Başka bir built-in componentle bunu yapamayız.

Bu kodu emülatörde çalıştırdığımızda metin ekranın sol üst tarafına yapışmış olacaktır. Nasıl yaparız da view’lerin görünümünü yerleşimini modifiye edebiliriz dediğimizde ise Modifiers devreye giriyor.

Modifiers:

  • Spacing/LayoutPadding
  • AspectRatio
  • Rows
  • Column

Ben bu yazı için compose’u denemeye başladığımda son version dev-03’tü. Sonra tekrar yeni bir proje açınca gördüm ki dev-04 gelmiş. Bugün android weekly’yi okurken bir yazıda ise dev05’den dev06’ya geçerken nelerin değiştiğini okudum. Dolayısıyla yukardaki modifier isimlerinin denediğinizde değişmiş olabileceğini bilmeli ve güncelini araştırmalısınız.

En çok kullanılan, alışık olunan özelliklerden biri olan margin dev03’te Spacing, dev04’te ise LayoutPadding olarak karşımıza çıkıyor. Aşağıdaki kod örneğinde de görüldüğü gibi text yaratılırken modifier’a verilerek padding elde edilebiliyor.

Row ve Column ise adından anlaşılacağı üzere yatayda ya da dikey düzlemde view’lerimizi yerleştirmek için kullanılıyor. Column alt alta, Row ise yan yana dizilimi sağlamış oluyor. Adeta LinearLayout’da horizontal ya da vertical kullanmışız gibi.

Yukarıdaki kod örneriğini run ettiğimizde ise aşağıdaki ekranla karşılaşırız.

Peki diyelim ki uygulamamızda tüm ortak konfigürasyonlara sahip bir container yaratmak istiyoruz. Generic bir container yapmak için Composable fonksiyonumuzun Unit return eden bir Composable fonksiyonu parametre olarak alması gerekmekte. Lambdaları daha önce kullanmamış olanlar daha fazla bilgi için linkten inceleyebilirler. Burada Unit döndürüyoruz çünkü farkettiğiniz üzere tüm Composable fonksiyonların Unit döndürmesi gerekli.

Bu fonksiyon yazının başlarında kullandığımıza eşdeğer bir sonuç çıkaracak ancak çok daha flexible. Container fonksiyonlar okunabilirliği arttırması ve kodun reusable olmasına olanak sağlaması açısından uygulanması iyi bir deneyimdir.

Buradan bir başka çok kullanılan ihtiyaçla devam edelim. Uygulamamıza imaj ekleme. Bunu da DrawImage fonksiyonunu kullanarak yapıyoruz.

Imaja herhangi bir kısıtlama vermedik, run ettiğimizde aşağıdaki gibi görüntü elde etmiş oluyoruz.

Şimdi imajımız da var ancak tüm view’i kapladı. Image isteğimiz gibibir stil verebilmek için Container‘ın içerisine koymalıyız. Container’lar içerisindeki view’leri tutan ve diğer UI elemenleriyle belli bir hizalaa yapmamızı sağlayan general-purpose content objesidir. Bunu kenarda aklımızda tutmakta fayda var, geliştireceğimiz UI komplexleştikçe Container ihtiyacı da olacak. Container ekledikten sonra size ve position’ı uygulayabiliriz.

  • Height: Container’ın boyunu belirtmek içindir. Height setting’i Expanded’dan daha önceliklidir, bu sebeple aşağıdaki örnekte imajın boyu 180 DP olacak. Genişlik ise parent’ının verdiği maximum genişlik olacak.
  • Expanded: Container’ın size’ını belirtir. Default değeri false’dur. (Container’ın boyutu içerisinde çocuğun boyutu kadar) Eğer ki true yaparsak, size’ın parentı kadar olmasını istediğimizi belirtmiş oluruz.

State in Compose: Compose’un en çok savunduğu şeylerden biri, data değişikliklerinde extra birşey yapmadan direk view’in güncellenmesi. Data değiştiğinde bu fonksiyonların çağrılığ güncellenmiş bir UI yaratılması dışında Compose aynı zamanda tek bir Composable’ın hangi dataya ihtiyacı olduğuna da bakar ve sadece o componentleri recompose eder. Diğerleri data değişikliğinden etkilenmemiş olurlar.

Managing State with @Model: Ekranda görüneni güncellemek için Composable fonksiyonları farklı input parametreleriyle çağırmak yerine, Compose bize @Model annotationını öneriyor. Compose fonksiyonunun input olarak aldığı data @Model annotationını kullanıyorsa, data değiştiğinde otomatik olarak recompose olacak. @Model sayesinde Compose compiler’ı observable ve thread-safe bir şekilde class’ı rewrite eder.

Aşağıdaki kod örneğindeki Counter widget’ını kullandığımız durumda, counter’ımız her tıklayışda bir artacak ve bunun için ekstra bir geliştirme yapmadan ekranda sayının değiştiğini görebileceğiz.

Flexible Layouts: Hem Row hem Column için geçerli olmak üzere itemlarını birbirini ardına yerleştirirler. Bazı item’ların belli bir weight’le ekranı kaplamasını istiyorsak Flexible modifier’ını kullanabiliriz. (Inflexible default davranış) Flexible ilgili parametreyi match_parent yapıp, weight’ini 1 verdiğimiz case’de LinearLayout’da çalıştığı gibi çalışır.

Sırada Theming var. Şimdiye kadar konuştuklarımız daha statik olarak değiştirilen özelliklerdi. Peki biraz özelliştirmek istersek bunu nasıl yapacağız.

Theme de diğer Composable Fonksiyonlar gibi Component hiyerarşisinin bir parçasıdır. Yukarıdaki Container kullanım örneğinde app’imize Material Theme’i vermiştik. Bu da aslında Material Theme altında tanımlı olan değerleri kullanabileceğimiz anlamına geliyor. Yine örnekte parametre olarak colors alıyordu. Renkleri de özelleştirebiliriz.

MaterialTheme, Material desing standartlarına göre styling principlerine tepki veren bir Composable fonksiyondur. İçindeki tüm componentlere bu styling information geçer ve kullanılabilir.

Tamam son olarak da listelerden bahsedelim. Listeler olmadan bir app yazmak çok da mümkün olmayabilir sanırım. Bildiğiniz üzere eskiden ListView’ler vardı ve biraz da ui performasının iyi/kötü olmasının developer’ın elindeydi. Sonrasında bu problemi ortadan kaldıran RecyclerView’le tanıştık. Compose’da ise bunu scrollinglist yapıyor. Kullanımı video izlediğim kadarıyla çok daha simple. Ancak şöyle bir sorun vardı, benim denediğim dev-03’te henüz bulunmuyordu. Yeni versiyonlarla tabi gelmiş olabilir. Jetnews örnek app’ini inceliğimde şimdilik bu fonksiyonelitiyi verticalscroller’la çözdüklerini gördüm.

Compose’un genel bakış açısı ve temel componentlerin kullanımıyla ilgili umarım fikir vermesi açısından faydalı olmuştur. Genel olarak toparlayacak olursak, Compose biraz ui kararlarını developer’ın elinden alıp, basitleştirip, daha az kod yazılmasını sağlıyor. Bunlar süreci basitleştirirken komplex layoutlarda ne kadar başarılı olabileceği de soru işareti doğuruyor. Şu an hala çok yeni, developer preview’ında, gelişimini yolda incelemek biz Android geliştiriciler faydalı olacaktır.

Kod örnekleri için linkteki sample projeyi inceleyebilirsiniz. 🙂

References:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.