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
  • 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 4
  • Update CI to Xcode 14

    Update CI to Xcode 14

    Now CI uses Xcode 13 to deploy library. It's not affect on library consumer side, but later GitHub will remove Xcode 13 support and we should update CI and project for this.

    enhancement 
    opened by Alex009 0
  • With CStateFlow , I am not getting the Types of the objects in iOS when accessing from State

    With CStateFlow , I am not getting the Types of the objects in iOS when accessing from State

    I already followed the tutorial https://medium.com/p/8158e98c091d.

    But my problem is with CStateFlow is, I am not getting the Types of the objects in iOS

    For example , a snippet from a Viewmodel

        private val _cgeneres: MutableStateFlow<GenreList?> = MutableStateFlow(null)
        val cgeneres: CStateFlow<GenreList?> = _cgeneres.cStateFlow()  // not working
    
        private val _isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
        val isLoading: CStateFlow<Boolean> = _isLoading.cStateFlow()
       .....
    @Serializable
    data class GenreList(
        var genres: ArrayList<Genres> = arrayListOf()
    )
    

    When I am trying to access the isLoading, I am able to get the Data type as Bool

    But,Now the problem is, when I am trying to access the cgeneres, I am unable to get the Data type . It showing some <<error type>> and I am getting this error : Type of expression is ambiguous without more context

    image

    Why is this ?

    Swift able to infer the type of a primitive type such as bool but not a data class GenreList. Why ?

    I need to manually subscribe to it to get the data. But I need to use them directly like a state variable like that isLoading boolean variable ?

    Please help me!

    opened by RageshAntony 1
  • 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
  • How to use cMutableStateFlow with custom objects? (SwiftUI)

    How to use cMutableStateFlow with custom objects? (SwiftUI)

    I want use a custom data class within a ViewModel but the view is not updated automatically. Can someone tell me what's wrong?

    The attached example show's what I try to do. When I change a value inside DataContainer (prop1 or prop2) the UI is not updated. I want prop1 and prop2 to behave like prop3. (prop3 works) I already tried many different ways to automatic update the ui but nothing works.

    Shared Code:

    import dev.icerock.moko.mvvm.flow.cMutableStateFlow
    import dev.icerock.moko.mvvm.viewmodel.ViewModel
    import kotlinx.coroutines.flow.MutableStateFlow
    
    data class DataContainer(
        var prop1 : String,
        var prop2 : String,
    )
    
    class ExampleViewModel: ViewModel() {
        val dataStateFlow = MutableStateFlow(
            DataContainer(prop1 = "1", prop2 = "2")
        ).cMutableStateFlow()
    
        val prop3 = MutableStateFlow("3")
            .cMutableStateFlow()
    
        fun extendProp1() {
            dataStateFlow.value.prop1 += "_1"
        }
    
        fun extendProp2() {
            dataStateFlow.value.prop2 += "_2"
        }
    
        fun extendProp3() {
            prop3.value += "_3"
        }
    }
    

    SwiftUI Code:

    import SwiftUI
    import MultiPlatformLibrary
    import mokoMvvmFlowSwiftUI
    
    struct ContentView: View {
        @ObservedObject var viewModel = ExampleViewModel()
    
        var body: some View {
            Text(viewModel.binding(\.dataStateFlow, equals: { $0 == $1 }, getMapper: { $0 as DataContainer }, setMapper: { $0 as DataContainer }).prop1.wrappedValue)
            Text(viewModel.binding(\.dataStateFlow, equals: { $0 == $1 }, getMapper: { $0 as DataContainer }, setMapper: { $0 as DataContainer }).wrappedValue.prop2)
            Text(viewModel.binding(\.prop3, equals: { $0 == $1 }, getMapper: { $0 as String }, setMapper: { $0 as NSString }).wrappedValue)
    
            Button {
                viewModel.extendProp1()
            } label: {
                Text("Extend Prop 1")
            }
    
            Button {
                viewModel.extendProp2()
            } label: {
                Text("Extend Prop 2")
            }
    
            Button {
                viewModel.extendProp3()
            } label: {
                Text("Extend Prop 3")
            }
        }
    }
    
    opened by Sinostria 2
Releases(release/0.15.0)
  • release/0.15.0(Dec 19, 2022)

    Changes

    • #180 binding for jetpack compose
    • #181 fix nullable swiftui binding
    • #208 fix swiftui array usage
    • #206 remove viewModelScope protect modifier to create extension functions
    • #186 try to fix Xcode 14 support
    Source code(tar.gz)
    Source code(zip)
  • 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 60 Dec 5, 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 840 Jan 2, 2023
🧬 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 275 Dec 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
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 Dec 9, 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
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

抓猪 82 Dec 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

Mina Mikhail 103 Dec 2, 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 813 Dec 30, 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 Dec 20, 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 460 Dec 31, 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 301 Dec 25, 2022