Android Kodunuzu Geleceğe Hazırlayın, Bölüm 2: İşlevsel Reaktif Programlama İş Başında
Yayınlanan: 2022-09-08Fonksiyonel 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:
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)'deng() = f(t)
T
vef(T)
türünden) birt
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.
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)
koduylaViewModel
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.