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

Overview

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.
Comments
  • Coroutines flow as reactive value holders

    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

    question 
    opened by vchernyshov 8
  • iOS pod installation problem

    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

    question 
    opened by Chopyhoo 7
  • Not compatible with CIO (Coroutine-based I/O) Ktor implementation

    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

    question 
    opened by september669 6
  • Separate library to multiple artifacts

    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)

    enhancement 
    opened by ausichenko 6
  • Could not resolve dev.icerock.moko:mvvm:0.6.0

    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.

    question 
    opened by GabrielNoam 6
  • collect CStateFlow in ios

    collect CStateFlow in ios

    val state: CStateFlow<BookingUiState> get() = _state.cStateFlow() how to collect this state in ios, also I want to bind the value to a function call that will fill tableIVew

    opened by periva101 5
  • Problem to install dependencies

    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

    opened by joavilati 5
  • Error Cannot access ViewModel

    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.

    question 
    opened by Diy2210 5
  • question about unit testing MutableLiveData

    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?

    question 
    opened by AliGolmirzaei 5
  • Append iOS-specific ability for memory collecting

    Append iOS-specific ability for memory collecting

    Same as follow:

    actual open class ViewModel actual constructor() {
        ...
        fun collectMemory() {
        	kotlin.native.internal.GC.collect()
        }
    }
    
    enhancement 
    opened by DevTchernov 5
  • Build Error : Execution failed for task ':MultiPlatformLibrary:linkPodDebugFrameworkIosSimulatorArm64'.

    Build Error : Execution failed for task ':MultiPlatformLibrary:linkPodDebugFrameworkIosSimulatorArm64'.

    I am getting build error , when building for iOS Sim in Xcode

    Error : full stacktrace: https://gist.github.com/RageshAntony/fb67ef278eb4f1394e9f7a2a1f319127

    
    > Task :MultiPlatformLibrary:compileKotlinIosSimulatorArm64 UP-TO-DATE
    > Task :MultiPlatformLibrary:linkPodDebugFrameworkIosSimulatorArm64 FAILED
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':MultiPlatformLibrary:linkPodDebugFrameworkIosSimulatorArm64'.
    > 'org.gradle.api.provider.Provider org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink.getIntermediateLibrary()'
    
    

    shared Gradle

    plugins {
        kotlin("multiplatform")
        kotlin("native.cocoapods")
        id("com.android.library")
        id("dev.icerock.moko.kswift")
    }
    
    version = "1.0"
    val mokoMvvmVersion = "0.13.0"
    
    kotlin {
        android()
        iosX64()
        iosArm64()
        iosSimulatorArm64()
    
        cocoapods {
            summary = "Some description for the Shared Module"
            homepage = "Link to the Shared Module homepage"
            ios.deploymentTarget = "14.1"
            podfile = project.file("../iosFullApp/Podfile")
            framework {
                baseName = "MultiPlatformLibrary"
                export("dev.icerock.moko:mvvm-core:$mokoMvvmVersion")
                export("dev.icerock.moko:mvvm-flow:$mokoMvvmVersion")
                export("dev.icerock.moko:mvvm-livedata:$mokoMvvmVersion")
                export("dev.icerock.moko:mvvm-livedata-resources:$mokoMvvmVersion")
                export("dev.icerock.moko:mvvm-state:$mokoMvvmVersion")
            }
        }
        
        sourceSets {
            val commonMain by getting {
                dependencies {
                    implementation ("com.badoo.reaktive:reaktive:1.2.2")
                    implementation ("com.badoo.reaktive:reaktive-annotations:1.2.2")
                    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1-native-mt")
    
                    api("dev.icerock.moko:mvvm-core:$mokoMvvmVersion")
                    api("dev.icerock.moko:mvvm-flow:$mokoMvvmVersion")
                    api("dev.icerock.moko:mvvm-livedata:$mokoMvvmVersion") // api mvvm-core, LiveData and extensions
                    api("dev.icerock.moko:mvvm-state:$mokoMvvmVersion") // api mvvm-livedata, ResourceState class and extensions
                    api("dev.icerock.moko:mvvm-livedata-resources:$mokoMvvmVersion") // api mvvm-core, moko-resources, extensions for LiveData with moko-resources
    
                }
            }
            val commonTest by getting {
                dependencies {
                    implementation(kotlin("test"))
                }
            }
            val androidMain by getting {
                dependencies {
                    api("dev.icerock.moko:mvvm-flow-compose:$mokoMvvmVersion")
                }
            }
            val androidTest by getting
            val iosX64Main by getting
            val iosArm64Main by getting
            val iosSimulatorArm64Main by getting
            val iosMain by creating {
                dependsOn(commonMain)
                iosX64Main.dependsOn(this)
                iosArm64Main.dependsOn(this)
                iosSimulatorArm64Main.dependsOn(this)
            }
            val iosX64Test by getting
            val iosArm64Test by getting
            val iosSimulatorArm64Test by getting
            val iosTest by creating {
                dependsOn(commonTest)
                iosX64Test.dependsOn(this)
                iosArm64Test.dependsOn(this)
                iosSimulatorArm64Test.dependsOn(this)
            }
        }
    }
    
    android {
        compileSdk = 32
        sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
        defaultConfig {
            minSdk = 21
            targetSdk = 32
        }
    }
    
    kswift {
        install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature)
        install(dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature)
    
    }
    
    dependencies {
        commonMainApi("dev.icerock.moko:kswift-runtime:0.5.0") // if you want use annotations
    }
    
    
    

    project gradle

    buildscript {
        val compose_version by extra("1.3.0-alpha02")
    
        repositories {
            gradlePluginPortal()
            google()
            mavenCentral()
        }
        dependencies {
            classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
            classpath("com.android.tools.build:gradle:7.2.1")
            classpath("dev.icerock.moko:kswift-gradle-plugin:0.5.0")
    
        }
    }
    
    allprojects {
        repositories {
            gradlePluginPortal()
            google()
            mavenCentral()
        }
    }
    
    tasks.register("clean", Delete::class) {
        delete(rootProject.buildDir)
    }
    

    podfile

    target 'iosFullApp' do
      use_frameworks!
      platform :ios, '14.1'
      pod 'MultiPlatformLibrary', :path => '../MultiPlatformLibrary'
      pod 'mokoMvvmFlowSwiftUI', :podspec => 'https://raw.githubusercontent.com/icerockdev/moko-mvvm/release/0.13.0/mokoMvvmFlowSwiftUI.podspec'
    end
    

    Please help me

    opened by RageshAntony 4
  • Prevent memory churn described in #208

    Prevent memory churn described in #208

    Fixes #208

    Accessing stateFlow.value is not guaranteed to always return the same instance even when the underlying value is unchanged. This stateFlow behavior was incorrectly causing infinite objectWillChange events due to the equality test always reporting false even when the new value was, in fact, equal to the previous.

    The fix is to go through isEqual(to:) to compare the value contents to determine equality before firing off an objectWillChange event.

    opened by darronschall 2
  • Memory churn when collecting `CStateFlow` of `List` due to `===` test.

    Memory churn when collecting `CStateFlow` of `List` due to `===` test.

    I have a viewModel that exposes a CStateFlow of List that I'm using in SwiftUI. It looks like this:

    private val _items = MutableStateFlow<List<Item>>(listOf()).cMutableStateFlow()
    var items: CStateFlow<List<Item>> = _items.cStateFlow()
    

    In my SwiftUI view, I use the state helper to subscribe to the stateFlow:

    struct ExampleView: View {
      @ObservableObject viewModel = ExampleViewModel()
    
      var body: some View {
        List(viewModel.state(\.items)) { (item: Item) in
          Text("Item: \(item.id)")
        }
      }
    }
    

    This setup is enough to trigger memory churn. Even though items never changes in the viewModel in this example, there's extremely high CPU usage and memory allocations for what should be an idle view.

    I did some digging and have a general idea of the root cause. From what I can tell, asking for stateFlow.value inside the state method allocates a new array, every time. I don't know why this happens, perhaps it has something to do with the Kotlin/Swift(ObjC) bridge. You can see this for yourself by adding some simple logging inside of the method:

    var value1 = stateFlow.value
    var value2 = stateFlow.value
    var value3 = stateFlow.value
                
    print("value1 = \(value1)")
    print("value2 = \(value2)")
    print("value3 = \(value3)")
    print("stateFlow.value = \(stateFlow.value)")
    print("stateFlow.value = \(stateFlow.value)")
    print("same instances = \(stateFlow.value === stateFlow.value)")
    

    This will output, in an infinite loop, something like this:

    value1 = Optional(<SharedKListAsNSArray 0x600001987900>(
    
    )
    )
    value2 = Optional(<SharedKListAsNSArray 0x6000019be500>(
    
    )
    )
    value3 = Optional(<SharedKListAsNSArray 0x6000019aba80>(
    
    )
    )
    stateFlow.value = Optional(<SharedKListAsNSArray 0x6000019abcc0>(
    
    )
    )
    stateFlow.value = Optional(<SharedKListAsNSArray 0x6000019f76c0>(
    
    )
    )
    same instances = false
    

    The solution is to change the equality test of NSArray state helper at https://github.com/icerockdev/moko-mvvm/blob/develop/mvvm-flow/apple/xcode/mokoMvvmFlowSwiftUI/ViewModelState.swift#L86.

    Rather than equals: { $0 === $1 }, it should use isEqual(to:) to compare array contents, equals: { $0!.isEqual(to: $1 as! Array<T>) },

    After making this change locally, the memory churn goes away.

    opened by darronschall 0
  • Add extention for binding error on TextInputLayout. Android platform

    Add extention for binding error on TextInputLayout. Android platform

    Please add binding for formfield error, for my solution im use this

    fun TextInputLayout.bindError( lifecycleOwner: LifecycleOwner, flow: CStateFlow<StringDesc?>, ): DisposableHandle { return flow.bind(lifecycleOwner) { this.error = it?.toString(this.context) this.isErrorEnabled = it != null } }

    opened by ExNDY 0
  • Unable to use viewModel with Koin (ViewModel error from composable)

    Unable to use viewModel with Koin (ViewModel error from composable)

    I got this compile time error after adding viewModel() to the composable, this declaration in composable.

    • viewModel: EmailInputScreenViewModel = viewModel()

    Cannot access 'dev.icerock.moko.mvvm.viewmodel.ViewModel' which is a supertype of 'com.apppackage.viewmodel.ftue.EmailInputScreenViewModel'. Check your module classpath for missing or conflicting dependencies

    Is there any sample project that uses Moko-Viewmodel + Koin + Compose, Is there any other thind

    Screenshot 2022-09-13 at 5 47 33 PM

    opened by sodiqOladeni 1
Releases(release/0.14.0)
  • release/0.14.0(Sep 13, 2022)

    Changes

    • add mvvm-flow-resources module to support StringDesc
    • #184 #195 Add flow-resources with StringDesc bindings for Flow
    • Support for bindText(flow: <CStateFlow) on IOS
    Source code(tar.gz)
    Source code(zip)
  • release/0.13.1(Aug 8, 2022)

  • release/0.13.0(Apr 30, 2022)

    Changes

    • #138 swift friendly api for uikit extensions and for swiftui
    • #162 add CompoundButton extensions
    • #36 added SwiftUI support with additional CocoaPods module (please read readme in install block)
    • #163 added observeAsState for our LiveData
    • #165 added bindings for StateFlow on Android Views and iOS UIViews
    • #168 added Jetpack Compose and SwiftUI sample (see sample-declarative-ui directory)
    Source code(tar.gz)
    Source code(zip)
  • release/0.12.0(Feb 25, 2022)

    Changes

    • Kotlin 1.6.10
    • #166 Apple Silicon support
    • #167 support of jvm, js, linux, windows, all apple platforms
    • #156 android sources publishing
    • #159 fix invalid value in observers of livedata in iOS
    • #155 fix android potential memory leak with databinding

    Breaking changes

    • MutableLiveData.postValue was extracted from class MutableLiveData and moved to extension function. To continue usage please add import dev.icerock.moko.mvvm.livedata.postValue
    Source code(tar.gz)
    Source code(zip)
  • release/0.11.0(Jun 27, 2021)

  • release/0.10.1(Apr 30, 2021)

  • release/0.10.0(Apr 6, 2021)

  • release/0.9.2(Apr 2, 2021)

  • release/0.9.1(Jan 23, 2021)

    Changes

    • Removed unnecessary properties from viewbinding MvvmFragment #96
    • fix unsupported nullable livedata on ios #100

    ATTENTION

    new iOS livedata binding extensions was renamed to fix invalid method calls from swift side.

    Thanks

    @Dorofeev @Tetraquark @ATchernov

    Source code(tar.gz)
    Source code(zip)
  • release/0.9.0(Jan 10, 2021)

    Changes

    • #65 library separated to multiple artifacts: mvvm-core, mvvm-livedata, mvvm-state, mvvm-test. Also only for android available: mvvm-databinding, mvvm-viewbinding, mvvm-livedata-material, mvvm-livedata-glide, mvvm-livedata-swiperefresh. Also mvvm module still exist, and depends on core, livedata, state-deprecated, databinding
    • #77 fix IDE error in databinding android classes
    • #47 State renamed to ResourceState in new module mvvm-state. To backward compatibility now mvvm depends on mvvm-state-deprecated with old named class
    • #60 Flow.asLiveData
    • #76 ViewBinding support
    • dokka generated documentation - https://icerockdev.github.io/moko-mvvm/
    • detekt lint
    • new bintray publish plugin (without false-error state)
    • new module mvvm-test for unit testing mvvm classes

    IMPORTANT

    now for iOS required export not one mvvm module, but multiple - mvvm-core, mvvm-livedata, mvvm-state

    Source code(tar.gz)
    Source code(zip)
  • release/0.8.1(Dec 15, 2020)

    Changes

    • #83 update to kotlin 1.4.21 (and coroutines 1.4.2)
    • #74 fix versions in podspec
    • #71 add ability to set placeholder image on ios bind image
    • #72 fix memory leak on android
    • #70 fix swift 5 support

    Thanks

    @IgnacioCarrionDev , @Dorofeev

    Source code(tar.gz)
    Source code(zip)
  • release/0.8.0(Sep 6, 2020)

  • release/0.7.1(Jul 22, 2020)

  • release/0.7.0(Jul 6, 2020)

    • Up moko-resources to 0.11.0
    • Up kotlin to 1.3.72
    • Up coroutines to 1.3.7
    • Up android arch components to 2.1.0
    • Added unit tests of basic LiveData
    Source code(tar.gz)
    Source code(zip)
  • release/0.6.0(Mar 11, 2020)

  • release/0.5.0(Mar 6, 2020)

    Changes

    • added Dispatchers.UI in public API #26
    • EventsDispatcher can be created from common code and now cache iOS events while listener not attached #12
    • ViewModel.onCleared call GC.collect on iOS #23
    • fix readme install #7
    • ios binding to views logic moved to kotlin #11

    Thanks

    @Dorofeev

    Source code(tar.gz)
    Source code(zip)
  • release/0.4.0(Dec 16, 2019)

    • update to kotlin 1.3.61
    • LiveData.debounce moved to coroutines Flow version (remove moko-core dependency)
    • android minsdk down to api 16
    • EventsDispatcher for iOS not require listener in constructor (can be set later)
    • CI configured
    Source code(tar.gz)
    Source code(zip)
  • release/0.3.1(Oct 30, 2019)

  • release/0.3.0(Oct 19, 2019)

    • ViewModel.coroutineScope renamed to ViewModel.viewModelScope like in Android Architecture Components;
    • UIDispatcher on iOS now support doOnTimeout. Implemented invokeOnTimeout;
    • LiveData<T>.asFlow(): Flow<T> added.
    Source code(tar.gz)
    Source code(zip)
Owner
IceRock Development
Kotlin Multiplatform developers team
IceRock Development
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

Behruz Bahromzoda 2 Apr 23, 2022
📒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

Akshat Bhuhagal 59 Nov 25, 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 Einsen is a prioritization app that uses Eisenhower matrix technique as workflow to prioritize a list of tasks & built to Demonstrate use of

Sanju S 835 Nov 23, 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.

Jaewoong Eum 274 Oct 21, 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

Konstantin Mikheev 2k Nov 18, 2022
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!

Sebastian Sellmair 343 Nov 16, 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

Geovani Amaral 4 Aug 26, 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. Also, It uses some of Jetpack and popular libraries. These are Kotlin Coroutine-Flow, kotlinx.serialization, Hilt, Compose, Accompanist, Retrofit2, OkHttp3, Chucker, MockWebServer, Truth.

Tolga Bolatcan 13 Nov 2, 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

RoboBinding open source 1.3k Nov 15, 2022
Android App using Kotlin, MVVM, ViewModel, LiveData, Coroutines, Room and DataBinding

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

Viacheslav Veselov 0 Jul 16, 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

抓猪 76 Nov 21, 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

Mina Mikhail 102 Nov 19, 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

Fernando Batista 0 Nov 24, 2021
ViewBinding, ViewModel Sample code

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

Hong SeongChan 0 Jan 1, 2022
📊 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

Sanju S 798 Nov 20, 2022
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

Lukoh-Nam 86 Sep 12, 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

Oolong 288 Nov 29, 2022
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.

Arkadii Ivanov 445 Nov 25, 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

Redux-Kotlin 294 Nov 18, 2022