Vo svete mobilného vývoja zohráva výber správnej architektúry aplikácií kľúčovú úlohu pri zabezpečovaní kvality kódu, udržateľnosti a škálovateľnosti. Každý rok prináša nové prístupy, knižnice a rámce navrhnuté tak, aby zjednodušili vývojový proces a štruktúrovali kód.V posledných rokoch získala architektúra MVI (Model-View-Intent) osobitnú popularitu tým, že ponúka elegantné riešenie pre správu stavu aplikácie a organizáciu jednosmerného toku dát. V tomto článku sa pozrieme na SimpleMVI – ľahké, ale výkonné riešenie na implementáciu vzoru MVI v projektoch s viacerými platformami Kotlin.Budeme skúmať základné komponenty knižnice, jej funkcie a analyzovať praktické príklady, ktoré vám pomôžu pochopiť, ako aplikovať SimpleMVI vo vašich projektoch. Čo je MVI vzor a jeho základné pojmy Model-View-Intent (MVI) je architektonický vzor pre vývoj používateľského rozhrania, inšpirovaný funkčným programovaním a reaktívnymi systémami. — data moves in one direction, forming a cycle: from user action to model change, then to view update. Unidirectional Data Flow — the application state is not changed directly; instead, a new state is created based on the previous one. Immutable State — the same user actions with the same initial state always lead to the same result. Determinism V architektúre MVI: Model predstavuje nemenný stav aplikácie, ktorý úplne opisuje údaje potrebné na zobrazenie rozhrania používateľa. Zobrazenie pasívne zobrazuje aktuálny stav a prenáša používateľské akcie ako zámery. Úmysel opisuje zámery používateľa alebo systému, ktoré môžu potenciálne zmeniť stav aplikácie. Okrem týchto základných zložiek MVI často zahŕňa: Reduktor – funkcia, ktorá berie aktuálny stav a zámer a vráti nový stav. Vedľajšie účinky – vedľajšie účinky, ktoré neovplyvňujú stav, ale vyžadujú interakciu s externými systémami (napr. navigácia, oznámenia, požiadavky API). Stručná história architektonických vzorov Architektonické vzory používateľského rozhrania sa v priebehu času výrazne vyvinuli: MVC (Model View Controller – ovládač zobrazenia modelu) Jeden z prvých vzorcov, ktorý rozdelil aplikáciu na tri komponenty: Model – údaje a obchodná logika View – používateľské rozhranie Controller – manipulácia s používateľským vstupom Hlavným problémom MVC je tesné spájanie komponentov a nejasné oddelenie zodpovedností, čo komplikuje testovanie a údržbu. MVP (modelový prezentátor) Zlepšenie v porovnaní s MVC, kde: Model – údaje a obchodná logika View – pasívne používateľské rozhranie Presenter – sprostredkovateľ medzi modelom a pohľadom MVP rieši problém testovateľnosti, ale často vedie k napučaným prezentátorom a tesnému spojeniu medzi prezentátorom a zobrazením. MVVM (Model-View-ViewModel – modelový pohľad) Ďalší krok v evolúcii: Model – údaje a obchodná logika View – používateľské rozhranie ViewModel – transformuje údaje z modelu do formátu vhodného pre zobrazenie MVVM používa koncept viazania údajov, ktorý znižuje množstvo kódu boilerplate, ale môže spôsobiť problémy so sledovaním toku údajov. MVI (Modelový pohľad na úmysel) Moderný prístup, ktorý zdôrazňuje: Predvídateľnosť – deterministický prístup k štátnemu riadeniu Immutabilita – stav sa nemení, ale nahrádza Jednosmerný tok údajov – jasná a transparentná sekvencia udalostí MVI je obzvlášť účinný pre komplexné, dátovo bohaté aplikácie s početnými používateľskými interakciami a asynchrónnymi operáciami. Prečo bol vytvorený SimpleMVI a jeho miesto medzi ostatnými knižnicami SimpleMVI bol vyvinutý s cieľom poskytnúť vývojárom jednoduchý, ale výkonný nástroj na implementáciu vzoru MVI v projektoch Kotlin Multiplatform. Zameriava sa na logiku domény bez uloženia riešení pre vrstvu rozhrania používateľa Dodržiava princíp „jednoduchosti predovšetkým“, poskytuje minimálny súbor potrebných komponentov Je optimalizovaný pre Kotlin Multiplatform, zabezpečuje kompatibilitu s rôznymi platformami Prísne kontroluje bezpečnosť vlákien, čo zaručuje, že interakcia so stavom sa vyskytuje iba na hlavnom vlákne Poskytuje flexibilnú konfiguráciu manipulácie s chybami prostredníctvom systému konfigurácie Hlavné výhody SimpleMVI oproti alternatívam: Menej závislostí a menšia veľkosť knižnice v porovnaní s komplexnejšími riešeniami Nižší vstupný prah pre pochopenie a použitie Kompletný Kotlin prístup s modernými jazykovými konštrukciami Pohodlný DSL pre popis obchodnej logiky Jasné rozdelenie zodpovednosti medzi zložkami SimpleMVI nemá za cieľ vyriešiť všetky problémy architektúry aplikácií, ale poskytuje spoľahlivý základ pre organizáciu obchodnej logiky, ktorá môže byť integrovaná s akýmikoľvek riešeniami pre rozhranie používateľa, navigáciu a ďalšie aspekty aplikácie. Základné pojmy a komponenty SimpleMVI SimpleMVI ponúka minimalistický prístup k implementácii architektúry MVI so zameraním na tri kľúčové komponenty: Store, Actor a Middleware.Každá z týchto zložiek má jedinečnú úlohu pri zabezpečovaní jednosmerného toku dát a spravovaní stavu aplikácie. Skladovanie - ústredný prvok architektúry Definícia a úloha obchodu Store je srdcom SimpleMVI – je to kontajner, ktorý uchováva stav aplikácie, spracováva zámery a vytvára vedľajšie účinky. public interface Store<in Intent : Any, out State : Any, out SideEffect : Any> { // Current state public val state: State // State flow public val states: StateFlow<State> // Side effects flow public val sideEffects: Flow<SideEffect> // Store initialization @MainThread public fun init() // Intent processing @MainThread public fun accept(intent: Intent) // Store destruction @MainThread public fun destroy() } Veľký životný cyklus Obchod má jasne definovaný životný cyklus: - instantiating the Store object with necessary dependencies Creation - calling the method, preparing internal components Initialization init() - processing intents through the method Active use accept(intent) - calling the method, releasing resources Destruction destroy() Je dôležité pochopiť, že: Všetky metódy verejného obchodu musia byť vyvolané iba na hlavnom drôte (označené @MainThread) Po zavolaní zničiť(), Obchod nie je možné použiť; pokusy o prístup k zničenému Obchodu povedú k chybe Obchod musí byť inicializovaný metódou init() pred použitím Štátne riadenie Obchod poskytuje nasledujúce možnosti pre prácu so štátom: via the property Access to the current state state via the flow Observing state changes states via the flow Processing side effects sideEffects SimpleMVI používa triedy z Kotlin Coroutines pre implementáciu toku: pre štáty a pravidelné pre vedľajšie účinky, zabezpečenie kompatibility so štandardnými prístupmi k reaktívnemu programovaniu v Kotlin. StateFlow Flow Pohodlné rozšírenia do obchodu SimpleMVI poskytuje pohodlné operátory pre prácu s úmyslami: // Instead of store.accept(intent) store + MyStore.Intent.LoadData // Instead of store.accept(intent) store += MyStore.Intent.LoadData Poskytovateľ - Implementácia obchodnej logiky Pracovné princípy aktérov Actor je komponent zodpovedný za obchodnú logiku v SimpleMVI. Prijíma zámery, spracováva ich a môže produkovať nový stav a vedľajšie účinky. Actor je sprostredkovateľom medzi používateľským rozhraním a dátami aplikácie. public interface Actor<Intent : Any, State : Any, out SideEffect : Any> { @MainThread public fun init( scope: CoroutineScope, getState: () -> State, reduce: (State.() -> State) -> Unit, onNewIntent: (Intent) -> Unit, postSideEffect: (sideEffect: SideEffect) -> Unit, ) @MainThread public fun onIntent(intent: Intent) @MainThread public fun destroy() } Každý účastník má prístup k: CoroutineScope - pre spustenie asynchrónnych operácií Funkcia aktuálneho stavu (getState) Funkcia štátnej redukcie (zníženie) Nová funkcia odosielania zámerov (onNewIntent) Funkcia odosielania vedľajších efektov (postSideEffect) pokus o spracovanie na metóda je vyzvaná Obchodom pri prijímaní nového zámeru a je hlavným vstupným bodom pre obchodnú logiku. onIntent(intent: Intent) Určuje typ prijatého zámeru Vykonáva potrebnú obchodnú logiku Aktualizácia štátu V prípade potreby vedľajšie účinky DefaultActor a DslActor: Rôzne prístupy k implementácii SimpleMVI ponúka dva rôzne prístupy k implementácii Actor: DefaultActor – objektovo orientovaný prístup class CounterActor : DefaultActor<CounterIntent, CounterState, CounterSideEffect>() { override fun handleIntent(intent: CounterIntent) { when (intent) { is CounterIntent.Increment -> { reduce { copy(count = count + 1) } } is CounterIntent.Decrement -> { reduce { copy(count = count - 1) } } is CounterIntent.Reset -> { reduce { CounterState() } sideEffect(CounterSideEffect.CounterReset) } } } override fun onInit() { // Initialization code } override fun onDestroy() { // Cleanup code } } Výhody DefaultActor: Známy prístup OOP Vhodné pre komplexnú obchodnú logiku Vhodné pre veľké projekty DslActor - funkčný prístup s DSL val counterActor = actorDsl<CounterIntent, CounterState, CounterSideEffect> { onInit { // Initialization code } onIntent<CounterIntent.Increment> { reduce { copy(count = count + 1) } } onIntent<CounterIntent.Decrement> { reduce { copy(count = count - 1) } } onIntent<CounterIntent.Reset> { reduce { CounterState() } sideEffect(CounterSideEffect.CounterReset) } onDestroy { // Cleanup code } } Výhody DslActor: Viac deklaratívny prístup Menej boilerplate kód Vhodnejšie pre malé a stredné projekty Typ Bezpečné úmyselné zaobchádzanie Oba prístupy poskytujú rovnakú funkčnosť a voľba medzi nimi závisí od preferencií vývojára a špecifiká projektu. Middleware – rozšírenie funkčnosti Účel Middleware Middleware v SimpleMVI pôsobí ako pozorovateľ udalostí v Obchode. Middleware nemôže modifikovať udalosti, ale môže na ne reagovať, čo z neho robí ideálny pre implementáciu cross-funkčnej logiky, ako je logovanie, analýza alebo debugging. public interface Middleware<Intent : Any, State : Any, SideEffect : Any> { // Called when Store is initialized public fun onInit(state: State) // Called when a new intent is received public fun onIntent(intent: Intent, state: State) // Called when state changes public fun onStateChanged(oldState: State, newState: State) // Called when a side effect is generated public fun onSideEffect(sideEffect: SideEffect, state: State) // Called when Store is destroyed public fun onDestroy(state: State) } Schopnosť skladovania a debugovania SimpleMVI obsahuje vstavanú Middleware implementáciu pre logovanie – : LoggingMiddleware val loggingMiddleware = LoggingMiddleware<MyIntent, MyState, MySideEffect>( name = "MyStore", logger = DefaultLogger ) zachytáva všetky udalosti v Obchode a odosiela ich do denníka: LoggingMiddleware MyStore | Initialization MyStore | Intent | LoadData MyStore | Old state | State(isLoading=false, data=null) MyStore | New state | State(isLoading=true, data=null) MyStore | SideEffect | ShowLoading MyStore | Destroying To je užitočné pre riešenie problémov, pretože vám umožní sledovať celý tok údajov v aplikácii. Implementácia Custom Middleware Vytvorenie vlastného Middleware je veľmi jednoduché: class AnalyticsMiddleware<Intent : Any, State : Any, SideEffect : Any>( private val analytics: AnalyticsService ) : Middleware<Intent, State, SideEffect> { override fun onInit(state: State) { analytics.logEvent("store_initialized") } override fun onIntent(intent: Intent, state: State) { analytics.logEvent("intent_received", mapOf("intent" to intent.toString())) } override fun onStateChanged(oldState: State, newState: State) { analytics.logEvent("state_changed") } override fun onSideEffect(sideEffect: SideEffect, state: State) { analytics.logEvent("side_effect", mapOf("effect" to sideEffect.toString())) } override fun onDestroy(state: State) { analytics.logEvent("store_destroyed") } } Middleware možno kombinovať a vytvoriť reťazec manipulátorov: val store = createStore( name = storeName<MyStore>(), initialState = MyState(), actor = myActor, middlewares = listOf( loggingMiddleware, analyticsMiddleware, debugMiddleware ) ) Kľúčové prípady použitia pre Middleware — recording all events for debugging Logging — tracking user actions Analytics — measuring intent processing time Performance metrics — visualizing data flow through UI Debugging — verifying the correctness of event sequences Testing Je dôležité si uvedomiť, že Middleware je pasívny pozorovateľ a nemôže modifikovať udalosti, ktoré dostáva. Spolupráca s knižnicou Inštalácia a inštalácia Pridanie závislosti do vášho projektu: // build.gradle.kts implementation("io.github.arttttt.simplemvi:simplemvi:<version>") Vytvorenie vášho prvého obchodu Najjednoduchší spôsob, ako vytvoriť obchod, je deklarovať triedu implementujúcu rozhranie obchodu: class CounterStore : Store<CounterStore.Intent, CounterStore.State, CounterStore.SideEffect> by createStore( name = storeName<CounterStore>(), initialState = State(), actor = actorDsl { onIntent<Intent.Increment> { reduce { copy(count = count + 1) } } onIntent<Intent.Decrement> { reduce { copy(count = count - 1) } } } ) { sealed interface Intent { data object Increment : Intent data object Decrement : Intent } data class State(val count: Int = 0) sealed interface SideEffect } Používanie obchodu // Creating an instance val counterStore = CounterStore() // Initialization counterStore.init() // Sending intents counterStore.accept(CounterStore.Intent.Increment) // or using operators counterStore + CounterStore.Intent.Increment counterStore += CounterStore.Intent.Decrement // Getting the current state val currentState = counterStore.state // Subscribing to the state flow val statesJob = launch { counterStore.states.collect { state -> // Useful work } } // Subscribing to side effects val sideEffectsJob = launch { counterStore.sideEffects.collect { sideEffect -> // Processing side effects } } // Releasing resources counterStore.destroy() Podpora viacerých platforiem Kotlin SimpleMVI podporuje rôzne platformy prostredníctvom Kotlin Multiplatform: Androidový IOS Mačiatko Šťava JS Použitie mechanizmov izolácie kódu špecifických pre platformu : expect/actual // Common code public expect fun isMainThread(): Boolean // Android implementation public actual fun isMainThread(): Boolean { return Looper.getMainLooper() == Looper.myLooper() } // iOS implementation public actual fun isMainThread(): Boolean { return NSThread.isMainThread } // wasm js implementation public actual fun isMainThread(): Boolean { return true // JavaScript is single-threaded } Logging je podobne implementovaný pre rôzne platformy: // Common code public expect fun logV(tag: String, message: String) // Android implementation public actual fun logV(tag: String, message: String) { Log.v(tag, message) } // iOS/wasm js implementation public actual fun logV(tag: String, message: String) { println("$tag: $message") } Praktický príklad: Counter Definícia veľkého dátového modelu class CounterStore : Store<CounterStore.Intent, CounterStore.State, CounterStore.SideEffect> { // Intents - user actions sealed interface Intent { data object Increment : Intent data object Decrement : Intent data object Reset : Intent } // State data class State( val count: Int = 0, val isPositive: Boolean = true ) // Side effects - one-time events sealed interface SideEffect { data object CounterReset : SideEffect } } Veľká implementácia class CounterStore : Store<CounterStore.Intent, CounterStore.State, CounterStore.SideEffect> by createStore( name = storeName<CounterStore>(), initialState = State(), actor = actorDsl { onIntent<Intent.Increment> { reduce { copy( count = count + 1, isPositive = count + 1 >= 0 ) } } onIntent<Intent.Decrement> { reduce { copy( count = count - 1, isPositive = count - 1 >= 0 ) } } onIntent<Intent.Reset> { reduce { State() } sideEffect(SideEffect.CounterReset) } } ) { // Data model defined above } Pripojenie k UI (Android Príklad) class CounterViewModel : ViewModel() { private val store = CounterStore() init { // Built-in extension for automatic lifecycle management attachStore(store) } val state = store.states.stateIn( scope = viewModelScope, started = SharingStarted.Eagerly, initialValue = store.state ) val sideEffects = store.sideEffects fun increment() { store.accept(CounterStore.Intent.Increment) } fun decrement() { store.accept(CounterStore.Intent.Decrement) } fun reset() { store.accept(CounterStore.Intent.Reset) } } Pokročilé funkcie Konfigurácia knižnice SimpleMVI poskytuje flexibilný systém konfigurácie: configureSimpleMVI { // Strict error handling mode (throws exceptions) strictMode = true // Logger configuration logger = object : Logger { override fun log(message: String) { // Your logging implementation } } } Chybné spôsoby konania strictMode = true - knižnica pracuje v striktnom režime a hodí výnimky, keď sa zistia chyby strictMode = false (predvolené) - knižnica funguje v režime lenivosti a iba zaznamenáva chyby bez prerušenia vykonávania Chybné konanie SimpleMVI má špeciálne výnimky: NotOnMainThreadException - pri pokuse o volanie metód Store nie z hlavného drôtu StoreIsNotInitializedException - pri pokuse o použitie neinicializovaného obchodu StoreIsAlreadyDestroyedException - pri pokuse o použitie už zničeného obchodu Testovanie komponentov Vďaka čistému oddeleniu zodpovedností je jednoduché otestovať komponenty SimpleMVI: // Example of Store testing @Test fun `increment should increase counter by 1`() { // Arrange val store = CounterStore() store.init() // Act store.accept(CounterStore.Intent.Increment) // Assert assertEquals(1, store.state.count) assertTrue(store.state.isPositive) // Cleanup store.destroy() } záver Keďže mobilný vývoj sa stáva čoraz zložitejší a požiadavky na kvalitu kódu a udržateľnosť aplikácií rastú, výber správnej architektúry sa stáva rozhodujúcim rozhodnutím. SimpleMVI ponúka moderný, elegantný prístup k organizácii kódu založený na princípoch vzorov MVI a prispôsobený pre multiplatformný vývoj s Kotlinom. Hlavné výhody SimpleMVI V súhrne možno zdôrazniť nasledujúce silné stránky knižnice: Minimalizmus a pragmatický prístup SimpleMVI poskytuje len potrebné komponenty pre implementáciu vzoru MVI, bez zbytočných abstrakcií a zložitostí. knižnica dodržiava princíp "jednoduchosti predovšetkým", čo z neho robí jednoduché pochopiť a používať aj pre vývojárov, ktorí sa práve zoznámili s architektúrou MVI. Plná podpora Kotlin Multiplatform Knižnica izoluje kód špecifický pre platformu prostredníctvom mechanizmu expect/real, čím zabezpečuje kompatibilitu s Androidom, iOS, macOS a wasm js. Predvídateľný štátny manažment Prísne dodržiavanie zásad štátnej nemennosti a jednosmerného toku dát robí aplikácie postavené na SimpleMVI predvídateľnejšie a menej náchylné na chyby.Každá zmena stavu prebieha prostredníctvom jasne definovaného procesu, ktorý zjednodušuje debugovanie a testovanie. Vstavaná ochrana proti bežným problémom Knižnica poskytuje prísnu kontrolu bezpečnosti vlákien, ktorá zaisťuje, že interakcia so stavom sa vyskytuje iba na hlavnom vlákne.Toto zabraňuje mnohým bežným chybám súvisiacim s multithreading, ktoré môžu byť ťažké zistiť a opraviť. Pohodlný DSL pre deklaratívny logický popis Vďaka podpore DSL umožňuje SimpleMVI popísať obchodnú logiku v deklaratívnom štýle, čím sa kód stáva čitateľnejším a zrozumiteľnejším. Flexibilita a rozšíriteľnosť Napriek minimalistickému prístupu poskytuje SimpleMVI mechanizmy na rozšírenie funkčnosti prostredníctvom systému Middleware.To uľahčuje pridávanie funkcií, ako je logovanie, analýza alebo debugging bez ovplyvnenia základnej obchodnej logiky. Typické prípady použitia SimpleMVI je obzvlášť vhodný pre nasledujúce scenáre: Projekty Kotlin Multiplatform Ak vyvíjate aplikáciu, ktorá potrebuje pracovať na viacerých platformách (Android a iOS, webové aplikácie), SimpleMVI vám umožňuje používať jediný architektonický prístup a zdieľaný obchodný logický kód. Aplikácie so zložitými štátnymi a užívateľskými interakciami Pre aplikácie, ktoré spravujú komplexný stav a zaoberajú sa početnými používateľskými interakciami, prístup MVI poskytuje jasnú štruktúru a predvídateľnosť. Projekty s dôrazom na testovateľnosť Vďaka jasnému oddeleniu zodpovednosti medzi komponentmi a predvídateľnému toku údajov sú aplikácie postavené pomocou SimpleMVI ľahko jednotkovo testovateľné. Migrácia existujúcich projektov do architektúry MVI SimpleMVI je možné zaviesť postupne, počnúc jednotlivými modulmi alebo funkciami, čím je vhodný pre postupnú migráciu existujúcich projektov do architektúry MVI. Vzdelávacie projekty a prototypy Vďaka svojej jednoduchosti a minimalizmu je SimpleMVI vhodný na výučbu princípov MVI a na rýchle prototypovanie. Zdroje pre ďalšie vzdelávanie Pre tých, ktorí chcú prehĺbiť svoje vedomosti o architektúre SimpleMVI a MVI vo všeobecnosti, odporúčam nasledujúce zdroje: SimpleMVI GitHub repository – zdrojový kód knižnice s príkladmi použitia Dokumentácia SimpleMVI – oficiálna dokumentácia s podrobným popisom a odporúčaniami API Konečné myšlienky SimpleMVI predstavuje vyvážené riešenie pre organizovanie podnikovej logiky aplikácií pomocou moderných prístupov k architektúre. Knižnica ponúka jasnú štruktúru a predvídateľný tok dát bez toho, aby si vyžadovala zbytočnú zložitosť. Pri výbere architektúry pre váš projekt nezabudnite, že neexistuje univerzálne riešenie vhodné pre všetky prípady. SimpleMVI môže byť vynikajúcou voľbou pre projekty, kde sa oceňuje jednoduchosť, predvídateľnosť a podpora viacerých platforiem, ale pre niektoré scenáre môžu byť vhodnejšie iné knižnice alebo prístupy. Experimentujte, skúmajte rôzne architektonické riešenia a vyberte to, čo najlepšie vyhovuje potrebám vášho projektu a tímu.A pamätajte: najlepšia architektúra je tá, ktorá vám pomôže efektívne riešiť úlohy, ktoré sú na dosah ruky, nie tá, ktorá vytvára dodatočnú zložitosť.