Android Kodunuzu Geleceğe Hazırlayın, Bölüm 2: İşlevsel Reaktif Programlama İş Başında

Yayınlanan: 2022-09-08

Fonksiyonel reaktif programlama (FRP), reaktif programlamadan gelen reaktiviteyi, fonksiyonel programlamadan bildirimsel fonksiyon kompozisyonu ile birleştiren bir paradigmadır. Karmaşık görevleri basitleştirir, zarif kullanıcı arayüzleri oluşturur ve durumu sorunsuz bir şekilde yönetir. Bu ve diğer birçok açık fayda nedeniyle, FRP kullanımı mobil ve web geliştirmede ana akım haline geliyor.

Bu, bu programlama paradigmasının anlaşılmasının kolay olduğu anlamına gelmez - hatta deneyimli geliştiriciler bile şunu merak edebilir: "FRP tam olarak nedir?" Bu öğreticinin 1. Kısmında, FRP'nin temel kavramlarını tanımladık: fonksiyonel programlama ve reaktif programlama. Bu taksit, faydalı kitaplıklara genel bir bakış ve ayrıntılı bir örnek uygulama ile sizi uygulamaya hazırlayacaktır.

Bu makale Android geliştiricileri düşünülerek yazılmıştır, ancak kavramlar genel programlama dillerinde deneyime sahip herhangi bir geliştirici için alakalı ve faydalıdır.

FRP'ye Başlarken: Sistem Tasarımı

FRP paradigması sonsuz bir durum ve olay döngüsüdür: State -> Event -> State' -> Event' -> State'' -> … . (Hatırlatma olarak, ' , "prime" olarak telaffuz edilir, aynı değişkenin yeni bir versiyonunu gösterir.) Her FRP programı, aldığı her olayla güncellenecek olan bir başlangıç ​​durumu ile başlar. Bu program, reaktif bir programdakilerle aynı öğeleri içerir:

  • Durum
  • Etkinlik
  • Bildirimsel ardışık düzen ( FRPViewModel function olarak belirtilir)
  • Gözlenebilir ( StateFlow olarak belirtilir)

Burada, genel reaktif öğeleri gerçek Android bileşenleri ve kitaplıkları ile değiştirdik:

İki ana mavi kutu, "StateFlow" ve "State", aralarında iki ana yola sahiptir. İlki "Gözlemler (değişiklikleri dinler)" aracılığıyladır. İkincisi, "Bildirir (en son durum)," mavi kutusuna "@Composable (JetpackCompose)", bu da "Kullanıcı girdisini" mavi kutuya "Olay"a dönüştürür, bu da "Tetikleyiciler" yoluyla mavi kutuya " FRPViewModel işlevi" ve son olarak "Ürünler (yeni durum)" aracılığıyla. "Durum" daha sonra "Girdi olarak davranır" aracılığıyla "FRPViewModel işlevine" geri bağlanır.
Android'de fonksiyonel reaktif programlama döngüsü.

FRP Kitaplıklarını ve Araçlarını Keşfetmek

FRP'ye başlamanıza yardımcı olabilecek ve aynı zamanda işlevsel programlamayla ilgili çeşitli Android kitaplıkları ve araçları vardır:

  • Ivy FRP : Bu derste eğitim amaçlı kullanılmak üzere yazdığım bir kütüphanedir. Bu, FRP'ye yaklaşımınız için bir başlangıç ​​noktası olarak verilmiştir, ancak uygun destekten yoksun olduğu için üretim kullanımı için tasarlanmamıştır. (Şu anda bakımını yapan tek mühendis benim.)
  • Ok : Bu, FP için en iyi ve en popüler Kotlin kitaplıklarından biridir, örnek uygulamamızda da kullanacağız. Nispeten hafif kalırken Kotlin'de işlevsel hale gelmek için ihtiyacınız olan hemen hemen her şeyi sağlar.
  • Jetpack Compose : Bu, yerel kullanıcı arayüzü oluşturmak için Android'in mevcut geliştirme araç takımıdır ve bugün kullanacağımız üçüncü kitaplıktır. Modern Android geliştiricileri için çok önemlidir - bunu öğrenmenizi ve hatta henüz yapmadıysanız kullanıcı arayüzünüzü taşımanızı tavsiye ederim.
  • Akış : Bu, Kotlin'in eşzamansız reaktif veri akışı API'sidir; Bu eğitimde onunla çalışmıyor olsak da RoomDB, Retrofit ve Jetpack gibi birçok yaygın Android kitaplığıyla uyumludur. Akış, eşyordamlarla sorunsuz çalışır ve tepkisellik sağlar. Örneğin RoomDB ile kullanıldığında Flow, uygulamanızın her zaman en son verilerle çalışmasını sağlar. Bir tabloda bir değişiklik meydana gelirse, bu tabloya bağlı akışlar hemen yeni değeri alacaktır.
  • Kotest : Bu test platformu, saf FP alan koduyla ilgili mülk tabanlı test desteği sunar.

Örnek Feet/Metre Dönüştürme Uygulaması Uygulama

Bir Android uygulamasında çalışan bir FRP örneğini görelim. Değerleri metre (m) ve fit (ft) arasında dönüştüren basit bir uygulama oluşturacağız.

Bu öğreticinin amaçları doğrultusunda, yalnızca FRP'yi anlamak için hayati önem taşıyan kod bölümlerini, basitlik adına tam dönüştürücü örnek uygulamamdan değiştirilmiş olarak ele alıyorum. Android Studio'da devam etmek istiyorsanız, projenizi bir Jetpack Compose etkinliği ile oluşturun ve Arrow ve Ivy FRP'yi kurun. 28 veya üzeri bir minSdk sürümüne ve Kotlin 1.6+ dil sürümüne ihtiyacınız olacak.

Durum

Uygulamamızın durumunu tanımlayarak başlayalım.

 // ConvState.kt enum class ConvType { METERS_TO_FEET, FEET_TO_METERS } data class ConvState( val conversion: ConvType, val value: Float, val result: Option<String> )

Eyalet sınıfımız oldukça açıklayıcıdır:

  • conversion : Neyi dönüştürdüğümüzü açıklayan bir tür—feet'ten metreye veya metreden fit'e.
  • value : Kullanıcının girdiği ve daha sonra dönüştüreceğimiz kayan nokta.
  • result : Başarılı bir dönüşümü temsil eden isteğe bağlı bir sonuç.

Ardından, kullanıcı girdisini bir olay olarak ele almamız gerekiyor.

Etkinlik

ConvEvent kullanıcı girdisini temsil etmek için kapalı bir sınıf olarak tanımladık:

 // ConvEvent.kt sealed class ConvEvent { data class SetConversionType(val conversion: ConvType) : ConvEvent() data class SetValue(val value: Float) : ConvEvent() object Convert : ConvEvent() }

Üyelerinin amaçlarını inceleyelim:

  • SetConversionType : Fitten metreye mi yoksa metreden fite mi çevireceğimizi seçer.
  • SetValue : Dönüştürme için kullanılacak sayısal değerleri ayarlar.
  • Convert : Girilen değerin dönüştürme türünü kullanarak dönüştürme işlemini gerçekleştirir.

Şimdi view modelimize devam edeceğiz.

Bildirime Dayalı İşlem Hattı: Olay İşleyici ve İşlev Bileşimi

Görünüm modeli, olay işleyicimizi ve işlev bileşimi (bildirimsel ardışık düzen) kodumuzu içerir:

 // ConverterViewModel.kt @HiltViewModel class ConverterViewModel @Inject constructor() : FRPViewModel<ConvState, ConvEvent>() { companion object { const val METERS_FEET_CONST = 3.28084f } // set initial state override val _state: MutableStateFlow<ConvState> = MutableStateFlow( ConvState( conversion = ConvType.METERS_TO_FEET, value = 1f, result = None ) ) override suspend fun handleEvent(event: ConvEvent): suspend () -> ConvState = when (event) { is ConvEvent.SetConversionType -> event asParamTo ::setConversion then ::convert is ConvEvent.SetValue -> event asParamTo ::setValue is ConvEvent.Convert -> stateVal() asParamTo ::convert } // ... }

Uygulamayı analiz etmeden önce, Ivy FRP kitaplığına özgü birkaç nesneyi parçalayalım.

FRPViewModel<S,E> , FRP mimarisini uygulayan soyut bir görünüm modeli tabanıdır. Kodumuzda, aşağıdaki yöntemlere uygulamamız gerekiyor:

  • val _state : Durumun başlangıç ​​değerini tanımlar (Ivy FRP, Flow'u reaktif veri akışı olarak kullanıyor).
  • handleEvent(Event): suspend () -> S : Event verilen bir sonraki durumu asenkron olarak üretir. Temeldeki uygulama, her olay için yeni bir eşyordam başlatır.
  • stateVal(): S : Mevcut durumu döndürür.
  • updateState((S) -> S): S ViewModel durumunu günceller.

Şimdi fonksiyon kompozisyonu ile ilgili birkaç metoda bakalım:

  • then : İki işlevi birlikte oluşturur.
  • asParamTo : f(T)'den g() = f(t) T ve f(T) türünden) bir t değeri üretir.
  • thenInvokeAfter : İki işlev oluşturur ve sonra onları çağırır.

updateState ve thenInvokeAfter sonraki kod parçacığında gösterilen yardımcı yöntemlerdir; kalan görünüm model kodumuzda kullanılacaklar.

Bildirime Dayalı İşlem Hattı: Ek İşlev Uygulamaları

Görünüm modelimiz ayrıca dönüşüm türümüzü ve değerimizi ayarlamak, gerçek dönüşümleri gerçekleştirmek ve nihai sonucumuzu biçimlendirmek için işlev uygulamalarını içerir:

 // ConverterViewModel.kt @HiltViewModel class ConverterViewModel @Inject constructor() : FRPViewModel<ConvState, ConvEvent>() { // ... private suspend fun setConversion(event: ConvEvent.SetConversionType) = updateState { it.copy(conversion = event.conversion) } private suspend fun setValue(event: ConvEvent.SetValue) = updateState { it.copy(value = event.value) } private suspend fun convert( state: ConvState ) = state.value asParamTo when (stateVal().conversion) { ConvType.METERS_TO_FEET -> ::convertMetersToFeet ConvType.FEET_TO_METERS -> ::convertFeetToMeters } then ::formatResult thenInvokeAfter { result -> updateState { it.copy(result = Some(result)) } } private fun convertMetersToFeet(meters: Float): Float = meters * METERS_FEET_CONST private fun convertFeetToMeters(ft: Float): Float = ft / METERS_FEET_CONST private fun formatResult(result: Float): String = DecimalFormat("###,###.##").format(result) }

Ivy FRP yardımcı fonksiyonlarımızı anlayarak, kodu analiz etmeye hazırız. Temel işlevsellikle başlayalım: convert . convert durumu ( ConvState ) girdi olarak kabul eder ve dönüştürülen girdinin sonucunu içeren yeni bir durum çıktısı veren bir işlev üretir. Sözde kodda bunu şöyle özetleyebiliriz: State (ConvState) -> Value (Float) -> Converted value (Float) -> Result (Option<String>) .

Event.SetValue olay işleme basittir; sadece durumu olaydan gelen değerle günceller (yani, kullanıcı dönüştürülecek bir sayı girer). Ancak Event.SetConversionType olayını işlemek, iki şey yaptığı için biraz daha ilginçtir:

  • Durumu, seçilen dönüştürme türüyle ( ConvType ) günceller.
  • Seçili dönüştürme türüne göre geçerli değeri convert için dönüştürmeyi kullanır.

Kompozisyonun gücünü kullanarak convert: State -> State fonksiyonunu diğer kompozisyonlar için girdi olarak kullanabiliriz. Yukarıda gösterilen kodun saf olmadığını fark etmiş olabilirsiniz: FRPViewModel içinde protected abstract val _state: MutableStateFlow<S> mutasyona FRPViewModel , bu da updateState {} kullandığımızda yan etkilere neden oluyor. Kotlin'de Android için tamamen saf FP kodu mümkün değildir.

Saf olmayan işlevler oluşturmak öngörülemeyen sonuçlara yol açabileceğinden, hibrit bir yaklaşım en pratik olanıdır: Çoğunlukla saf işlevleri kullanın ve saf olmayan işlevlerin kontrollü yan etkileri olduğundan emin olun. Bu tam olarak yukarıda yaptığımız şey.

Gözlenebilir ve Kullanıcı Arayüzü

Son adımımız, uygulamamızın kullanıcı arayüzünü tanımlamak ve dönüştürücümüze hayat vermek.

Dört ok sağdan işaret eden büyük gri bir dikdörtgen. Yukarıdan aşağıya, "Düğmeler" etiketli ilk ok, iki küçük dikdörtgene işaret eder: büyük harfle "Metre - fit" metnini içeren koyu mavi bir sol dikdörtgen ve "Feet to metre" metnini içeren açık mavi bir sağ dikdörtgen. "TextField" etiketli ikinci ok, sola hizalanmış "100.0" metni olan beyaz bir dikdörtgeni gösterir. "Düğme" etiketli üçüncü ok, "Dönüştür" metniyle birlikte sola hizalı yeşil bir dikdörtgeni gösterir. "Metin" etiketli son ok, sola hizalanmış mavi metni gösterir: "Sonuç: 328.08ft."
Uygulamanın kullanıcı arayüzünün bir maketi.

Uygulamamızın kullanıcı arayüzü biraz "çirkin" olacaktır, ancak bu örneğin amacı Jetpack Compose kullanarak güzel bir tasarım oluşturmak değil, FRP'yi göstermektir.

 // ConverterScreen.kt @Composable fun BoxWithConstraintsScope.ConverterScreen(screen: ConverterScreen) { FRP<ConvState, ConvEvent, ConverterViewModel> { state, onEvent -> UI(state, onEvent) } }

UI kodumuz, mümkün olan en az kod satırında temel Jetpack Oluşturma ilkelerini kullanır. Ancak bahsetmeye değer ilginç bir işlev var: FRP<ConvState, ConvEvent, ConverterViewModel> . FRP , Ivy FRP çerçevesinden birkaç şey yapan şekillendirilebilir bir işlevdir:

  • @HiltViewModel kullanarak görünüm modelini başlatır.
  • Akış kullanarak görünüm modelinin State gözlemler.
  • Olayları onEvent: (Event) -> Unit) koduyla ViewModel yayar.
  • Olay yayılımını gerçekleştiren ve en son durumu alan @Composable üst düzey bir işlev sağlar.
  • İsteğe bağlı olarak, uygulama başladığında çağrılan initialEvent geçmek için bir yol sağlar.

Ivy FRP kitaplığında FRP işlevinin nasıl uygulandığı aşağıda açıklanmıştır:

 @Composable inline fun <S, E, reified VM : FRPViewModel<S, E>> BoxWithConstraintsScope.FRP( initialEvent: E? = null, UI: @Composable BoxWithConstraintsScope.( state: S, onEvent: (E) -> Unit ) -> Unit ) { val viewModel: VM = viewModel() val state by viewModel.state().collectAsState() if (initialEvent != null) { onScreenStart { viewModel.onEvent(initialEvent) } } UI(state, viewModel::onEvent) }

Dönüştürücü örneğinin tam kodunu GitHub'da bulabilirsiniz ve UI kodunun tamamı ConverterScreen.kt dosyasının UI işlevinde bulunabilir. Uygulamayı veya kodu denemek isterseniz, Ivy FRP deposunu klonlayabilir ve sample uygulamayı Android Studio'da çalıştırabilirsiniz. Uygulamanın çalışabilmesi için öykünücünüzün daha fazla depolama alanına ihtiyacı olabilir.

FRP ile Daha Temiz Android Mimarisi

İşlevsel programlama, reaktif programlama ve son olarak işlevsel reaktif programlama hakkında güçlü bir temel anlayışla, FRP'nin avantajlarından yararlanmaya ve daha temiz ve daha sürdürülebilir Android mimarisi oluşturmaya hazırsınız.

Toptal Engineering Blog, bu makalede sunulan kod örneklerini incelediği için Tarun Goyal'a şükranlarını sunar.