Skip to content

Commit

Permalink
#113 change viewModelScope ability and test sample for LoginViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex009 committed Mar 20, 2021
1 parent 5849e93 commit 9bac53d
Show file tree
Hide file tree
Showing 25 changed files with 228 additions and 40 deletions.
2 changes: 2 additions & 0 deletions mvvm-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ version = Deps.mokoMvvmVersion
dependencies {
commonMainImplementation(Deps.Libs.MultiPlatform.coroutines)

commonMainImplementation(project(":mvvm-internal"))

androidMainApi(Deps.Libs.Android.appCompat)
androidMainApi(Deps.Libs.Android.androidViewModel)

Expand Down
11 changes: 0 additions & 11 deletions mvvm-core/src/androidMain/kotlin/dev/icerock/moko/mvvm/UI.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ actual class EventsDispatcher<ListenerType : Any> {
this.executor = executor
}

/**
* Constructor without lifecycle connection. Used for tests
*/
constructor(executor: Executor, listener: ListenerType) {
this.executor = executor
this.eventsListener = listener
}

fun bind(lifecycleOwner: LifecycleOwner, listener: ListenerType) {
val observer = object : LifecycleObserver {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
package dev.icerock.moko.mvvm.viewmodel

import androidx.lifecycle.ViewModel
import dev.icerock.moko.mvvm.UI
import dev.icerock.moko.mvvm.internal.createViewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel

@Suppress("EmptyDefaultConstructor")
actual open class ViewModel actual constructor() : ViewModel() {
// for now dispatcher fixed on Main. after implementing multithread coroutines on native - we can change it
protected actual val viewModelScope: CoroutineScope = CoroutineScope(Dispatchers.UI)
protected actual val viewModelScope: CoroutineScope = createViewModelScope()

public actual override fun onCleared() {
super.onCleared()
Expand Down
4 changes: 3 additions & 1 deletion mvvm-core/src/commonMain/kotlin/dev/icerock/moko/mvvm/UI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package dev.icerock.moko.mvvm

import dev.icerock.moko.mvvm.internal.createUIDispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

expect val Dispatchers.UI: CoroutineDispatcher
val Dispatchers.UI: CoroutineDispatcher
get() = createUIDispatcher()
12 changes: 0 additions & 12 deletions mvvm-core/src/iosMain/kotlin/dev/icerock/moko/mvvm/UI.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package dev.icerock.moko.mvvm.dispatcher

import platform.darwin.dispatch_async
import platform.darwin.dispatch_get_current_queue
import platform.darwin.dispatch_get_main_queue
import platform.darwin.dispatch_queue_t
import kotlin.native.ref.WeakReference
Expand Down Expand Up @@ -44,8 +45,10 @@ actual class EventsDispatcher<ListenerType : Any> {
return
}

dispatch_async(queue) {
if (dispatch_get_current_queue() == queue) {
block(listener)
} else {
dispatch_async(queue) { block(listener) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

package dev.icerock.moko.mvvm.viewmodel

import dev.icerock.moko.mvvm.UI
import dev.icerock.moko.mvvm.internal.createViewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import platform.UIKit.UIViewController
import platform.darwin.dispatch_async
Expand All @@ -15,8 +14,7 @@ import kotlin.native.internal.GC

@Suppress("EmptyDefaultConstructor")
actual open class ViewModel actual constructor() {
// for now dispatcher fixed on Main. after implementing multithread coroutines on native - we can change it
protected actual val viewModelScope: CoroutineScope = CoroutineScope(Dispatchers.UI)
protected actual val viewModelScope: CoroutineScope = createViewModelScope()

actual open fun onCleared() {
viewModelScope.cancel()
Expand Down
17 changes: 17 additions & 0 deletions mvvm-internal/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
plugin(Deps.Plugins.androidLibrary)
plugin(Deps.Plugins.kotlinMultiplatform)
plugin(Deps.Plugins.mobileMultiplatform)
plugin(Deps.Plugins.mavenPublish)
}

group = "dev.icerock.moko"
version = Deps.mokoMvvmVersion

dependencies {
commonMainApi(Deps.Libs.MultiPlatform.coroutines)
}
2 changes: 2 additions & 0 deletions mvvm-internal/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="dev.icerock.moko.mvvm.internal" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.internal

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

actual fun createUIDispatcher(): CoroutineDispatcher = Dispatchers.Main
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.internal

import kotlinx.coroutines.CoroutineDispatcher

expect fun createUIDispatcher(): CoroutineDispatcher
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.internal

import kotlinx.coroutines.CoroutineScope
import kotlin.native.concurrent.ThreadLocal

/**
* Factory of viewModelScope. Internal API, for ability of mvvm-test to change viewModelScope
* dispatcher.
*
* In default implementation create main-thread dispatcher scope.
*/
@ThreadLocal
var createViewModelScope: () -> CoroutineScope = {
CoroutineScope(createUIDispatcher())
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.viewmodel
package dev.icerock.moko.mvvm.internal

import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
Expand All @@ -18,7 +18,7 @@ import platform.darwin.dispatch_get_main_queue
import platform.darwin.dispatch_time
import kotlin.coroutines.CoroutineContext

@UseExperimental(InternalCoroutinesApi::class)
@OptIn(ExperimentalUnsignedTypes::class, InternalCoroutinesApi::class)
internal class UIDispatcher : CoroutineDispatcher(), Delay {
private val mQueue = dispatch_get_main_queue()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.internal

import kotlinx.coroutines.CoroutineDispatcher

actual fun createUIDispatcher(): CoroutineDispatcher = UIDispatcher()
1 change: 1 addition & 0 deletions mvvm-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
commonMainApi(Deps.Libs.MultiPlatform.coroutines)

commonMainApi(project(":mvvm-core"))
commonMainImplementation(project(":mvvm-internal"))

commonMainApi(Deps.Libs.Tests.kotlinTestJUnit)
androidMainApi(Deps.Libs.Tests.coroutinesTest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ package dev.icerock.moko.mvvm.test
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher

actual fun <T : Any> createTestEventsDispatcher(): EventsDispatcher<T> {
return EventsDispatcher { it.run() }
return EventsDispatcher(executor = { it.run() })
}

actual fun <T : Any> createTestEventsDispatcher(listener: T): EventsDispatcher<T> {
return EventsDispatcher(executor = { it.run() }, listener = listener)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ package dev.icerock.moko.mvvm.test
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher

expect fun <T : Any> createTestEventsDispatcher(): EventsDispatcher<T>

expect fun <T : Any> createTestEventsDispatcher(listener: T): EventsDispatcher<T>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.test

class TestObserver<T> : (T) -> Unit {
private var _invokeCount: Int = 0
val invokeCount: Int get() = _invokeCount

private var _lastObservedValue: T? = null
val lastObservedValue: T? get() = _lastObservedValue

override fun invoke(p1: T) {
_lastObservedValue = p1
_invokeCount++
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.test

import dev.icerock.moko.mvvm.internal.createViewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlin.native.concurrent.ThreadLocal

@ThreadLocal
object TestViewModelScope {
private val originalScope = createViewModelScope

fun setupViewModelScope(coroutineScope: CoroutineScope) {
createViewModelScope = { coroutineScope }
}

fun resetViewModelScope() {
createViewModelScope = originalScope
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher
actual fun <T : Any> createTestEventsDispatcher(): EventsDispatcher<T> {
return EventsDispatcher()
}

actual fun <T : Any> createTestEventsDispatcher(listener: T): EventsDispatcher<T> {
return EventsDispatcher(listener = listener)
}
3 changes: 3 additions & 0 deletions sample/mpp-library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
androidMainApi(Deps.Libs.MultiPlatform.mokoMvvmLiveDataGlide)
androidMainApi(Deps.Libs.MultiPlatform.mokoMvvmLiveDataMaterial)
androidMainApi(Deps.Libs.MultiPlatform.mokoMvvmLiveDataSwipeRefresh)

commonTestImplementation(Deps.Libs.MultiPlatform.mokoTest)
commonTestImplementation(Deps.Libs.MultiPlatform.mokoMvvmTest)
}

framework {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcherOwner
import dev.icerock.moko.mvvm.livedata.LiveData
import dev.icerock.moko.mvvm.livedata.MutableLiveData
import dev.icerock.moko.mvvm.livedata.not
import dev.icerock.moko.mvvm.livedata.all
import dev.icerock.moko.mvvm.livedata.map
import dev.icerock.moko.mvvm.livedata.readOnly
import dev.icerock.moko.mvvm.livedata.revert
import dev.icerock.moko.mvvm.viewmodel.ViewModel
Expand All @@ -28,7 +29,11 @@ class LoginViewModel(
private val _isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
val isLoading: LiveData<Boolean> = _isLoading.readOnly()

val isLoginButtonVisible: LiveData<Boolean> = isLoading.revert()
val isLoginButtonVisible: LiveData<Boolean> = listOf(
isLoading.revert(),
email.map { it.isNotBlank() },
password.map { it.isNotBlank() }
).all(true)

init {
eventsDispatcher.dispatchEvent { showError("inited".desc()) }
Expand All @@ -51,7 +56,7 @@ class LoginViewModel(
userRepository.login(email = emailValue, password = passwordValue)

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

Expand Down
Loading

0 comments on commit 9bac53d

Please sign in to comment.