Building UI with Compose

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.

@Composable
fun Greeting(name: String) {
Text (text = "Hello $name!")
}

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.

@Composable
fun Greeting(name: String) {
Text (text = "Hello $name!")
}

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.

@Composable
fun Greeting(name: String) {
Surface(color = Color.Yellow){
Text (text = "Hello $name!")
}
}
view raw Compose Surface hosted with ❤ by GitHub

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.

@Composable
fun Greeting(name: String) {
Text (text = "Hello $name!", modifier = Spacing(24.dp))
}
view raw Compose Spacing hosted with ❤ by GitHub

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.

@Composable
fun SpeakerInformation(speaker: SpeakerInfo) {
Column(
modifier = Spacing(16.dp)
) {
Text(speaker.name)
Text(speaker.title)
Text(speaker.sessionTitle)
}
}
view raw Compose Column hosted with ❤ by GitHub

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.

@Composable
fun IWDDemoApp(children: @Composable() () -> Unit) {
MaterialTheme(colors = lightThemeColors){
children()
}
}

Buradan bir baลŸka รงok kullanฤฑlan ihtiyaรงla devam edelim. Uygulamamฤฑza imaj ekleme. Bunu da DrawImage fonksiyonunu kullanarak yapฤฑyoruz.

@Composable
fun SpeakerInformation(speaker: SpeakerInfo) {
Column(
modifier = Spacing(16.dp)
) {
DrawImage(image = speaker.image)
Text(speaker.name)
Text(speaker.title)
Text(speaker.sessionTitle)
}
}

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.
@Composable
fun SpeakerInformation(speaker: SpeakerInfo) {
Column(
modifier = Spacing(16.dp)
) {
Container(modifier = Height(180.dp) wraps Expanded) {
Clip(RoundedCornerShape(5.dp)) {
DrawImage(image = speaker.image)
}
}
Text(speaker.name)
Text(speaker.title)
Text(speaker.sessionTitle)
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

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.

@Model
class CounterState(var count: Int = 0)
@Composable
fun Counter(state: CounterState) {
Button(
text = "I've been clicked ${state.count} times",
onClick = {
state.count++
},
style = ContainedButtonStyle(),
modifier = Spacing(24.dp)
)
}
view raw Compose Model hosted with ❤ by GitHub

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.

@Composable
fun StateSampleScreen(
names: List<String> = listOf("Android", "there"),
counterState: CounterState = CounterState()
) {
Column(modifier = ExpandedHeight) {
Column(modifier = Flexible(1f)) {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
}
Counter(counterState)
}
}

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.

@Composable
fun Title(title: String) {
Text(title,
style = themeTypography.h6)
}
view raw Compose theming hosted with ❤ by GitHub

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.

@Composable
fun SpeakerList(speakers: List<SpeakerInfo>){
ScrollingList(speakers) { speaker ->
SpeakerInformation(speaker)
}
}

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:

3 responses to “Building UI with Compose”

  1. baลŸarฤฑlฤฑ harika teknoloji haberleriyle dolu gรผzel bir blog olmuลŸ aslฤฑnda teknoloji hakkฤฑnda birลŸey alฤฑnฤฑrken size danฤฑลŸฤฑlmalฤฑ

Leave a Reply to TATUMฤฐGA Cancel 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 )

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.