Skip to content

Commit

Permalink
Merge pull request #36 from Tlaster/compose-1.4
Browse files Browse the repository at this point in the history
Compose 1.4
  • Loading branch information
Tlaster authored Apr 17, 2023
2 parents 16d1a47 + 09cfbd1 commit 20a53fd
Show file tree
Hide file tree
Showing 64 changed files with 1,194 additions and 1,039 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PreCompose
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/moe.tlaster/precompose/badge.svg)](https://maven-badges.herokuapp.com/maven-central/moe.tlaster/precompose)
[![compose-jb-version](https://img.shields.io/badge/compose--jb-1.3.0--beta03-blue)](https://github.com/JetBrains/compose-jb)
[![compose-jb-version](https://img.shields.io/badge/compose--jb-1.4.0-blue)](https://github.com/JetBrains/compose-jb)
![license](https://img.shields.io/github/license/Tlaster/PreCompose)

![badge-Android](https://img.shields.io/badge/Platform-Android-brightgreen)
Expand All @@ -17,6 +17,7 @@ Compose Multiplatform Navigation && ViewModel, inspired by Jetpack Lifecycle, Vi
- Super easy to set up.
- No need to write platform-specific code and UI.
- Lifecycle is handled by PreCompose, you don't need to worry about it.
- With Molecule integration, you can easily write your business logic in Kotlin Multiplatform project.

# Setup
[Setup guide for PreCompose](/docs/setup.md)
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ repositories {
}

dependencies {
implementation("com.android.tools.build:gradle:7.4.0")
api(kotlin("gradle-plugin", version = "1.8.0"))
implementation("com.android.tools.build:gradle:7.4.1")
api(kotlin("gradle-plugin", version = "1.8.20"))
}
8 changes: 4 additions & 4 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object Versions {
}

object Kotlin {
const val lang = "1.8.00"
const val lang = "1.8.20"
const val coroutines = "1.6.4"
}

Expand All @@ -21,10 +21,10 @@ object Versions {

const val spotless = "6.7.0"
const val ktlint = "0.45.2"
const val compose = "1.3.1"
const val compose_jb = "1.3.0"
const val compose = "1.4.1"
const val compose_jb = "1.4.0"

object AndroidX {
const val activity = "1.6.1"
const val activity = "1.7.0"
}
}
15 changes: 0 additions & 15 deletions docs/component/live_data.md

This file was deleted.

11 changes: 9 additions & 2 deletions docs/component/view_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ class HomeViewModel : ViewModel() {
```
With `viewModelScope` you can run suspend function like what you've done in Jetpack ViewModel.

To use ViewModel in compose, you can use the `viewModel()`
To use ViewModel in compose, you can use the `viewModel()`
```kotlin
val viewModel = viewModel(keys = listOf(someKey)) {
SomeViewModel(someKey)
}
```
When the data that passing to the `keys` parameter changed, viewModel will be recreated, otherwise you will have the same viewModel instance. It's useful when your viewModel depends on some parameter that will receive from outside.
When the data that passing to the `keys` parameter changed, viewModel will be recreated, otherwise you will have the same viewModel instance. It's useful when your viewModel depends on some parameter that will receive from outside.

NOTE: If you're using Kotlin/Native target, please use viewModel with modelClass parameter instead.
```kotlin
val viewModel = viewModel(modelClass = SomeViewModel::class, keys = listOf(someKey)) {
SomeViewModel(someKey)
}
```
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ kotlin.native.cacheKind=none
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
android.disableAutomaticComponentCreation=true
android.disableAutomaticComponentCreation=true
kotlin.mpp.androidSourceSetLayoutVersion=2
9 changes: 5 additions & 4 deletions precompose-molecule/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import org.jetbrains.compose.compose
import java.util.Properties

plugins {
Expand Down Expand Up @@ -30,13 +29,16 @@ kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
js(IR) {
browser()
}
sourceSets {
val commonMain by getting {
dependencies {
compileOnly(compose.foundation)
compileOnly(compose.animation)
compileOnly(project(":precompose"))
compileOnly("app.cash.molecule:molecule-runtime:0.7.0")
compileOnly("app.cash.molecule:molecule-runtime:0.9.0")
}
}
val commonTest by getting {
Expand All @@ -52,7 +54,7 @@ kotlin {
implementation("androidx.compose.ui:ui:${Versions.compose}")
}
}
val androidTest by getting {
val androidUnitTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2")
Expand Down Expand Up @@ -100,7 +102,6 @@ android {
namespace = "moe.tlaster.precompose.molecule"
defaultConfig {
minSdk = Versions.Android.min
targetSdk = Versions.Android.target
}
compileOptions {
sourceCompatibility = Versions.Java.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package moe.tlaster.precompose.molecule
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
Expand All @@ -14,15 +15,16 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import moe.tlaster.precompose.ui.viewModel
import moe.tlaster.precompose.viewmodel.ViewModel
import moe.tlaster.precompose.stateholder.LocalStateHolder
import moe.tlaster.precompose.stateholder.StateHolder
import kotlin.coroutines.CoroutineContext

internal expect fun providePlatformDispatcher(): CoroutineContext

private class PresenterViewModel<T>(
@OptIn(ExperimentalStdlibApi::class)
private class PresenterHolder<T>(
body: @Composable () -> T,
) : ViewModel() {
) : AutoCloseable {
private val dispatcher = providePlatformDispatcher()
private val clock = if (dispatcher[MonotonicFrameClock] == null) {
RecompositionClock.Immediate
Expand All @@ -32,29 +34,16 @@ private class PresenterViewModel<T>(
private val scope = CoroutineScope(dispatcher)
val state = scope.launchMolecule(clock, body)

override fun onCleared() {
override fun close() {
scope.cancel()
}
}

@Composable
private fun <T> rememberPresenterState(
keys: List<Any?>,
body: @Composable () -> T,
): StateFlow<T> {
@Suppress("UNCHECKED_CAST")
val viewModel = viewModel(
modelClass = PresenterViewModel::class,
keys = keys,
creator = { PresenterViewModel(body) }
) as PresenterViewModel<T>
return viewModel.state
}

private class ActionViewModel<T> : ViewModel() {
@OptIn(ExperimentalStdlibApi::class)
private class ActionViewHolder<T> : AutoCloseable {
val channel = Channel<T>(Channel.UNLIMITED)
val pair = channel to channel.consumeAsFlow()
override fun onCleared() {
override fun close() {
channel.close()
}
}
Expand All @@ -63,18 +52,55 @@ private class ActionViewModel<T> : ViewModel() {
private fun <E> rememberAction(
keys: List<Any?>,
): Pair<Channel<E>, Flow<E>> {
@Suppress("UNCHECKED_CAST")
val viewModel = viewModel(
modelClass = ActionViewModel::class,
keys = keys,
creator = { ActionViewModel<E>() }
) as ActionViewModel<E>
return viewModel.pair
val stateHolder = LocalStateHolder.current
val key = remember(keys) {
(keys.map { it.hashCode().toString() } + ActionViewHolder::class.simpleName).joinToString()
}
return stateHolder.getOrPut(key) {
ActionViewHolder<E>()
}.pair
}

/**
* Return StateFlow, use it in your Compose UI
* The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
* @param keys The keys to use to identify the Presenter
* @param body The body of the molecule presenter
* @return StateFlow
*/
@Composable
private fun <T> rememberPresenterState(
keys: List<Any?> = emptyList(),
body: @Composable () -> T,
): StateFlow<T> {
val stateHolder = LocalStateHolder.current
val key = remember(keys) {
(keys.map { it.hashCode().toString() } + PresenterHolder::class.simpleName).joinToString()
}
return stateHolder.getOrPut(key) {
PresenterHolder(body)
}.state
}

/**
* Return State, use it in your Compose UI
* The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
* @param keys The keys to use to identify the Presenter
* @param body The body of the molecule presenter
* @return State
*/
@Composable
fun <T> producePresenter(
keys: List<Any?> = emptyList(),
body: @Composable () -> T,
): State<T> {
val presenter = rememberPresenterState(keys = keys) { body() }
return presenter.collectAsState()
}

/**
* Return pair of State and Action Channel, use it in your Compose UI
* The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel]
* The molecule scope and the Action Channel will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
*
* @param keys The keys to use to identify the Presenter
* @param body The body of the molecule presenter, the flow parameter is the flow of the action channel
Expand All @@ -93,7 +119,7 @@ fun <T, E> rememberPresenter(

/**
* Return pair of State and Action Channel, use it in your Compose UI
* The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel]
* The molecule scope and the Action Channel will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
*
* @param body The body of the molecule presenter, the flow parameter is the flow of the action channel
* @return Pair of State and Action channel
Expand Down
Loading

0 comments on commit 20a53fd

Please sign in to comment.