{"id":13407084,"url":"https://github.com/icerockdev/moko-mvvm","last_synced_at":"2025-05-15T11:08:48.509Z","repository":{"id":35640238,"uuid":"204883322","full_name":"icerockdev/moko-mvvm","owner":"icerockdev","description":"Model-View-ViewModel architecture components for mobile (android \u0026 ios) Kotlin Multiplatform development","archived":false,"fork":false,"pushed_at":"2024-04-18T12:22:17.000Z","size":1381,"stargazers_count":1064,"open_issues_count":53,"forks_count":96,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-05-07T05:04:01.144Z","etag":null,"topics":["android","cocoapod","coroutines","databinding","ios","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","kotlin-native","livedata","moko","mvvm","swift","viewmodel"],"latest_commit_sha":null,"homepage":"https://moko.icerock.dev/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icerockdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-28T08:23:07.000Z","updated_at":"2025-04-29T23:02:44.000Z","dependencies_parsed_at":"2024-12-21T08:08:36.667Z","dependency_job_id":"98fbb99c-7a0a-417e-bbe7-b79bf37da22b","html_url":"https://github.com/icerockdev/moko-mvvm","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icerockdev%2Fmoko-mvvm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icerockdev%2Fmoko-mvvm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icerockdev%2Fmoko-mvvm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icerockdev%2Fmoko-mvvm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icerockdev","download_url":"https://codeload.github.com/icerockdev/moko-mvvm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328385,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","cocoapod","coroutines","databinding","ios","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","kotlin-native","livedata","moko","mvvm","swift","viewmodel"],"created_at":"2024-07-30T20:00:19.377Z","updated_at":"2025-05-15T11:08:48.479Z","avatar_url":"https://github.com/icerockdev.png","language":"Kotlin","readme":"![moko-mvvm](img/logo.png)  \n[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/dev.icerock.moko/mvvm-core) ](https://repo1.maven.org/maven2/dev/icerock/moko/mvvm-core) ![kotlin-version](https://kotlin-version.aws.icerock.dev/kotlin-version?group=dev.icerock.moko\u0026name=mvvm-core)\n![badge][badge-android]\n![badge][badge-ios]\n![badge][badge-mac]\n![badge][badge-watchos]\n![badge][badge-tvos]\n![badge][badge-jvm]\n![badge][badge-js]\n![badge][badge-windows]\n![badge][badge-linux]\n\n# Mobile Kotlin Model-View-ViewModel architecture components\nThis is a Kotlin Multiplatform library that provides architecture components of Model-View-ViewModel\n for UI applications. Components are lifecycle-aware on Android.  \n\n## Table of Contents\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Documentation](#documentation)\n- [Usage](#usage)\n- [Samples](#samples)\n- [Set Up Locally](#set-up-locally)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n- **ViewModel** - store and manage UI-related data. Interop with `Android Architecture Components` - on Android it's precisely `androidx.lifecycle.ViewModel`;\n- **LiveData, MutableLiveData, MediatorLiveData** - lifecycle-aware reactive data holders with set of operators to transform, merge, etc.;\n- **EventsDispatcher** - dispatch events from `ViewModel` to `View` with automatic lifecycle control and explicit interface of required events;\n- **DataBinding, ViewBinding, Jetpack Compose, SwiftUI support** - integrate to Android \u0026 iOS app with commonly used tools;\n- **All Kotlin targets support** - `core`, `flow` and `livedata` modules support all Kotlin targets.\n\n## Requirements\n- Gradle version 6.8+\n- Android API 16+\n- iOS version 11.0+\n\n## Installation\nroot build.gradle  \n```groovy\nallprojects {\n    repositories {\n        mavenCentral()\n    }\n}\n```\n\nproject build.gradle\n```groovy\ndependencies {\n    commonMainApi(\"dev.icerock.moko:mvvm-core:0.16.1\") // only ViewModel, EventsDispatcher, Dispatchers.UI\n    commonMainApi(\"dev.icerock.moko:mvvm-flow:0.16.1\") // api mvvm-core, CFlow for native and binding extensions\n    commonMainApi(\"dev.icerock.moko:mvvm-livedata:0.16.1\") // api mvvm-core, LiveData and extensions\n    commonMainApi(\"dev.icerock.moko:mvvm-state:0.16.1\") // api mvvm-livedata, ResourceState class and extensions\n    commonMainApi(\"dev.icerock.moko:mvvm-livedata-resources:0.16.1\") // api mvvm-core, moko-resources, extensions for LiveData with moko-resources\n    commonMainApi(\"dev.icerock.moko:mvvm-flow-resources:0.16.1\") // api mvvm-core, moko-resources, extensions for Flow with moko-resources\n    \n    // compose multiplatform\n    commonMainApi(\"dev.icerock.moko:mvvm-compose:0.16.1\") // api mvvm-core, getViewModel for Compose Multiplatform\n    commonMainApi(\"dev.icerock.moko:mvvm-flow-compose:0.16.1\") // api mvvm-flow, binding extensions for Compose Multiplatform\n    commonMainApi(\"dev.icerock.moko:mvvm-livedata-compose:0.16.1\") // api mvvm-livedata, binding extensions for Compose Multiplatform\n\n    androidMainApi(\"dev.icerock.moko:mvvm-livedata-material:0.16.1\") // api mvvm-livedata, Material library android extensions\n    androidMainApi(\"dev.icerock.moko:mvvm-livedata-glide:0.16.1\") // api mvvm-livedata, Glide library android extensions\n    androidMainApi(\"dev.icerock.moko:mvvm-livedata-swiperefresh:0.16.1\") // api mvvm-livedata, SwipeRefreshLayout library android extensions\n    androidMainApi(\"dev.icerock.moko:mvvm-databinding:0.16.1\") // api mvvm-livedata, DataBinding support for Android\n    androidMainApi(\"dev.icerock.moko:mvvm-viewbinding:0.16.1\") // api mvvm-livedata, ViewBinding support for Android\n    \n    commonTestImplementation(\"dev.icerock.moko:mvvm-test:0.16.1\") // test utilities\n}\n```\n\nAlso required export of dependency to iOS framework. For example:\n```\nkotlin {\n    // export correct artifact to use all classes of library directly from Swift\n    targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java).all {\n        binaries.withType(org.jetbrains.kotlin.gradle.plugin.mpp.Framework::class.java).all {\n            export(\"dev.icerock.moko:mvvm-core:0.16.1\")\n            export(\"dev.icerock.moko:mvvm-livedata:0.16.1\")\n            export(\"dev.icerock.moko:mvvm-livedata-resources:0.16.1\")\n            export(\"dev.icerock.moko:mvvm-state:0.16.1\")\n        }\n    }\n}\n```\n\n### KSwift\n\nFor iOS we recommend use [moko-kswift](https://github.com/icerockdev/moko-kswift) with extensions  \ngeneration enabled. All `LiveData` to `UIView` bindings is extensions for UI elements.\n\n### SwiftUI additions\n\nTo use MOKO MVVM with SwiftUI set name of your kotlin framework to `MultiPlatformLibrary` and add\ndependency to CocoaPods:\n```ruby\npod 'mokoMvvmFlowSwiftUI', :podspec =\u003e 'https://raw.githubusercontent.com/icerockdev/moko-mvvm/release/0.16.1/mokoMvvmFlowSwiftUI.podspec'\n```\nrequired export of `mvvm-core` and `mvvm-flow`.\n\n## Usage\n### Simple view model\nLet’s say we need a screen with a button click counter. To implement it we should:\n#### common\nIn `commonMain` we can create a `ViewModel` like:\n```kotlin\nclass SimpleViewModel : ViewModel() {\n    private val _counter: MutableLiveData\u003cInt\u003e = MutableLiveData(0)\n    val counter: LiveData\u003cString\u003e = _counter.map { it.toString() }\n\n    fun onCounterButtonPressed() {\n        val current = _counter.value\n        _counter.value = current + 1\n    }\n}\n``` \nAnd after that integrate the `ViewModel` on platform the sides.\n#### Android  \n`SimpleActivity.kt`:\n```kotlin\nclass SimpleActivity : MvvmActivity\u003cActivitySimpleBinding, SimpleViewModel\u003e() {\n    override val layoutId: Int = R.layout.activity_simple\n    override val viewModelVariableId: Int = BR.viewModel\n    override val viewModelClass: Class\u003cSimpleViewModel\u003e = SimpleViewModel::class.java\n\n    override fun viewModelFactory(): ViewModelProvider.Factory {\n        return createViewModelFactory { SimpleViewModel() }\n    }\n}\n```\n`MvvmActivity` automatically loads a databinding layout, resolves `ViewModel` object and sets a databinding variable.  \n`activity_simple.xml`:\n```xml\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cvariable\n            name=\"viewModel\"\n            type=\"com.icerockdev.library.sample1.SimpleViewModel\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:padding=\"16dp\"\u003e\n\n        \u003cTextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{viewModel.counter.ld}\" /\u003e\n\n        \u003cButton\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"@{() -\u003e viewModel.onCounterButtonPressed()}\"\n            android:text=\"Press me to count\" /\u003e\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n#### iOS\n`SimpleViewController.swift`:\n```swift\nimport MultiPlatformLibrary\nimport MultiPlatformLibraryMvvm\n\nclass SimpleViewController: UIViewController {\n    @IBOutlet private var counterLabel: UILabel!\n    \n    private var viewModel: SimpleViewModel!\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        viewModel = SimpleViewModel()\n        \n        counterLabel.bindText(liveData: viewModel.counter)\n    }\n    \n    @IBAction func onCounterButtonPressed() {\n        viewModel.onCounterButtonPressed()\n    }\n    \n    override func didMove(toParentViewController parent: UIViewController?) {\n        if(parent == nil) { viewModel.onCleared() }\n    }\n}\n```\n`bindText` is an extension from the `MultiPlatformLibraryMvvm` CocoaPod.\n\n### ViewModel with send events to View\nLet’s say we need a screen from which we should go to another screen by pressing a button. To implement it we should:\n#### common\n```kotlin\nclass EventsViewModel(\n    val eventsDispatcher: EventsDispatcher\u003cEventsListener\u003e\n) : ViewModel() {\n\n    fun onButtonPressed() {\n        eventsDispatcher.dispatchEvent { routeToMainPage() }\n    }\n\n    interface EventsListener {\n        fun routeToMainPage()\n    }\n}\n```\n`EventsDispatcher` is a special class that automatically removes observers from lifecycle and buffers input\n events while listener is not attached (on the Android side).\n#### Android\n`EventsActivity.kt`:\n```kotlin\nclass EventsActivity : MvvmActivity\u003cActivityEventsBinding, EventsViewModel\u003e(),\n    EventsViewModel.EventsListener {\n    override val layoutId: Int = R.layout.activity_events\n    override val viewModelVariableId: Int = BR.viewModel\n    override val viewModelClass: Class\u003cEventsViewModel\u003e = EventsViewModel::class.java\n\n    override fun viewModelFactory(): ViewModelProvider.Factory {\n        return createViewModelFactory { EventsViewModel(eventsDispatcherOnMain()) }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        viewModel.eventsDispatcher.bind(\n            lifecycleOwner = this,\n            listener = this\n        )\n    }\n\n    override fun routeToMainPage() {\n        Toast.makeText(this, \"here must be routing to main page\", Toast.LENGTH_SHORT).show()\n    }\n}\n```\n`eventsDispatcher.bind` attaches `EventsDispatcher` to the lifecycle (in this case - to an activity) to correctly\n subscribe and unsubscribe, without memory leaks.\n\nWe can also simplify the binding of `EventsDispatcher` with `MvvmEventsActivity` and `EventsDispatcherOwnder`.\n`EventsOwnerViewModel.kt`:\n```kotlin\nclass EventsOwnerViewModel(\n    override val eventsDispatcher: EventsDispatcher\u003cEventsListener\u003e\n) : ViewModel(), EventsDispatcherOwner\u003cEventsOwnerViewModel.EventsListener\u003e {\n\n    fun onButtonPressed() {\n        eventsDispatcher.dispatchEvent { routeToMainPage() }\n    }\n\n    interface EventsListener {\n        fun routeToMainPage()\n    }\n}\n```\n`EventsOwnderActivity.kt`:\n```kotlin\nclass EventsOwnerActivity :\n    MvvmEventsActivity\u003cActivityEventsOwnerBinding, EventsOwnerViewModel, EventsOwnerViewModel.EventsListener\u003e(),\n    EventsOwnerViewModel.EventsListener {\n\n    override val layoutId: Int = R.layout.activity_events_owner\n    override val viewModelVariableId: Int = BR.viewModel\n    override val viewModelClass: Class\u003cEventsOwnerViewModel\u003e = EventsOwnerViewModel::class.java\n\n    override fun viewModelFactory(): ViewModelProvider.Factory {\n        return createViewModelFactory { EventsOwnerViewModel(eventsDispatcherOnMain()) }\n    }\n\n    override fun routeToMainPage() {\n        Toast.makeText(this, \"here must be routing to main page\", Toast.LENGTH_SHORT).show()\n    }\n}\n```\n\n#### iOS\n`EventsViewController.swift`:\n```swift\nimport MultiPlatformLibrary\nimport MultiPlatformLibraryMvvm\n\nclass EventsViewController: UIViewController {\n    private var viewModel: EventsViewModel!\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        let eventsDispatcher = EventsDispatcher\u003cEventsViewModelEventsListener\u003e(listener: self)\n        viewModel = EventsViewModel(eventsDispatcher: eventsDispatcher)\n    }\n    \n    @IBAction func onButtonPressed() {\n        viewModel.onButtonPressed()\n    }\n    \n    override func didMove(toParentViewController parent: UIViewController?) {\n        if(parent == nil) { viewModel.onCleared() }\n    }\n}\n\nextension EventsViewController: EventsViewModelEventsListener {\n    func routeToMainPage() {\n        showAlert(text: \"go to main page\")\n    }\n}\n```\nOn iOS we create an instance of `EventsDispatcher` with the link to the listener. We shouldn't call `bind` like\n on Android (in iOS this method doesn't exist).\n\n### ViewModel with validation of user input\n```kotlin\nclass ValidationMergeViewModel() : ViewModel() {\n    val email: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n    val password: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n\n    val isLoginButtonEnabled: LiveData\u003cBoolean\u003e = email.mergeWith(password) { email, password -\u003e\n        email.isNotEmpty() \u0026\u0026 password.isNotEmpty()\n    }\n}\n```\n`isLoginButtonEnabled` is observable `email` \u0026 `password` `LiveData`, and in case there are any changes it calls lambda\n with the newly calculated value.\n\nWe can also use one of these combinations:\n```kotlin\nclass ValidationAllViewModel() : ViewModel() {\n    val email: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n    val password: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n\n    private val isEmailValid: LiveData\u003cBoolean\u003e = email.map { it.isNotEmpty() }\n    private val isPasswordValid: LiveData\u003cBoolean\u003e = password.map { it.isNotEmpty() }\n    val isLoginButtonEnabled: LiveData\u003cBoolean\u003e = listOf(isEmailValid, isPasswordValid).all(true)\n}\n```\nHere we have separated LiveData with the validation flags - `isEmailValid`, `isPasswordValid` and combine both\n to `isLoginButtonEnabled` by merging all boolean LiveData in the list with on the condition that \"all values must be true\".\n\n### ViewModel for login feature\n#### common\n```kotlin\nclass LoginViewModel(\n    override val eventsDispatcher: EventsDispatcher\u003cEventsListener\u003e,\n    private val userRepository: UserRepository\n) : ViewModel(), EventsDispatcherOwner\u003cLoginViewModel.EventsListener\u003e {\n    val email: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n    val password: MutableLiveData\u003cString\u003e = MutableLiveData(\"\")\n\n    private val _isLoading: MutableLiveData\u003cBoolean\u003e = MutableLiveData(false)\n    val isLoading: LiveData\u003cBoolean\u003e = _isLoading.readOnly()\n\n    val isLoginButtonVisible: LiveData\u003cBoolean\u003e = isLoading.not()\n\n    fun onLoginButtonPressed() {\n        val emailValue = email.value\n        val passwordValue = password.value\n\n        viewModelScope.launch {\n            _isLoading.value = true\n\n            try {\n                userRepository.login(email = emailValue, password = passwordValue)\n\n                eventsDispatcher.dispatchEvent { routeToMainScreen() }\n            } catch (error: Throwable) {\n                val message = error.message ?: error.toString()\n                val errorDesc = message.desc()\n\n                eventsDispatcher.dispatchEvent { showError(errorDesc) }\n            } finally {\n                _isLoading.value = false\n            }\n        }\n    }\n\n    interface EventsListener {\n        fun routeToMainScreen()\n        fun showError(error: StringDesc)\n    }\n}\n```\n`viewModelScope` is a `CoroutineScope` field of the `ViewModel` class with a default Dispatcher - `UI` on both platforms. \n All coroutines will be canceled in `onCleared` automatically.\n#### Android\n`LoginActivity.kt`:\n```kotlin\nclass LoginActivity :\n    MvvmEventsActivity\u003cActivityLoginBinding, LoginViewModel, LoginViewModel.EventsListener\u003e(),\n    LoginViewModel.EventsListener {\n\n    override val layoutId: Int = R.layout.activity_login\n    override val viewModelVariableId: Int = BR.viewModel\n    override val viewModelClass: Class\u003cLoginViewModel\u003e =\n        LoginViewModel::class.java\n\n    override fun viewModelFactory(): ViewModelProvider.Factory {\n        return createViewModelFactory {\n            LoginViewModel(\n                userRepository = MockUserRepository(),\n                eventsDispatcher = eventsDispatcherOnMain()\n            )\n        }\n    }\n\n    override fun routeToMainScreen() {\n        Toast.makeText(this, \"route to main page here\", Toast.LENGTH_SHORT).show()\n    }\n\n    override fun showError(error: StringDesc) {\n        Toast.makeText(this, error.toString(context = this), Toast.LENGTH_SHORT).show()\n    }\n}\n```\n`activity_login.xml`:\n```xml\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cvariable\n            name=\"viewModel\"\n            type=\"com.icerockdev.library.sample6.LoginViewModel\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:padding=\"16dp\"\u003e\n\n        \u003cEditText\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"email\"\n            android:text=\"@={viewModel.email.ld}\" /\u003e\n\n        \u003cEditText\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:hint=\"password\"\n            android:text=\"@={viewModel.password.ld}\" /\u003e\n\n        \u003cFrameLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\u003e\n\n            \u003cButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"8dp\"\n                android:onClick=\"@{() -\u003e viewModel.onLoginButtonPressed()}\"\n                android:text=\"Login\"\n                app:visibleOrGone=\"@{viewModel.isLoginButtonVisible.ld}\" /\u003e\n\n            \u003cProgressBar\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                app:visibleOrGone=\"@{viewModel.isLoading.ld}\" /\u003e\n        \u003c/FrameLayout\u003e\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n#### iOS\n`LoginViewController.swift`:\n```swift\nclass LoginViewController: UIViewController {\n    @IBOutlet private var emailField: UITextField!\n    @IBOutlet private var passwordField: UITextField!\n    @IBOutlet private var loginButton: UIButton!\n    @IBOutlet private var progressBar: UIActivityIndicatorView!\n    \n    private var viewModel: LoginViewModel!\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        let eventsDispatcher = EventsDispatcher\u003cLoginViewModelEventsListener\u003e(listener: self)\n        viewModel = LoginViewModel(eventsDispatcher: eventsDispatcher,\n                                   userRepository: MockUserRepository())\n        \n        emailField.bindTextTwoWay(liveData: viewModel.email)\n        passwordField.bindTextTwoWay(liveData: viewModel.password)\n        loginButton.bindVisibility(liveData: viewModel.isLoginButtonVisible)\n        progressBar.bindVisibility(liveData: viewModel.isLoading)\n    }\n    \n    @IBAction func onLoginButtonPressed() {\n        viewModel.onLoginButtonPressed()\n    }\n    \n    override func didMove(toParentViewController parent: UIViewController?) {\n        if(parent == nil) { viewModel.onCleared() }\n    }\n}\n\nextension LoginViewController: LoginViewModelEventsListener {\n    func routeToMainScreen() {\n        showAlert(text: \"route to main screen\")\n    }\n    \n    func showError(error: StringDesc) {\n        showAlert(text: error.localized())\n    }\n}\n```\n\n## Samples\nPlease see more examples in the [sample directory](sample).\n\n## Set Up Locally \n- The [mvvm directory](mvvm) contains the umbrella library;\n- The [mvvm-core directory](mvvm-core) contains the core - ViewModel, EventsDispatcher;\n- The [mvvm-livedata directory](mvvm-livedata) contains the livedata classes and extensions;\n- The [mvvm-databinding directory](mvvm-databinding) contains DataBinding support code for Android;\n- The [mvvm-viewbinding directory](mvvm-viewbinding) contains ViewBinding support code for Android;\n- The [mvvm-test directory](mvvm-test) contains the test utilities;\n- In [sample directory](sample) contains sample apps for Android and iOS; plus the mpp-library connected to the apps;\n- In [sample-declarative-ui directory](sample-declarative-ui) contains sample apps with Jetpack Compose and SwiftUI.\n\n## Contributing\nAll development (both new features and bug fixes) is performed in the `develop` branch. This way `master` always contains the sources of the most recently released version. Please send PRs with bug fixes to the `develop` branch. Documentation fixes in the markdown files are an exception to this rule. They are updated directly in `master`.\n\nThe `develop` branch is pushed to `master` on release.\n\nFor more details on contributing please see the [contributing guide](CONTRIBUTING.md).\n\n## We’re hiring a Mobile Developers for our main team in Novosibirsk and remote team with Moscow timezone!\n\nIf you like to develop mobile applications, are an expert in iOS/Swift or Android/Kotlin and eager to use Kotlin Multiplatform in production, we'd like to talk to you.\n\n[To learn more and apply](https://career.habr.com/companies/icerockdev)\n\n## License\n        \n    Copyright 2019 IceRock MAG Inc.\n    \n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n    \n       http://www.apache.org/licenses/LICENSE-2.0\n    \n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n\n[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat\n[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat\n[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat\n[badge-jvm]: http://img.shields.io/badge/platform-jvm-DB413D.svg?style=flat\n[badge-linux]: http://img.shields.io/badge/platform-linux-2D3F6C.svg?style=flat\n[badge-windows]: http://img.shields.io/badge/platform-windows-4D76CD.svg?style=flat\n[badge-mac]: http://img.shields.io/badge/platform-macos-111111.svg?style=flat\n[badge-watchos]: http://img.shields.io/badge/platform-watchos-C0C0C0.svg?style=flat\n[badge-tvos]: http://img.shields.io/badge/platform-tvos-808080.svg?style=flat\n[badge-wasm]: https://img.shields.io/badge/platform-wasm-624FE8.svg?style=flat\n[badge-nodejs]: https://img.shields.io/badge/platform-nodejs-68a063.svg?style=flat\n","funding_links":[],"categories":["Libraries","Architecture"],"sub_categories":["Architecture","🏗 Architecture","SQL"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficerockdev%2Fmoko-mvvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficerockdev%2Fmoko-mvvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficerockdev%2Fmoko-mvvm/lists"}