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.


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 .

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 AndroidDataFlow
ve getWeatherOfTheDay
bazı 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 Weather
durum 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 setState
veya 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, setState
2. 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 safeCall
riskli bir ifadeyi Try
tü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.
GIPHY App Key not set. Please check settings