Android’in tek yönlü veri akışını Kotlin ile yapmak

Google, 2017’de , geliştiricilere uygulamalarını geliştirmeleri için gerçek destek sunan Android Mimarisi bileşenlerini piyasaya sürdü . İlk sürümden bu yana, birçok şirket için bu bileşenlerle yoğun olarak çalışıyorum. Ayrıca, 2018’den bu yana Avrupa’da birçok konferans için atölye çalışmaları vermek için iyi bir fırsat oldu .

Tanınmış MVP mimarisi , görünümleri ve denetleyicisini bir sözleşmeyle birbirine bağlayan 1 – 1 sözleşmedeki işleri tutarken, MVVM mimarisi yaklaşımı denetleyicinizi gözlemlemenin ve veri akışı olarak sonuçları almanın bir yolunu sunar. Ama sonra bir yerde, View ve ViewModel arasında bir tür “resmi sözleşme” kaybediyoruz. Hiçbir şey sizi ViewModel’inizi bu şekilde veya bu şekilde yapılandırmaya zorlamaz. Biz sadece görüşlere “güncellemeler” uyguluyoruz.

Zaten 2016 yılında, konuyla ilgili çok iyi bir geri bildirim vardı. Florina Muntenescu ve Lucia Payo’dan gelen bu 2 makale hala çok iyi referanslar: Android Mimarisi Modelleri Bölüm 3: Model-View-ViewModel ve MVVM + RxJava: Genel Hatalar .

“Android Öncesi Mimari Bileşenleri” stilini RxJava tesisatçılığı ile örtse bile, ana fikir aynı kalır: ViewModel, olayları farklı olay akışları arasında yaymak yerine, çoğunlukla verileri eyalet olarak yayınlamalıdır. Neden? Zaman içinde verilerimizin görünüm için tutarlı kalmasını sağlamalıyız.

Görünüm verilerini tanımlamak için birkaç veri akışı
Görünüm durumunu tanımlamak için bir veri akışı

Bu tür bir yaklaşımdan yükselen test edilebilirlik gerçekten harika. ViewModel’iniz bir seferde bir duruma sahip olduğundan, test etmek sadece test durumları dizisidir! Dahası, artık herhangi bir durumu tekrar oynatabilir ve daha sonra herhangi bir senaryoyu kolayca yaratabilirsiniz!

ViewModel, Görünümünüz için tüm mantıksal soyutlamaları işler. Görünüm, eylemleri bağlamak ve durumları ve olayları oluşturmak için burada. Başka bir şey hakkında düşünmeni sağlıyor mu? Tek yönlü veri akışına tepki !

Başka bir iyi fikir hakkında da düşünebiliriz: MVI. İşte Hannes Dorman’ın mükemmel dizileri http://hannesdorfmann.com/android/mosby3-mvi-1

… Ama üzgünüm, benim için çok fazla ders! Sadece birkaç satır yazıp, ViewModel dersime sadık kalabileceğim bir şey istiyorum. Bunu nasıl yapabiliriz? 

ViewModel, İşlemler ve Durumlar

Tüm bunları biraz tanımla resmileştirelim:

  • bir durum bir kerede ViewModel tarafından gösterilecek olan bir veri seti tanımlar.
  • Eylem , ViewModel tarafından sunulan ve yeni bir ViewModel durumu oluşturmanıza izin veren bir işlevdir.
  • ViewModel eylemleri ortaya çıkarır ve durumunu değiştirmesine izin verilen tek bileşendir

Görünüm, bir durum akışını gözlemler (veri sınıfı, mühürlü sınıf, miras kalan UIState).

Bu potansiyel devletler , Görünüm tarafından kullanılacak olan sözleşmeyi temsil eder . Bir ViewModel, Eylemleri görünüme gösterir ve sonuçları durum akışları olarak sunar.

Örnek bir uygulama alalım

Bir örnek olalım (bazı kişiler atölyelerimden aşağıdaki ekran görüntüsünü tanır). Son ekrana odaklanalım: bir günün hava durumunu gösterir .

Hava Durumu Uygulaması – Son ekran bir günlük hava durumunu göstermek üzere

Görüşümüze göstermek istediğimiz ülkeleri (sözleşmemizi) yazalım :

  • Bir init devleti (henüz hiçbir veriye sahip olmadığımız)
  • “Hava Durumu” durumu (ana verilerimiz için)
  • bir şeylerin yanlış gitmesi durumunda başarısız bir devlet
sealed class WeatherViewState : UIState(){
  object Init : WeatherViewState()
  data class Weather(val day : String, val temperature : String) : WeatherViewState()
  data class Failed(val error : Exception) : WeatherViewState()
}

Şimdi genişleyen AndroidDataFlowve getWeatherOfTheDaybazı durumları zorlama eylemini tanımlayalım bir sınıf oluşturalım :

class WeatherDataFlow(val repo : WeatherRepository) : AndroidDataFlow() {

    init {
        //set initial state
        setState { WeatherViewState.Init }
    }
    
    // Our Action here
    fun getWeatherOfTheDay(day : String) = setState {
        // background call to get weather data for the day
        val weather = repo.getWeather(day).await()
        // make a state to render for the UI
        WeatherViewState.Weather(weather.day, weather.temperature)
    }
}

Bu getWeatherOfTheDay, belirtilen gün için hava durumunu alır (String cinsinden) ve Weatherdurum güncellemesini görünüme itecektir .

Her durum, yalnızca ViewModel tarafından yayımlanan değişmez bir Kotlin verisidir (veri sınıfı veya nesne).

Görünüm, yalnızca ViewModel eylemlerini çağırır ve UI oluşturma için gözlemlenen sonucu bağlar. Aktivitenin işlevi onStates, gelen durumları gözlemlemeye izin verir. Burada doğrudan “elle” bağlanıyoruz.

class WeatherActivity : AppCompatActivity {
  
  // ViewModel created with Koin for example :)
  val weatherFlow : WeatherDataFlow by viewModel()

  override fun onCreate(savedInstanceState: Bundle?) {		
      // Observe incoming states
      onStates(weatherFlow) { state ->
          when (state) {
              // react on each updates
              is WeatherViewState.Init -> showEmptyView()
              is WeatherViewState.Weather -> showWeather(state)
              is WeatherViewState.Failed -> showError(state.error)
          }
      }
  }
}

Son olarak, akışınızı Mockk ile çok kolay bir şekilde test edebilirsiniz :

@Test
fun `has some weather`() {
	// prepare test data
	val day = "Friday"
	val weatherData = WeatherData(...)
	
	// mock coroutine call
	coEvery { mockedRepo.getWeather(day) } return weatherData

	dataFlow.getWeatherOfTheDay(day)
		
	// verify state sequence
	verifySequence {
    		view.hasState(WeatherViewState.Init)
		view.hasState(WeatherViewState.Weather(weatherData.day, weatherData.temperature))
	}
}

Sistemimiz zaman içinde tahmin edilebilir veri durumlarına sahiptir! Paralel olarak birlikte tetiklenen birkaç eyleminiz olsa bile, bir seferde sadece bir durumumuz olmasını sağlıyoruz.

Uygulamanız hata ayıklamak daha kolaydır: Her durum güncellemesi Uniflow tarafından izlenir. Bu durum kaydı, herhangi bir kayıt sistemine kolayca yönlendirilebilir (Crashlytics…). Son olarak, üretimde neler olup bittiğini anlamak ve herhangi bir buggy durumunu tekrarlamak oldukça kolaydır.

Uniflow, belirli bir devlet için bir koruma sağlamamıza izin veriyor: sadece belirli bir eyalette olmamız durumunda bir eylemi tetiklememizi sağlıyor ve ardından tutarlı bir devlet akışına sahibiz.

Bazen, yalnızca yeni UI güncellemelerini zorlamak istemezsiniz. Bu durumda, biz kullanacağız olayları için tetik “yan etkileri”. Kullanımları durumlara çok benzer , belgeleri kontrol edin .

Kotlin Coroutines için hazır ?

Görmüş olabileceğiniz gibi, herhangi bir eylem kodu bloğuna doğrudan Kotlin koroutinlerini yazabiliriz . Sadece setStateveya başka herhangi bir eylem oluşturucuyu kullanın , IO Dispatcher’da varsayılan olarak herhangi bir coroine kodunu çalıştırmanıza izin verecektir.

fun getWeatherOfTheDay(day : String) = setState ({
      // background call to get weather data for the day
      val weather = repo.getWeather(day).await()
      // make a state to render for the UI
      WeatherViewState.Weather(weather.day, weather.temperature)
  },
  // Got error
  { error -> WeatherViewState.Failed(error = error) })

Bir eylem işlevinin yalnızca bir UIState nesnesi döndürmesi gerekir, LiveData ile ana iş parçacığına sizin için zorlanır.

Harika! Ancak… Kotlin coroutines ile çalışan herkes hataları dikkatlice ele almanız gerektiğini biliyor. Neden? Çünkü coroutinler istisnalara dayanır ve daha sonra etrafındaki herhangi bir sorunu yakalamak için eski “dene / yakala” bloğunu kullanmanız gerekir.

Uniflow tarafından sunulan bir yol, bir geri dönüş lambda işlevi sunarak bu “dene / yakala” bloğunu sizin için yapıp, hata durumunda işleminizi yapmanıza olanak sağlamaktır. Aşağıda, setState2. lambda ifadesinin herhangi bir hatayı yakalamasına izin verin:

fun getWeatherOfTheDay(day : String) = setState ({
      // background call to get weather data for the day
      val weather = repo.getWeather(day).await()
      // make a state to render for the UI
      WeatherViewState.Weather(weather.day, weather.temperature)
  },
  // Got error
  { error -> WeatherViewState.Failed(error = error) })

İşlevsel programlama ile daha güvenli akış

İşlemlerinizi yazmak için zorunlu kodu mükemmel bir şekilde yazabilir ve Uniflow tarafından sunulan hata işlemeyi kullanabilirsiniz. Ancak, belirli bloklarda try / catch kullanmadan kodunuzun akışını daha kesin bir şekilde kontrol etmeniz gerekebilir.

Hatalara yol açabilecek ifadelerle başa çıkmanın başka bir yolu (aka “yan etkiler”), Arrow-kt.io ile bazı işlevsel iyilikler getirmektir . Burada safeCallriskli bir ifadeyi Trytür ile sarmak için kullanıyoruz :

safeCall { weatherDatasource.geocode(targetLocation).await() }
	    .map { it.mapToLocation() }
	    .flatMap { (lat, lng) -> safeCall { weatherDatasource.weather(lat, lng).await() } }
	    .map { it.mapToWeatherEntities() }
	    .onSuccess { weatherCache.save(it) }

Yapılan herhangi bir işlevsel ifadeden başarı ve başarısız durumları kolayca gerçekleştirebilirsiniz toState:

fun getWeatherOfTheDay(day : String) = setState {
      // background call to get weather data for the day
      safeCall { repo.getWeather(day).await() }
        // render to success state
        .toState { weather -> WeatherViewState.Weather(weather.day, weather.temperature) }
  }, 
  { error -> WeatherViewState.Failed(error = error) })

Uniflow, tüm bu fikirleri küçük bir Kotlin / Android kütüphanesinde birleştirdi. Günlük ekiplerimle kullanırım. Birkaç aydır üretimde kullanıyoruz.

Gradle ile kurulum yapmak için Uniflow GitHub projesini kontrol edin . Bağımlılıklarına eklemek için sadece bir satır:

// Jcenter()
// Core
implementation 'io.uniflow:uniflow-core:$version'
testImplementation 'io.uniflow:uniflow-test:$version'

// Android
implementation 'io.uniflow:uniflow-android:$version'
testImplementation 'io.uniflow:uniflow-android-test:$version'

// AndroidX
implementation 'io.uniflow:uniflow-androidx:$version'
testImplementation 'io.uniflow:uniflow-androidx-test:$version'

Bu konuda daha fazla açıklamak için daha eksiksiz bir uygulama örneği yayınlayacağım. Arrow-kt.io ile daha fazla entegrasyon veya doğrudan Android Compose bileşenleriyle entegrasyon hayal edebiliriz.

Comments

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

GIPHY App Key not set. Please check settings

Yükleniyor…

0

Ne düşünüyorsun?

Mustafa Kemal Atatürk’ün Müzik Zevki

Kotlin | Animasyon | 2020