Skip to content

Commit

Permalink
Merge pull request #235 from icerockdev/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex009 committed Apr 8, 2023
2 parents 4ab6dee + 5c04bc6 commit 8545a19
Show file tree
Hide file tree
Showing 38 changed files with 324 additions and 395 deletions.
41 changes: 22 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,25 @@ allprojects {
project build.gradle
```groovy
dependencies {
commonMainApi("dev.icerock.moko:mvvm-core:0.15.0") // only ViewModel, EventsDispatcher, Dispatchers.UI
commonMainApi("dev.icerock.moko:mvvm-flow:0.15.0") // api mvvm-core, CFlow for native and binding extensions
commonMainApi("dev.icerock.moko:mvvm-livedata:0.15.0") // api mvvm-core, LiveData and extensions
commonMainApi("dev.icerock.moko:mvvm-state:0.15.0") // api mvvm-livedata, ResourceState class and extensions
commonMainApi("dev.icerock.moko:mvvm-livedata-resources:0.15.0") // api mvvm-core, moko-resources, extensions for LiveData with moko-resources
commonMainApi("dev.icerock.moko:mvvm-flow-resources:0.15.0") // api mvvm-core, moko-resources, extensions for Flow with moko-resources
commonMainApi("dev.icerock.moko:mvvm-core:0.16.0") // only ViewModel, EventsDispatcher, Dispatchers.UI
commonMainApi("dev.icerock.moko:mvvm-flow:0.16.0") // api mvvm-core, CFlow for native and binding extensions
commonMainApi("dev.icerock.moko:mvvm-livedata:0.16.0") // api mvvm-core, LiveData and extensions
commonMainApi("dev.icerock.moko:mvvm-state:0.16.0") // api mvvm-livedata, ResourceState class and extensions
commonMainApi("dev.icerock.moko:mvvm-livedata-resources:0.16.0") // api mvvm-core, moko-resources, extensions for LiveData with moko-resources
commonMainApi("dev.icerock.moko:mvvm-flow-resources:0.16.0") // api mvvm-core, moko-resources, extensions for Flow with moko-resources
androidMainApi("dev.icerock.moko:mvvm-flow-compose:0.15.0") // api mvvm-flow, binding extensions for Jetpack Compose (jvm, js, android)
androidMainApi("dev.icerock.moko:mvvm-livedata-compose:0.15.0") // api mvvm-livedata, binding extensions for Jetpack Compose (jvm, js, android)
androidMainApi("dev.icerock.moko:mvvm-livedata-material:0.15.0") // api mvvm-livedata, Material library android extensions
androidMainApi("dev.icerock.moko:mvvm-livedata-glide:0.15.0") // api mvvm-livedata, Glide library android extensions
androidMainApi("dev.icerock.moko:mvvm-livedata-swiperefresh:0.15.0") // api mvvm-livedata, SwipeRefreshLayout library android extensions
androidMainApi("dev.icerock.moko:mvvm-databinding:0.15.0") // api mvvm-livedata, DataBinding support for Android
androidMainApi("dev.icerock.moko:mvvm-viewbinding:0.15.0") // api mvvm-livedata, ViewBinding support for Android
// compose multiplatform
commonMainApi("dev.icerock.moko:mvvm-compose:0.16.0") // api mvvm-core, getViewModel for Compose Multiplatfrom
commonMainApi("dev.icerock.moko:mvvm-flow-compose:0.16.0") // api mvvm-flow, binding extensions for Compose Multiplatfrom
commonMainApi("dev.icerock.moko:mvvm-livedata-compose:0.16.0") // api mvvm-livedata, binding extensions for Compose Multiplatfrom
androidMainApi("dev.icerock.moko:mvvm-livedata-material:0.16.0") // api mvvm-livedata, Material library android extensions
androidMainApi("dev.icerock.moko:mvvm-livedata-glide:0.16.0") // api mvvm-livedata, Glide library android extensions
androidMainApi("dev.icerock.moko:mvvm-livedata-swiperefresh:0.16.0") // api mvvm-livedata, SwipeRefreshLayout library android extensions
androidMainApi("dev.icerock.moko:mvvm-databinding:0.16.0") // api mvvm-livedata, DataBinding support for Android
androidMainApi("dev.icerock.moko:mvvm-viewbinding:0.16.0") // api mvvm-livedata, ViewBinding support for Android
commonTestImplementation("dev.icerock.moko:mvvm-test:0.15.0") // test utilities
commonTestImplementation("dev.icerock.moko:mvvm-test:0.16.0") // test utilities
}
```

Expand All @@ -75,10 +78,10 @@ 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.15.0")
export("dev.icerock.moko:mvvm-livedata:0.15.0")
export("dev.icerock.moko:mvvm-livedata-resources:0.15.0")
export("dev.icerock.moko:mvvm-state:0.15.0")
export("dev.icerock.moko:mvvm-core:0.16.0")
export("dev.icerock.moko:mvvm-livedata:0.16.0")
export("dev.icerock.moko:mvvm-livedata-resources:0.16.0")
export("dev.icerock.moko:mvvm-state:0.16.0")
}
}
}
Expand All @@ -94,7 +97,7 @@ generation enabled. All `LiveData` to `UIView` bindings is extensions for UI ele
To use MOKO MVVM with SwiftUI set name of your kotlin framework to `MultiPlatformLibrary` and add
dependency to CocoaPods:
```ruby
pod 'mokoMvvmFlowSwiftUI', :podspec => 'https://raw.githubusercontent.com/icerockdev/moko-mvvm/release/0.15.0/mokoMvvmFlowSwiftUI.podspec'
pod 'mokoMvvmFlowSwiftUI', :podspec => 'https://raw.githubusercontent.com/icerockdev/moko-mvvm/release/0.16.0/mokoMvvmFlowSwiftUI.podspec'
```
required export of `mvvm-core` and `mvvm-flow`.

Expand Down
4 changes: 1 addition & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ org.gradle.configureondemand=false
org.gradle.parallel=true

kotlin.code.style=official
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.mpp.enableCompatibilityMetadataVariant=true
kotlin.mpp.androidSourceSetLayoutVersion=2

android.useAndroidX=true

Expand Down
32 changes: 16 additions & 16 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
[versions]
kotlinVersion = "1.6.10"
androidLifecycleVersion = "2.2.0"
coroutinesVersion = "1.6.0-native-mt"
mokoResourcesVersion = "0.18.0"
kotlinVersion = "1.8.10"
androidLifecycleVersion = "2.6.1"
coroutinesVersion = "1.6.4"
mokoResourcesVersion = "0.21.1"
mokoTestVersion = "0.6.1"
mokoMvvmVersion = "0.15.0"
mokoKSwiftVersion = "0.4.0"
composeVersion = "1.1.1"
composeJetBrainsVersion = "1.1.1"
mokoMvvmVersion = "0.16.0"
mokoKSwiftVersion = "0.6.1"
composeVersion = "1.4.0"
composeJetBrainsVersion = "1.3.1"

[libraries]
# android
appCompat = { module = "androidx.appcompat:appcompat", version = "1.2.0" }
material = { module = "com.google.android.material:material", version = "1.2.1" }
lifecycle = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "androidLifecycleVersion" }
appCompat = { module = "androidx.appcompat:appcompat", version = "1.6.1" }
androidxCore = { module = "androidx.core:core", version = "1.9.0" }
material = { module = "com.google.android.material:material", version = "1.8.0" }
lifecycleKtx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidLifecycleVersion" }
androidViewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidLifecycleVersion" }
androidLiveData = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidLifecycleVersion" }
glide = { module = "com.github.bumptech.glide:glide", version = "4.11.0" }
glide = { module = "com.github.bumptech.glide:glide", version = "4.15.1" }
swipeRefresh = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }

# compose
Expand All @@ -35,14 +35,14 @@ mokoKSwift = { module = "dev.icerock.moko:kswift-runtime", version.ref = "mokoKS

# tests
kotlinTestJUnit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinVersion" }
androidCoreTesting = { module = "androidx.arch.core:core-testing", version = "2.1.0" }
androidCoreTesting = { module = "androidx.arch.core:core-testing", version = "2.2.0" }

# gradle plugins
dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "kotlinVersion" }
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion" }
mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform", version = "0.13.0" }
androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "7.2.2" }
detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.19.0" }
mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform", version = "0.14.2" }
androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "7.4.2" }
detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.22.0" }
kswiftGradlePlugin = { module = "dev.icerock.moko:kswift-gradle-plugin", version.ref = "mokoKSwiftVersion" }
composeJetBrainsGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetBrainsVersion" }

Expand Down
4 changes: 3 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
# can't update to gradle 8 with kotlin 1.8
# https://youtrack.jetbrains.com/issue/KT-55751
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion mokoMvvmFlowSwiftUI.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'mokoMvvmFlowSwiftUI'
s.version = '0.15.0'
s.version = '0.16.0'
s.summary = 'MOKO MVVM SwiftUI additions for Flow'
s.description = 'some description here'
s.homepage = 'localhost'
Expand Down
4 changes: 2 additions & 2 deletions mvvm-build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

enableFeaturePreview("VERSION_CATALOGS")

dependencyResolutionManagement {
repositories {
mavenCentral()
Expand All @@ -16,3 +14,5 @@ dependencyResolutionManagement {
}
}
}

rootProject.name = "mvvm-build-logic"
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ kotlin {
val mobileMain by creating
val mobileTest by creating
val androidMain by getting
val androidTest by getting
val androidUnitTest by getting

mobileMain.dependsOn(commonMain)
mobileTest.dependsOn(commonTest)

androidMain.dependsOn(mobileMain)
androidTest.dependsOn(mobileTest)
androidUnitTest.dependsOn(mobileTest)

iosMain.dependsOn(mobileMain)
iosTest.dependsOn(mobileTest)
Expand Down
42 changes: 42 additions & 0 deletions mvvm-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("kmp-library-convention")
id("detekt-convention")
id("org.jetbrains.compose")
id("javadoc-stub-convention")
id("publication-convention")
}

android {
namespace = "dev.icerock.moko.mvvm.compose"
defaultConfig {
minSdk = 21
}
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}

kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.mvvmCore)
api(compose.runtime)
}
}

androidMain {
dependencies {
implementation(libs.composeFoundation)
implementation(libs.androidxCore)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.compose

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ComponentActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.viewModelFactory
import dev.icerock.moko.mvvm.getViewModel
import dev.icerock.moko.mvvm.viewmodel.ViewModel
import kotlin.reflect.KClass

@Composable
actual fun <T : ViewModel> getViewModel(
key: Any,
klass: KClass<T>,
viewModelBlock: () -> T
): T {
val context: Context = LocalContext.current
val storeHolder: ViewModelStoreHolder = remember(context, key) {
val viewModelStore: ViewModelStoreOwner = context as? ViewModelStoreOwner
?: error("context not implement ViewModelStoreOwner")

val storeViewModel: StoreViewModel = viewModelStore.getViewModel { StoreViewModel() }
storeViewModel.get(key)
}
val viewModel: T = remember(storeHolder) {
ViewModelProvider(
store = storeHolder.viewModelStore,
factory = viewModelFactory { addInitializer(klass) { viewModelBlock() } }
)[klass.java]
}

DisposableEffect(context, storeHolder) {
onDispose {
val componentActivity: ComponentActivity = context as? ComponentActivity
?: error("context should be ComponentActivity")

if (!componentActivity.isChangingConfigurations) {
storeHolder.viewModelStore.clear()
storeHolder.disposeStore()
}
}
}

return viewModel
}

private class StoreViewModel : androidx.lifecycle.ViewModel() {
private val stores: MutableMap<Any, ViewModelStore> = mutableMapOf()

fun get(key: Any): ViewModelStoreHolder {
val store: ViewModelStore = stores[key] ?: ViewModelStore()

if (stores.containsKey(key).not()) {
stores[key] = store
}

return ViewModelStoreHolder(
viewModelStore = store,
disposeStore = { stores.remove(key) }
)
}
}

private data class ViewModelStoreHolder(
val viewModelStore: ViewModelStore,
val disposeStore: () -> Unit
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.compose

import dev.icerock.moko.mvvm.viewmodel.ViewModel
import kotlin.reflect.KClass

interface ViewModelFactory<T : ViewModel> {
val kClass: KClass<T>

fun createViewModel(): T
}

inline fun <reified T : ViewModel> viewModelFactory(
crossinline builder: () -> T
): ViewModelFactory<T> {
return object : ViewModelFactory<T> {
override val kClass: KClass<T> = T::class
override fun createViewModel(): T = builder()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.mvvm.compose

import androidx.compose.runtime.Composable
import dev.icerock.moko.mvvm.viewmodel.ViewModel
import kotlin.reflect.KClass

@Composable
expect fun <T : ViewModel> getViewModel(
key: Any,
klass: KClass<T>,
viewModelBlock: () -> T
): T

// at now function can't be used on iOS because of https://youtrack.jetbrains.com/issue/KT-57727
//@Composable
//inline fun <reified T> getViewModel(
// key: Any,
// noinline viewModelBlock: @DisallowComposableCalls () -> T
//): T = remember(key, T::class, viewModelBlock)

/**
* While https://youtrack.jetbrains.com/issue/KT-57727 it's best API that i can do.
*
* Created ViewModel will be saved during configuration change on Android and will call
* `onCleared` when composition will destroyed.
*
* Usage:
* ```
* getViewModel(
* key = "some-key-of-current-navigation-screen",
* factory = viewModelFactory { SimpleViewModel() }
* )
* ```
* @param key if key have same value with last recomposition - function return same ViewModel instance
* @param factory ViewModel factory, used when key changed or ViewModel not created at all
*
* @return new created, or already exist ViewModel instance
*/
@Composable
fun <T : ViewModel> getViewModel(
key: Any,
factory: ViewModelFactory<T>
): T {
return getViewModel(
key = key,
klass = factory.kClass,
viewModelBlock = factory::createViewModel
)
}
Loading

0 comments on commit 8545a19

Please sign in to comment.