Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development

Last update: May 17, 2022

moko-mvvm
GitHub license Download kotlin-version

Mobile Kotlin Model-View-ViewModel architecture components

This is a Kotlin Multiplatform library that provides architecture components of Model-View-ViewModel for UI applications. Components are lifecycle-aware on Android.

Table of Contents

Features

  • ViewModel - store and manage UI-related data. Interop with Android Architecture Components - on Android it's precisely androidx.lifecycle.ViewModel;
  • LiveData, MutableLiveData, MediatorLiveData - lifecycle-aware reactive data holders with set of operators to transform, merge, etc.;
  • EventsDispatcher - dispatch events from ViewModel to View with automatic lifecycle control and explicit interface of required events;
  • DataBinding, ViewBinding support - integrate to android app with commonly used tools.

Requirements

  • Gradle version 6.8+
  • Android API 16+
  • iOS version 11.0+

Installation

root build.gradle

allprojects {
    repositories {
        mavenCentral()
    }
}

project build.gradle

dependencies {
    commonMainApi("dev.icerock.moko:mvvm-core:0.11.0") // only ViewModel, EventsDispatcher, Dispatchers.UI
    commonMainApi("dev.icerock.moko:mvvm-livedata:0.11.0") // api mvvm-core, LiveData and extensions
    commonMainApi("dev.icerock.moko:mvvm-state:0.11.0") // api mvvm-livedata, ResourceState class and extensions
    
    androidMainApi("dev.icerock.moko:mvvm-livedata-material:0.11.0") // api mvvm-livedata, Material library android extensions
    androidMainApi("dev.icerock.moko:mvvm-livedata-glide:0.11.0") // api mvvm-livedata, Glide library android extensions
    androidMainApi("dev.icerock.moko:mvvm-livedata-swiperefresh:0.11.0") // api mvvm-livedata, SwipeRefreshLayout library android extensions
    androidMainApi("dev.icerock.moko:mvvm-databinding:0.11.0") // api mvvm-livedata, DataBinding support for Android
    androidMainApi("dev.icerock.moko:mvvm-viewbinding:0.11.0") // api mvvm-livedata, ViewBinding support for Android
    
    commonTestImplementation("dev.icerock.moko:mvvm-test:0.11.0") // test utilities
}

Also required export of dependency to iOS framework. For example:

kotlin {
    // export correct artifact to use all classes of library directly from Swift
    targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java).all {
        binaries.withType(org.jetbrains.kotlin.gradle.plugin.mpp.Framework::class.java).all {
            export("dev.icerock.moko:mvvm-core:0.11.0")
            export("dev.icerock.moko:mvvm-livedata:0.11.0")
            export("dev.icerock.moko:mvvm-state:0.11.0")
        }
    }
}

On iOS, in addition to the Kotlin library add in Podfile

pod 'MultiPlatformLibraryMvvm', :git => 'https://github.com/icerockdev/moko-mvvm.git', :tag => 'release/0.11.0'

MultiPlatformLibraryMvvm CocoaPod requires that the framework compiled from Kotlin be named MultiPlatformLibrary and be connected as a CocoaPod MultiPlatformLibrary. Here's an example. To simplify integration with MultiPlatformFramework you can use mobile-multiplatform-plugin

MultiPlatformLibraryMvvm CocoaPod contains the extension to UIViews for binding with LiveData.

Documentation

Documentation generated by Dokka and available at https://icerockdev.github.io/moko-mvvm/

Usage

Simple view model

Let’s say we need a screen with a button click counter. To implement it we should:

common

In commonMain we can create a ViewModel like:

class SimpleViewModel() : ViewModel() {
    private val _counter: MutableLiveData<Int> = MutableLiveData(0)
    val counter: LiveData<String> = _counter.map { it.toString() }

    fun onCounterButtonPressed() {
        val current = _counter.value
        _counter.value = current + 1
    }
}

And after that integrate the ViewModel on platform the sides.

Android

SimpleActivity.kt:

class SimpleActivity : MvvmActivity<ActivitySimpleBinding, SimpleViewModel>() {
    override val layoutId: Int = R.layout.activity_simple
    override val viewModelVariableId: Int = BR.viewModel
    override val viewModelClass: Class<SimpleViewModel> = SimpleViewModel::class.java

    override fun viewModelFactory(): ViewModelProvider.Factory {
        return createViewModelFactory { SimpleViewModel() }
    }
}

MvvmActivity automatically loads a databinding layout, resolves ViewModel object and sets a databinding variable.
activity_simple.xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.icerockdev.library.sample1.SimpleViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.counter.ld}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:onClick="@{() -> viewModel.onCounterButtonPressed()}"
            android:text="Press me to count" />
    </LinearLayout>
</layout>

iOS

SimpleViewController.swift:

import MultiPlatformLibrary
import MultiPlatformLibraryMvvm

class SimpleViewController: UIViewController {
    @IBOutlet private var counterLabel: UILabel!
    
    private var viewModel: SimpleViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewModel = SimpleViewModel()
        
        counterLabel.bindText(liveData: viewModel.counter)
    }
    
    @IBAction func onCounterButtonPressed() {
        viewModel.onCounterButtonPressed()
    }
    
    override func didMove(toParentViewController parent: UIViewController?) {
        if(parent == nil) { viewModel.onCleared() }
    }
}

bindText is an extension from the MultiPlatformLibraryMvvm CocoaPod.

ViewModel with send events to View

Let’s say we need a screen from which we should go to another screen by pressing a button. To implement it we should:

common

class EventsViewModel(
    val eventsDispatcher: EventsDispatcher<EventsListener>
) : ViewModel() {

    fun onButtonPressed() {
        eventsDispatcher.dispatchEvent { routeToMainPage() }
    }

    interface EventsListener {
        fun routeToMainPage()
    }
}

EventsDispatcher is a special class that automatically removes observers from lifecycle and buffers input events while listener is not attached (on the Android side).

Android

EventsActivity.kt:

class EventsActivity : MvvmActivity<ActivityEventsBinding, EventsViewModel>(),
    EventsViewModel.EventsListener {
    override val layoutId: Int = R.layout.activity_events
    override val viewModelVariableId: Int = BR.viewModel
    override val viewModelClass: Class<EventsViewModel> = EventsViewModel::class.java

    override fun viewModelFactory(): ViewModelProvider.Factory {
        return createViewModelFactory { EventsViewModel(eventsDispatcherOnMain()) }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.eventsDispatcher.bind(
            lifecycleOwner = this,
            listener = this
        )
    }

    override fun routeToMainPage() {
        Toast.makeText(this, "here must be routing to main page", Toast.LENGTH_SHORT).show()
    }
}

eventsDispatcher.bind attaches EventsDispatcher to the lifecycle (in this case - to an activity) to correctly subscribe and unsubscribe, without memory leaks.

We can also simplify the binding of EventsDispatcher with MvvmEventsActivity and EventsDispatcherOwnder. EventsOwnerViewModel.kt:

class EventsOwnerViewModel(
    override val eventsDispatcher: EventsDispatcher<EventsListener>
) : ViewModel(), EventsDispatcherOwner<EventsOwnerViewModel.EventsListener> {

    fun onButtonPressed() {
        eventsDispatcher.dispatchEvent { routeToMainPage() }
    }

    interface EventsListener {
        fun routeToMainPage()
    }
}

EventsOwnderActivity.kt:

class EventsOwnerActivity :
    MvvmEventsActivity<ActivityEventsOwnerBinding, EventsOwnerViewModel, EventsOwnerViewModel.EventsListener>(),
    EventsOwnerViewModel.EventsListener {

    override val layoutId: Int = R.layout.activity_events_owner
    override val viewModelVariableId: Int = BR.viewModel
    override val viewModelClass: Class<EventsOwnerViewModel> = EventsOwnerViewModel::class.java

    override fun viewModelFactory(): ViewModelProvider.Factory {
        return createViewModelFactory { EventsOwnerViewModel(eventsDispatcherOnMain()) }
    }

    override fun routeToMainPage() {
        Toast.makeText(this, "here must be routing to main page", Toast.LENGTH_SHORT).show()
    }
}

iOS

EventsViewController.swift:

import MultiPlatformLibrary
import MultiPlatformLibraryMvvm

class EventsViewController: UIViewController {
    private var viewModel: EventsViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let eventsDispatcher = EventsDispatcher<EventsViewModelEventsListener>(listener: self)
        viewModel = EventsViewModel(eventsDispatcher: eventsDispatcher)
    }
    
    @IBAction func onButtonPressed() {
        viewModel.onButtonPressed()
    }
    
    override func didMove(toParentViewController parent: UIViewController?) {
        if(parent == nil) { viewModel.onCleared() }
    }
}

extension EventsViewController: EventsViewModelEventsListener {
    func routeToMainPage() {
        showAlert(text: "go to main page")
    }
}

On iOS we create an instance of EventsDispatcher with the link to the listener. We shouldn't call bind like on Android (in iOS this method doesn't exist).

ViewModel with validation of user input

class ValidationMergeViewModel() : ViewModel() {
    val email: MutableLiveData<String> = MutableLiveData("")
    val password: MutableLiveData<String> = MutableLiveData("")

    val isLoginButtonEnabled: LiveData<Boolean> = email.mergeWith(password) { email, password ->
        email.isNotEmpty() && password.isNotEmpty()
    }
}

isLoginButtonEnabled is observable email & password LiveData, and in case there are any changes it calls lambda with the newly calculated value.

We can also use one of these combinations:

class ValidationAllViewModel() : ViewModel() {
    val email: MutableLiveData<String> = MutableLiveData("")
    val password: MutableLiveData<String> = MutableLiveData("")

    private val isEmailValid: LiveData<Boolean> = email.map { it.isNotEmpty() }
    private val isPasswordValid: LiveData<Boolean> = password.map { it.isNotEmpty() }
    val isLoginButtonEnabled: LiveData<Boolean> = listOf(isEmailValid, isPasswordValid).all(true)
}

Here we have separated LiveData with the validation flags - isEmailValid, isPasswordValid and combine both to isLoginButtonEnabled by merging all boolean LiveData in the list with on the condition that "all values must be true".

ViewModel for login feature

common

class LoginViewModel(
    override val eventsDispatcher: EventsDispatcher<EventsListener>,
    private val userRepository: UserRepository
) : ViewModel(), EventsDispatcherOwner<LoginViewModel.EventsListener> {
    val email: MutableLiveData<String> = MutableLiveData("")
    val password: MutableLiveData<String> = MutableLiveData("")

    private val _isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
    val isLoading: LiveData<Boolean> = _isLoading.readOnly()

    val isLoginButtonVisible: LiveData<Boolean> = isLoading.not()

    fun onLoginButtonPressed() {
        val emailValue = email.value
        val passwordValue = password.value

        viewModelScope.launch {
            _isLoading.value = true

            try {
                userRepository.login(email = emailValue, password = passwordValue)

                eventsDispatcher.dispatchEvent { routeToMainScreen() }
            } catch (error: Throwable) {
                val message = error.message ?: error.toString()
                val errorDesc = message.desc()

                eventsDispatcher.dispatchEvent { showError(errorDesc) }
            } finally {
                _isLoading.value = false
            }
        }
    }

    interface EventsListener {
        fun routeToMainScreen()
        fun showError(error: StringDesc)
    }
}

viewModelScope is a CoroutineScope field of the ViewModel class with a default Dispatcher - UI on both platforms. All coroutines will be canceled in onCleared automatically.

Android

LoginActivity.kt:

class LoginActivity :
    MvvmEventsActivity<ActivityLoginBinding, LoginViewModel, LoginViewModel.EventsListener>(),
    LoginViewModel.EventsListener {

    override val layoutId: Int = R.layout.activity_login
    override val viewModelVariableId: Int = BR.viewModel
    override val viewModelClass: Class<LoginViewModel> =
        LoginViewModel::class.java

    override fun viewModelFactory(): ViewModelProvider.Factory {
        return createViewModelFactory {
            LoginViewModel(
                userRepository = MockUserRepository(),
                eventsDispatcher = eventsDispatcherOnMain()
            )
        }
    }

    override fun routeToMainScreen() {
        Toast.makeText(this, "route to main page here", Toast.LENGTH_SHORT).show()
    }

    override fun showError(error: StringDesc) {
        Toast.makeText(this, error.toString(context = this), Toast.LENGTH_SHORT).show()
    }
}

activity_login.xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewModel"
            type="com.icerockdev.library.sample6.LoginViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="email"
            android:text="@={viewModel.email.ld}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:hint="password"
            android:text="@={viewModel.password.ld}" />

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:onClick="@{() -> viewModel.onLoginButtonPressed()}"
                android:text="Login"
                app:visibleOrGone="@{viewModel.isLoginButtonVisible.ld}" />

            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                app:visibleOrGone="@{viewModel.isLoading.ld}" />
        </FrameLayout>
    </LinearLayout>
</layout>

iOS

LoginViewController.swift:

class LoginViewController: UIViewController {
    @IBOutlet private var emailField: UITextField!
    @IBOutlet private var passwordField: UITextField!
    @IBOutlet private var loginButton: UIButton!
    @IBOutlet private var progressBar: UIActivityIndicatorView!
    
    private var viewModel: LoginViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let eventsDispatcher = EventsDispatcher<LoginViewModelEventsListener>(listener: self)
        viewModel = LoginViewModel(eventsDispatcher: eventsDispatcher,
                                   userRepository: MockUserRepository())
        
        emailField.bindTextTwoWay(liveData: viewModel.email)
        passwordField.bindTextTwoWay(liveData: viewModel.password)
        loginButton.bindVisibility(liveData: viewModel.isLoginButtonVisible)
        progressBar.bindVisibility(liveData: viewModel.isLoading)
    }
    
    @IBAction func onLoginButtonPressed() {
        viewModel.onLoginButtonPressed()
    }
    
    override func didMove(toParentViewController parent: UIViewController?) {
        if(parent == nil) { viewModel.onCleared() }
    }
}

extension LoginViewController: LoginViewModelEventsListener {
    func routeToMainScreen() {
        showAlert(text: "route to main screen")
    }
    
    func showError(error: StringDesc) {
        showAlert(text: error.localized())
    }
}

Samples

Please see more examples in the sample directory.

Set Up Locally

Contributing

All 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.

The develop branch is pushed to master on release.

For more details on contributing please see the contributing guide.

We’re hiring a Mobile Developers for our main team in Novosibirsk and remote team with Moscow timezone!

If 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.

To learn more and apply

License

Copyright 2019 IceRock MAG Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

GitHub

https://github.com/icerockdev/moko-mvvm
Comments
  • 1. Coroutines flow as reactive value holders

    Why you don't use coroutines flow to implement reactive value holders instead of LiveData?

    I mean something like this: State: https://gist.github.com/vchernyshov/d661e153e768915c5a2b3a23a7338b1c ViewModel: https://gist.github.com/vchernyshov/cfd39b16b6e022188b97887f3f4c3351

    Reviewed by vchernyshov at 2020-03-15 20:54
  • 2. Not compatible with CIO (Coroutine-based I/O) Ktor implementation

        sourceSets {
            val commonMain by getting {
                dependencies {
                    //  http client
                    implementation("io.ktor:ktor-client-core:1.5.0")
                    implementation("io.ktor:ktor-client-cio:1.5.0")
    
                    //  MVVM
                    api("dev.icerock.moko:mvvm-core:0.9.0")
    ....
    }
    
    Testing started at 7:06 PM ...
    Executing tasks: [cleanIosX64Test, iosX64Test] in project /Users/dda/Projects/tmp/shared
    
    
    > Configure project :shared
    Kotlin Multiplatform Projects are an Alpha feature. See: https://kotlinlang.org/docs/reference/evolution/components-stability.html. To hide this message, add 'kotlin.mpp.stability.nowarn=true' to the Gradle properties.
    > Task :shared:cleanIosX64Test
    > Task :shared:compileKotlinIosX64 FAILED
    e: Compilation failed: Deserializer for declaration public kotlinx.coroutines/SingleThreadDispatcher|null[0] is not found
     * Source files: Greeting.kt, Platform.kt, Platform.kt
     * Compiler version info: Konan: 1.4.21 / Kotlin: 1.4.21
     * Output kind: LIBRARY
    e: java.lang.IllegalStateException: Deserializer for declaration public kotlinx.coroutines/SingleThreadDispatcher|null[0] is not found
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.handleNoModuleDeserializerFound(KotlinIrLinker.kt:451)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.findModuleDeserializer(KotlinIrLinker.kt:345)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.deserializeIrSymbolData(KotlinIrLinker.kt:368)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.deserializeIrSymbol(KotlinIrLinker.kt:384)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeIrSymbolAndRemap(IrFileDeserializer.kt:140)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeSimpleType(IrFileDeserializer.kt:165)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeIrTypeData(IrFileDeserializer.kt:208)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.deserializeIrType(KotlinIrLinker.kt:390)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeIrFunction(IrFileDeserializer.kt:1616)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeIrProperty(IrFileDeserializer.kt:1347)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeDeclaration(IrFileDeserializer.kt:1408)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeIrClass(IrFileDeserializer.kt:1050)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeDeclaration(IrFileDeserializer.kt:1406)
    	at org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer.deserializeDeclaration(IrFileDeserializer.kt:1448)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.deserializeDeclaration(KotlinIrLinker.kt:274)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile$FileDeserializationState.processPendingDeclarations(KotlinIrLinker.kt:262)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$IrDeserializerForFile.deserializeAllFileReachableTopLevel(KotlinIrLinker.kt:439)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$BasicIrModuleDeserializer$ModuleDeserializationState.processPendingDeclarations(KotlinIrLinker.kt:105)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker$BasicIrModuleDeserializer.deserializeReachableDeclarations(KotlinIrLinker.kt:214)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.deserializeAllReachableTopLevels(KotlinIrLinker.kt:509)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.findDeserializedDeclarationForSymbol(KotlinIrLinker.kt:528)
    	at org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker.getDeclaration(KotlinIrLinker.kt:566)
    	at org.jetbrains.kotlin.ir.util.ExternalDependenciesGeneratorKt.getDeclaration(ExternalDependenciesGenerator.kt:60)
    	at org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator.generateUnboundSymbolsAsDependencies(ExternalDependenciesGenerator.kt:50)
    	at org.jetbrains.kotlin.psi2ir.generators.ModuleGenerator.generateUnboundSymbolsAsDependencies(ModuleGenerator.kt:62)
    	at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:89)
    	at org.jetbrains.kotlin.backend.konan.PsiToIrKt.psiToIr(PsiToIr.kt:126)
    	at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt:134)
    	at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt)
    	at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:97)
    	at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:95)
    	at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
    	at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:30)
    	at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
    	at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:41)
    	at org.jetbrains.kotlin.backend.konan.KonanDriverKt.runTopLevelPhases(KonanDriver.kt:29)
    	at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:78)
    	at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:35)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
    	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
    	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:76)
    	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:45)
    	at org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:227)
    	at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:277)
    	at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:261)
    	at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:27)
    	at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:21)
    	at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithGradleRenderer(K2Native.kt:276)
    	at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithGradleRenderer(K2Native.kt:458)
    	at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt:43)
    	at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt)
    	at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:17)
    	at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain(main.kt:43)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.runInProcess(KotlinToolRunner.kt:100)
    	at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.run(KotlinToolRunner.kt:73)
    	at org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile.compile(KotlinNativeTasks.kt:315)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
    	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:49)
    	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:42)
    	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:28)
    	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:726)
    	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:693)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$3.run(ExecuteActionsTaskExecuter.java:569)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:395)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:387)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:157)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:242)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:150)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:84)
    	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:554)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:537)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$300(ExecuteActionsTaskExecuter.java:108)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:278)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:267)
    	at org.gradle.internal.execution.steps.ExecuteStep.lambda$execute$1(ExecuteStep.java:33)
    	at java.util.Optional.orElseGet(Optional.java:267)
    	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:33)
    	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:26)
    	at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:67)
    	at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:36)
    	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:49)
    	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:34)
    	at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:43)
    	at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:73)
    	at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:54)
    	at org.gradle.internal.execution.steps.CatchExceptionStep.execute(CatchExceptionStep.java:34)
    	at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:44)
    	at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:54)
    	at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:38)
    	at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:49)
    	at org.gradle.internal.execution.steps.CacheStep.executeWithoutCache(CacheStep.java:159)
    	at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:72)
    	at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:43)
    	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:44)
    	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:33)
    	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:38)
    	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:24)
    	at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:92)
    	at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:85)
    	at java.util.Optional.map(Optional.java:215)
    	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
    	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
    	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:76)
    	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:37)
    	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:36)
    	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:26)
    	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:94)
    	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:49)
    	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:79)
    	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:53)
    	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
    	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:78)
    	at java.util.Optional.orElseGet(Optional.java:267)
    	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:78)
    	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
    	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:39)
    	at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:40)
    	at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:28)
    	at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:33)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:194)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:186)
    	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:114)
    	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
    	at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
    	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
    	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
    	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
    	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:409)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:399)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:157)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:242)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:150)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:94)
    	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
    	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
    	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:356)
    	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
    	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
    	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
    	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
    	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
    	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
    	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
    	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    	at java.lang.Thread.run(Thread.java:748)
    FAILURE: Build failed with an exception.
    * What went wrong:
    Execution failed for task ':shared:compileKotlinIosX64'.
    > Compilation finished with errors
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
    * Get more help at https://help.gradle.org
    BUILD FAILED in 2s
    2 actionable tasks: 2 executed
    7:06:14 PM: Tasks execution finished 'cleanIosX64Test iosX64Test --tests "org.dda.myapplication.shared.IosGreetingTest"'.
    
    

    but without implementation("io.ktor:ktor-client-cio:1.5.0") compiled successfully

    Reviewed by september669 at 2021-01-22 09:08
  • 3. Separate library to multiple artifacts

    It would be nice to see the new moko-viewmodel or moko-mvvm-viewmodel library. Without databinding support for declarative UI - Jetpack Compose.

    And the moko-mvvm library can be updated by composing from moko-viewmodel, moko-livedata (for the future, as an example)

    Reviewed by ausichenko at 2020-09-30 12:14
  • 4. iOS pod installation problem

    Hi!

    On iOS, in addition to the Kotlin library add in Podfile

    pod 'MultiPlatformLibraryMvvm', :git => 'https://github.com/icerockdev/moko-mvvm.git', :tag => 'release/0.6.0'

    Trying to install via cocoa pods and getting an error:

    Pre-downloading: MultiPlatformLibraryMvvm from https://github.com/icerockdev/moko-mvvm.git, tag release/0.6.0 [!] Unable to find a specification for MultiPlatformLibrary depended upon by MultiPlatformLibraryMvvm

    You have either:

    • out-of-date source repos which you can update with pod repo update or with pod install --repo-update.
    • mistyped the name or version.
    • not added the source repo that hosts the Podspec to your Podfile.

    "Pod repo update" doesn't fix the problem, although without it binding LiveData works fine.

    Is there something I'm missing? Thank you

    Reviewed by Chopyhoo at 2020-06-08 18:42
  • 5. Could not resolve dev.icerock.moko:mvvm:0.6.0

    I am combining it into my project after follow basic guidelines for building multi-project using: https://proandroiddev.com/kotlin-multiplatform-very-beginners-guide-part-1-6419f74afa0f https://proandroiddev.com/kotlin-multiplatform-very-beginners-guide-part-2-api-d54f7326dc57 I run into strange error of "Unable to find a matching variant of dev.icerock.moko:mvvm:0.6.0" (see below gradle output) thaw I am doing the exact steps in https://github.com/icerockdev/moko-mvvm README

    I've added my project gradle files (app and shared) to share location on my Google drive: https://drive.google.com/file/d/1Iq8IxXFnnnZE-Kpgb9xgbIMcmMvfLDqG/view?usp=sharing

    Thank you in advance for the great work.

    Best regards, Gabriel

    • What went wrong: Execution failed for task ':Shared:compileKotlinAndroid'.

    Could not resolve all files for configuration ':Shared:androidCompileClasspath'. Could not resolve dev.icerock.moko:mvvm:0.6.0. Required by: project :Shared > Unable to find a matching variant of dev.icerock.moko:mvvm:0.6.0: - Variant 'android-debugApiElements' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attribute: - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'androidJvm'. - Other attributes: - Found com.android.build.api.attributes.BuildTypeAttr 'debug' but wasn't required. - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required. - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required. - Found org.gradle.status 'release' but wasn't required. - Required org.gradle.usage 'java-api' and found compatible value 'java-api'. - Variant 'android-debugRuntimeElements' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attribute: - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'androidJvm'. - Other attributes: - Found com.android.build.api.attributes.BuildTypeAttr 'debug' but wasn't required. - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required. - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required. - Found org.gradle.status 'release' but wasn't required. - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime'. - Variant 'android-releaseApiElements' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attribute: - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'androidJvm'. - Other attributes: - Found com.android.build.api.attributes.BuildTypeAttr 'release' but wasn't required. - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required. - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required. - Found org.gradle.status 'release' but wasn't required. - Required org.gradle.usage 'java-api' and found compatible value 'java-api'. - Variant 'android-releaseRuntimeElements' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attribute: - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'androidJvm'. - Other attributes: - Found com.android.build.api.attributes.BuildTypeAttr 'release' but wasn't required. - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required. - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required. - Found org.gradle.status 'release' but wasn't required. - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime'. - Variant 'iosArm64-api' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attributes: - Required org.gradle.usage 'java-api' and found incompatible value 'kotlin-api'. - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'native'. - Other attributes: - Found org.gradle.status 'release' but wasn't required. - Found org.jetbrains.kotlin.native.target 'ios_arm64' but wasn't required. - Variant 'iosX64-api' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attributes: - Required org.gradle.usage 'java-api' and found incompatible value 'kotlin-api'. - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'native'. - Other attributes: - Found org.gradle.status 'release' but wasn't required. - Found org.jetbrains.kotlin.native.target 'ios_x64' but wasn't required. - Variant 'metadata-api' capability dev.icerock.moko:mvvm:0.6.0: - Incompatible attributes: - Required org.gradle.usage 'java-api' and found incompatible value 'kotlin-api'. - Required org.jetbrains.kotlin.platform.type 'jvm' and found incompatible value 'common'. - Other attribute: - Found org.gradle.status 'release' but wasn't required.

    Reviewed by GabrielNoam at 2020-03-24 00:13
  • 6. Problem to install dependencies

    I am trying to install some moko dependencies and getting this error.

    org.gradle.internal.resolve.ArtifactNotFoundException: Could not find mvvm-livedata-0.9.1-samplessources.jar (dev.icerock.moko:mvvm-livedata:0.9.1). Searched in the following locations: https://dl.bintray.com/icerockdev/moko/dev/icerock/moko/mvvm-livedata/0.9.1/mvvm-livedata-0.9.1-samplessources.jar

    it looks like the "-samplessources" part of the link shouldn't be there, but a don't know how to remove it.

    image

    image

    Reviewed by joavilati at 2021-01-25 19:41
  • 7. Error Cannot access ViewModel

    I get some error: Cannot access 'dev.icerock.moko.mvvm.viewmodel.ViewModel' which is a supertype of 'net.compoza.deactivator.mpp.model.ListViewModel'. Check your module classpath for missing or conflicting dependencies.

    ViewModel class: Снимок экрана 2020-09-29 в 12 32 27

    Fragment code: Снимок экрана 2020-09-29 в 12 32 07

    shared build.gradle: Снимок экрана 2020-09-29 в 12 36 11 Снимок экрана 2020-09-29 в 12 36 20

    I use: Android Studio: 4.0.1 Kotlin: 1.4.10 gradle: 6.6 build.gradle: 4.0.1 And block Android() in shared build.gradle.

    Reviewed by Diy2210 at 2020-09-29 09:37
  • 8. question about unit testing MutableLiveData

    Hello I am start writing multi platform code using moko-mvvm. Now I want to write unit tests for my common logic which is using MutableLiveData. At first run I encounter the error: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked which seems to be related to android. Searching a bit i found this as a solution. Problem is it requires InstantTaskExecutorRule which is part of android platform and thus is not accessible in common code. I know I can move my test to android source set and it will goes well but I think its more clean to hold common tests in common source sets. So does anyone have a suggestion how to fix this?

    Reviewed by AliGolmirzaei at 2020-05-03 02:40
  • 9. Append iOS-specific ability for memory collecting

    Same as follow:

    actual open class ViewModel actual constructor() {
        ...
        fun collectMemory() {
        	kotlin.native.internal.GC.collect()
        }
    }
    
    Reviewed by DevTchernov at 2020-02-17 08:31
  • 10. #166 ios simulator arm64 support

    • [x] fix detekt on CI
    • [x] fix tests with postValue
    • [x] rename bindings to support convention View.bindAttribute(livedata)
    • [x] remove api files (can't use binary validator anymore)
    • [x] fix sonarqube for tests
    • [x] moko-kswift integration annotations and checks
    Reviewed by Alex009 at 2022-02-11 16:22
  • 11. No matching variant of dev.icerock.moko:mvvm:0.8.0 was found

    Hi, I add moko-mvvm to multiplatform project, but when start build I get error: no matching variant of dev.icerock.moko:mvvm:0.8.0 was found

    I use kotlin: 1.4.10 build gradle: 4.0.1 gradle: 6.6 Android Studio: 4.0.1

    build.gradle: add maven url to allProjects 1

    Add implementation from common gradle build.gradle.kts: 2 3

    ViewModel without any errors: 4

    But I get error: 5

    Reviewed by Diy2210 at 2020-09-22 14:07
  • 12. Add mokoMvvmFlowSwiftUI without Cocoapods

    We recently switched our project from using Cocoapods to Swift Package Manager. Is there any possibility to use mokoMvvmFlowSwiftUI without using Cocoapods, so we can use MOKO MVVM with SwiftUI?

    Reviewed by ThomasGoehringer at 2022-05-04 11:36
  • 13. Installation with default KMM template Cocoapods

    Hello! @Alex009 I have created KMM project from default template with cocoapods. I'm IOS developer and Gradle is hard for understanding. There is tutorial in MOKO MVVM REAMDE, but Gradle files are different. All my attempts end with working android app and crashing IOS app. I have no idea why it is crashing.

    This is empty project created from the template: https://github.com/IlyasNN/MultiplatformMokoExample

    Can you show how dependencies should be written to work great. Thanks!

    Reviewed by IlyasNN at 2021-11-10 09:54
  • 14. need flag for show or hide setEror in bindToTextInputLayoutError

    https://github.com/icerockdev/moko-mvvm/blob/master/mvvm-livedata-material/src/main/kotlin/dev/icerock/moko/mvvm/livedata/material/TextInputLayoutBindings.kt

    Reviewed by anton6tak at 2021-10-25 12:10
  • 15. add koin integration module

    https://kotlinlang.slack.com/archives/CMC5LN42W/p1634901111003300?thread_ts=1634899812.002200&cid=CMC5LN42W

    So for Koin, we have our own method too In common:

    expect inline fun <reified T : BaseViewModel<*, *>> Module.viewModelDefinition(
        qualifier: Qualifier? = null,
        noinline definition: Definition<T>
    ): Pair<Module, InstanceFactory<T>>
    

    While then for Android the implementation will call the Koin viewModel:

    actual inline fun <reified T : BaseViewModel<*, *>> Module.viewModelDefinition(
        qualifier: Qualifier?,
        noinline definition: Definition<T>,
    ): Pair<Module, InstanceFactory<T>> = viewModel(qualifier = qualifier, definition = definition)
    

    And for iOS will use a factory:

    actual inline fun <reified T : BaseViewModel<*, *>> Module.viewModelDefinition(
        qualifier: Qualifier?,
        noinline definition: Definition<T>,
    ): Pair<Module, InstanceFactory<T>> = factory(qualifier = qualifier, definition = definition)
    
    Reviewed by Alex009 at 2021-10-25 03:09
  • 16. add extensions to LiveData

    @JvmName("bindToTextViewTextStringDesc") fun <T : StringDesc?> LiveData.bindToTextViewText( lifecycleOwner: LifecycleOwner, textView: TextView ): Closeable { val context = textView.context return bindNotNull(lifecycleOwner) { textView.text = it?.toString(context) } }

    fun LiveData.bindToPincodeViewValue(lifecycleOwner: LifecycleOwner, pinCodeView: PinCodeView):Closeable { return this.bindNotNull(lifecycleOwner) { pinCodeView.value = it } }

    fun LiveData<T?>.isNotNull(): LiveData = this.map { it != null }

    Reviewed by anton6tak at 2021-10-14 05:21
Android MVVM Base Architecture for Enterprise Mobile Application using Architectural Components
Android MVVM Base Architecture for Enterprise Mobile Application using Architectural Components

Android MVVM Base Architecture for Enterprise Mobile Application using Architectural Components Highlights MVVM Architectural pattern Offline Support

Apr 23, 2022
📒Note taking app, MVVM with Google Architectural components Room, LiveData and ViewModel written in Kotlin, androidx libraries
📒Note taking app, MVVM with Google Architectural components Room, LiveData and ViewModel written in Kotlin, androidx libraries

?? MyNotes Note taking Android App using androidx libraries, MVVM with Google Architectural components Room, LiveData and ViewModel. Written in Kotlin

May 1, 2022
🎯 Einsen is a prioritization app that uses Eisenhower matrix technique as workflow to prioritize a list of tasks & built to Demonstrate use of Jetpack Compose with Modern Android Architecture Components & MVVM Architecture.
🎯 Einsen is a prioritization app that uses Eisenhower matrix technique as workflow to prioritize a list of tasks & built to Demonstrate use of Jetpack Compose with Modern Android Architecture Components & MVVM Architecture.

?? Einsen Einsen is a prioritization app that uses Eisenhower matrix technique as workflow to prioritize a list of tasks & built to Demonstrate use of

May 15, 2022
🧬 Android DataBinding kit for notifying data changes from Model layers to UI layers on MVVM architecture.
🧬 Android DataBinding kit for notifying data changes from Model layers to UI layers on MVVM architecture.

?? Android DataBinding kit for notifying data changes from Model layers to UI layers on MVVM architecture.

May 19, 2022
Nucleus is an Android library, which utilizes the Model-View-Presenter pattern to properly connect background tasks with visual parts of an application.

Nucleus Deprecation notice Nucleus is not under develpment anymore. It turns out that Redux architecture scales way better than MVP/MVI/MVVM/MVxxx and

May 4, 2022
❤️ Android Ghibli using Hilt, Motion, Coroutines, Jetpack (ViewModel) based on MVVM architecture.

Glibhi ❤️ Android Ghibli using Hilt, Motion, Coroutines, Jetpack (ViewModel) based on MVVM architecture. Tech stack & Open-source libraries Minimum SD

Mar 11, 2022
Kotlin Multiplatform Router for Android and iOS
Kotlin Multiplatform Router for Android and iOS

A powerful Kotlin Multiplatform Router for Android and iOS Support I am happy to help you with any problem on gitter Feel free to open any new issue!

Apr 12, 2022
A data-binding Presentation Model(MVVM) framework for the Android platform.

PLEASE NOTE, THIS PROJECT IS NO LONGER BEING MAINTAINED. As personal time contraints, I am currently unable to keep up. Please use official android da

May 18, 2022
JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends.
 JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends.

JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends. Also, It uses some of Jetpack and popular libraries. These are Kotlin Coroutine-Flow, kotlinx.serialization, Hilt, Compose, Accompanist, Retrofit2, OkHttp3, Chucker, MockWebServer, Truth.

May 19, 2022
Android App using Kotlin, MVVM, ViewModel, LiveData, Coroutines, Room and DataBinding
Android App using Kotlin, MVVM, ViewModel, LiveData, Coroutines, Room and DataBinding

Words Android App using Kotlin, MVVM, ViewModel, LiveData, Coroutines, Room and

Mar 9, 2022
Kotlin+Flow+Retrofit+OKHttp+ViewBanding+ViewModel+LiveData封装的一个Kotlin版本的MVVM框架

Gitee 地址:kmvvm Github 地址:kmvvm CHANGE LOG 技术要点 支持Flow+Retrofit+OkHttp实现链式http请求 支持Rxjava+Retrofit+OkHttp实现链式http请求 封装基类:BaseActivity、BaseVMActivity、Ba

May 23, 2022
Android Clean Architecture💎 Base Project Android with Kotlin and MVVM applying clean architecture
 Android Clean Architecture💎 Base Project Android with Kotlin and MVVM applying clean architecture

Android Clean Architecture?? Base Project Android with Kotlin and MVVM applying clean architecture

May 18, 2022
Atividade do Google Codelabs, utilizando ViewModel e DataBinding.

Cupcake app This app contains an order flow for cupcakes with options for quantity, flavor, and pickup date. The order details get displayed on an ord

Nov 24, 2021
ViewBinding, ViewModel Sample code

Sample_viewBiningK viewBinding, viewModel 샘플코드입니다. 코드출처 app: "모던 안드로이드 아키텍쳐 - Room + LiveData + ViewModel 대통합(https://www.youtube.com/watch?v=fUbiWZ2g

Jan 1, 2022
📊 A Minimal Expense Tracker App built to demonstrate the use of modern android architecture component with MVVM Architecture
📊 A Minimal Expense Tracker App built to demonstrate the use of modern android architecture component with MVVM Architecture

Expenso ?? A Simple Expense Tracker App ?? built to demonstrate the use of modern android architecture component with MVVM Architecture ?? . Made with

May 10, 2022
LukohSplash is based on Android latest architectural components,Jetpack, and follows MVVM design pattern.
LukohSplash is based on Android latest architectural components,Jetpack, and follows MVVM design pattern.

?? The LukohSplash by open-source contributor, Lukoh. LukohSplash LukohSplash is based on Android latest architectural components,Jetpack, and follows

May 3, 2022
MVU for Kotlin Multiplatform

Oolong Oolong is an Elm inspired Model-View-Update (MVU) implementation for Kotlin multiplatform. As the name implies, three core concepts comprise th

May 16, 2022
Extendable MVI framework for Kotlin Multiplatform with powerful debugging tools (logging and time travel), inspired by Badoo MVICore library
Extendable MVI framework for Kotlin Multiplatform with powerful debugging tools (logging and time travel), inspired by Badoo MVICore library

Should you have any questions or ideas please welcome to the Slack channel: #mvikotlin Inspiration This project is inspired by Badoo MVICore library.

May 17, 2022
Redux implementation for Kotlin (supports multiplatform JVM, native, JS, WASM)

Redux-Kotlin ![badge][badge-ios] A redux standard for Kotlin that supports multiplatform projects. Full documentation at http://reduxkotlin.org. Misso

May 16, 2022