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.
Issues
  • Kotlin 1.5 compatibility

    Kotlin 1.5 compatibility

    Are there any timeframes of when the library will be compatible with kotlin 1.5?

    enhancement 
    opened by jorgezim 9
  • 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 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
  • 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
  • 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
  • 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
  • Add support of using @State attribute of SwiftUI with moko-mvvm State class

    Add support of using @State attribute of SwiftUI with moko-mvvm State class

    When importing the MultiPlatformLibrary to a SwiftUI view I get an error "Unknown attribute @State". I think that the State class defined in moko-mvvm somehow causes this issue.

    enhancement 
    opened by michaelbrainvu 4
  • Moko Swift extensions are failed to compile

    Moko Swift extensions are failed to compile

    The Swift extensions for Moko LiveData are failed to compile with "Use of undeclared type LiveData" message. It happens because after compiling the shared library the respective class names have "Mvvm" prefix. Moreover, when I change the names to the correct ones, happens that they don't have generics which leads to new compilation errors.

    Screen Shot 2020-02-28 at 17 33 50 question 
    opened by caesar84mx 4
  • Use MutableLiveData with Model

    Use MutableLiveData with Model

    I try use MutableLiveData with Model, but it not work for me. My Model: Снимок экрана 2020-07-01 в 16 31 03

    My Screen Model: Снимок экрана 2020-07-01 в 16 30 44 Снимок экрана 2020-07-01 в 16 30 53

    And use in Screen: Снимок экрана 2020-07-01 в 16 31 40

    And I dont understand how use MutableLiveData type - Int. I try get value like this: Снимок экрана 2020-07-01 в 16 31 50

    But its not work: Снимок экрана 2020-07-01 в 16 32 02

    And one more question, how use MutableLiveData type Array.

    question 
    opened by Diy2210 4
  • Work on generics in ResourceState

    Work on generics in ResourceState

    Generics now are more meaningful, and redundant generics were removed

    opened by y9san9 2
  • How to correctly export mpp-library as framework?

    How to correctly export mpp-library as framework?

    I clone this project and run bash ./gradlew packForXcode command. But I got the error: Task 'packForXcode' not found in root project 'mokoMvvm'. After that I add:

    val packForXcode by tasks.creating(Sync::class) {
        group = "build"
        val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
        val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
        val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
        val framework =
            kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
        inputs.property("mode", mode)
        dependsOn(framework.linkTask)
        val targetDir = File(buildDir, "xcode-frameworks")
        from({ framework.outputDirectory })
        into(targetDir)
    }
    
    tasks.getByName("build").dependsOn(packForXcode)
    

    But get an error: Key debugFramework is missing in the map.

    question 
    opened by Mura75 1
  • Execution failed for task ':shared:linkDebugFrameworkIosX64'.

    Execution failed for task ':shared:linkDebugFrameworkIosX64'.

    I want to export moko-mvvm library with this command: targets.withType(KotlinNativeTarget::class.java).all { val arch = when (this.konanTarget) { org.jetbrains.kotlin.konan.target.KonanTarget.IOS_X64 -> "iosx64" org.jetbrains.kotlin.konan.target.KonanTarget.IOS_ARM64 -> "iosarm64" else -> throw IllegalArgumentException() } binaries.withType(Framework::class.java).all { isStatic = false export("dev.icerock.moko:mvvm-$arch:0.10.1") } }

    But when I run ./gradlew packForXcode I have an error: `Following dependencies exported in the debugFramework binary are not specified as API-dependencies of a corresponding source set:

    dev.icerock.moko:mvvm-iosx64:0.10.1

    Please add them in the API-dependencies and rerun the build.`

    question 
    opened by Mura75 1
  • Add livedata twoway bindings with text formatting

    Add livedata twoway bindings with text formatting

    Now we implement textformatting support inside project, but we can implement support of formatting out of box. for example with integration of https://github.com/redmadrobot/input-mask-android and https://github.com/redmadrobot/input-mask-ios as we do inside our projects.

    or with https://github.com/artemkrachulov/AKMaskField on iOS

    or by custom code:

    import Foundation
    import UIKit
    
    class DefaultTextFormatter: NSObject {
        
        public let textPattern: String
        
        /// Symbol that will be replace by input symbols
        public let patternSymbol: Character
        
        public init(textPattern: String,
                    patternSymbol: Character = "#") {
            self.textPattern = textPattern
            self.patternSymbol = patternSymbol
        }
        
        func format(_ unformattedText: String?) -> String? {
            guard let unformattedText = unformattedText else { return nil }
            var formatted = String.init()
            var unformattedIndex = 0
            var patternIndex = 0
            
            while patternIndex < textPattern.count && unformattedIndex < unformattedText.count {
                guard let patternCharacter = textPattern.characterAt(patternIndex) else { break }
                if patternCharacter == patternSymbol {
                    if let unformattedCharacter = unformattedText.characterAt(unformattedIndex) {
                        formatted.append(unformattedCharacter)
                    }
                    unformattedIndex += 1
                } else {
                    formatted.append(patternCharacter)
                }
                patternIndex += 1
            }
            return formatted
        }
        
        func unformat(_ formatted: String?) -> String? {
            guard let formatted = formatted else { return nil }
            
            if textPattern.starts(with: formatted) {
                return formatted
            }
            var unformatted = String()
            var formattedIndex = 0
            
            while formattedIndex < formatted.count {
                if let formattedCharacter = formatted.characterAt(formattedIndex) {
                    if formattedIndex >= textPattern.count {
                        unformatted.append(formattedCharacter)
                    } else if formattedCharacter != textPattern.characterAt(formattedIndex) {
                        unformatted.append(formattedCharacter)
                    }
                    formattedIndex += 1
                }
            }
            return unformatted
        }
    }
    
    extension DefaultTextFormatter: UITextFieldDelegate {
        func textField(
            _ textField: UITextField,
            shouldChangeCharactersIn range: NSRange,
            replacementString string: String
        ) -> Bool {
            let newText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
            let unformattedText = unformat(newText)
            
            // Обход проблемы, когда юзер начинает ввод с символа, с которого начинается паттерн
            // (не возможно было начать ввод с 8)
            if textPattern.starts(with: newText ?? "") {
                textField.text = newText
            } else {
                textField.text = format(unformattedText)?.uppercased()
            }
            
            textField.sendActions(for: UIControl.Event.editingChanged)
            return false
        }
    }
    
    
    enhancement 
    opened by Alex009 0
  • #105 Cold livedata rework

    #105 Cold livedata rework

    opened by Alex009 0
  • Implement Swift-friendly api for livedata,state and other

    Implement Swift-friendly api for livedata,state and other

    now from Swift side we got Any types in bind method, and we can't define extensions to UIViews in kotlin, so we should implement good Swift-friendly api in CocoaPod addition or in other way

    enhancement 
    opened by Alex009 1
  • Append `isEmpty()` extension for LiveData<List>

    Append `isEmpty()` extension for LiveData

    That may be helpful for units binding for example:

      tableView.bindVisibility(viewModel.units.isEmpty().not())
    
    enhancement 
    opened by DevTchernov 0
  • Closeable plus operator with nullable argument

    Closeable plus operator with nullable argument

    It would be useful if the plus operator of the Closeable interface would support nullable argument:

    operator fun plus(other: Closeable?): Closeable {
        if (other == null) return this
        return Closeable {
            this.close()
            other.close()
        }
    }
    
    enhancement 
    opened by Tetraquark 0
  • Error when first time compile

    Error when first time compile

    A problem occurred configuring project ':mvvm-databinding'.

    Failed to notify project evaluation listener. SDK location not found. Define location with an ANDROID_SDK_ROOT environment variable or by setting the sdk.dir path in your project's local properties file at '/Users/irfanirawansukirman/AndroidStudioProjects/moko-mvvm/local.properties'. SoftwareComponentInternal with name 'release' not found.

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

    How to solve the problem?

    question 
    opened by irfanirawansukirman 1
Releases(release/0.11.0)
  • 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 15, 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
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 614 Aug 1, 2021
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 336 Jun 24, 2021
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 186 Jul 27, 2021
Repository that showcases 3 Android app architectures: "Standard Android", MVP and MVVM. The exact same app is built 3 times following the different patterns.

Archi This repository showcases and compares different architectural patterns that can be used to build Android apps. The exact same sample app is bui

Iván Carballo 3.4k Jul 26, 2021
Moxy is MVP library for Android

Moxy This Moxy repository is deprecated and no longer supported. Please migrate to the actual version of the Moxy framework at Moxy communuty repo. De

Arello Mobile 1.6k Jul 25, 2021
A full-featured framework that allows building android applications following the principles of Clean Architecture.

EasyMVP A powerful, and very simple MVP library with annotation processing and bytecode weaving. EasyMVP eliminates the boilerplate code for dealing w

null 1.3k Jul 21, 2021
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 259 Jul 30, 2021
BaseDemo 是Android MVVM + Retrofit + OkHttp + Coroutine 协程 + 组件化架构的Android应用开发规范化架构

BaseDemo 是Android MVVM + Retrofit + OkHttp + Coroutine 协程 + 组件化架构的Android应用开发规范化架构,通过不断的升级迭代,目前主要分为两个版本,分别为分支 MVVM+Databinding 组件化版本,分支MVVM+Databinding+Single 单体版本。旨在帮助您快速构建属于自己的APP项目架构,做到快速响应上手,另外再长期的实践经验中汇总了大量的使用工具类,主要放在了项目 `lib_common` 组件中,以供大家参考使用。具体使用请开发者工具自己项目需求决定选择如何使用。

Huan Zhou 35 Jul 22, 2021
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 Jul 16, 2021
Unidirectional Data Flow in Kotlin - Port of https://github.com/ReSwift/ReSwift to Kotlin

ReKotlin Port of ReSwift to Kotlin, which corresponds to ReSwift/4.0.0 Introduction ReKotlin is a Redux-like implementation of the unidirectional data

null 475 Jul 29, 2021
A Model-View-Presenter / Model-View-Intent library for modern Android apps

Mosby A Model-View-Presenter and Model-View-Intent library for Android apps. Dependency dependencies { compile 'com.hannesdorfmann.mosby3:mvi:3.1.1

Hannes Dorfmann 5.5k Jul 25, 2021
A sample project in Kotlin to demonstrate AndroidX, MVVM, Coroutines, Hilt, Room, Data Binding, View Binding, Retrofit, Moshi, Leak Canary and Repository pattern.

This repository contains a sample project in Kotlin to demonstrate AndroidX, MVVM, Coroutines, Hilt, Room, Data Binding, View Binding, Retrofit, Moshi, Leak Canary and Repository pattern

Areg Petrosyan 5 May 24, 2021
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 Jul 14, 2021
MVVM RECIPE ANDROID APP Is an app where I show how to use MVVM, retrofit, dagger hilt, coroutine, liveData, Kotlin, navigation component, and so on...

MVVM RECIPE ANDROID APP Is an app where I show how to use MVVM, retrofit, dagger hilt, coroutine, liveData, kotlin, navigation component, and so on...

Isaias Cuvula 6 Jul 7, 2021