Skip to content

Commit c9eee8f

Browse files
committed
Use molecule
1 parent 696642b commit c9eee8f

File tree

19 files changed

+195
-142
lines changed

19 files changed

+195
-142
lines changed

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ plugins {
1414
id("io.gitlab.arturbosch.detekt") version "1.21.0"
1515
}
1616

17+
// https://issuetracker.google.com/issues/240445963
1718
buildscript {
1819
dependencies {
1920
classpath("org.apache.commons:commons-compress:1.22")

clients/build.gradle.kts

+34-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ kotlin {
4040
config()
4141
}
4242

43+
watchosArm64 {
44+
config()
45+
}
46+
watchosSimulatorArm64 {
47+
config()
48+
}
49+
4350
js(IR) {
4451
browser()
4552
}
@@ -51,7 +58,7 @@ kotlin {
5158
dependencies {
5259
api(projects.shared)
5360
implementation("app.cash.sqldelight:coroutines-extensions:$sqlDelight")
54-
61+
api("app.cash.molecule:molecule-runtime:0.6.0")
5562
api("io.ktor:ktor-client-logging:$ktor")
5663
}
5764
}
@@ -70,21 +77,45 @@ kotlin {
7077
}
7178
}
7279

73-
val iosArm64Main by getting {
80+
val darwinMain by creating {
81+
dependsOn(commonMain.get())
7482
dependencies {
7583
implementation("io.ktor:ktor-client-darwin:$ktor")
7684
implementation("app.cash.sqldelight:native-driver:$sqlDelight")
7785
}
7886
}
87+
val darwinTest by creating {
88+
dependsOn(commonTest.get())
89+
}
90+
91+
val iosArm64Main by getting {
92+
dependsOn(darwinMain)
93+
}
7994
val iosSimulatorArm64Main by getting {
8095
dependsOn(iosArm64Main)
8196
}
8297

83-
val iosArm64Test by getting
98+
val iosArm64Test by getting {
99+
dependsOn(darwinTest)
100+
}
84101

85102
val iosSimulatorArm64Test by getting {
86103
dependsOn(iosArm64Test)
87104
}
105+
106+
val watchosArm64Main by getting {
107+
dependsOn(darwinMain)
108+
}
109+
val watchosArm64Test by getting {
110+
dependsOn(darwinTest)
111+
}
112+
val watchosSimulatorArm64Main by getting {
113+
dependsOn(darwinMain)
114+
}
115+
val watchosSimulatorArm64Test by getting {
116+
dependsOn(darwinTest)
117+
}
118+
88119
val jsMain by getting {
89120
dependencies {
90121
api("app.cash.sqldelight:sqljs-driver:$sqlDelight")

clients/src/commonMain/kotlin/app/softwork/composetodo/viewmodels/LoginViewModel.kt

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package app.softwork.composetodo.viewmodels
22

3+
import androidx.compose.runtime.*
4+
import app.cash.molecule.*
35
import app.softwork.composetodo.*
46
import kotlinx.coroutines.*
57
import kotlinx.coroutines.flow.*
@@ -8,27 +10,49 @@ class LoginViewModel(
810
private val api: API.LoggedOut,
911
private val onLogin: (API.LoggedIn) -> Unit
1012
) : ViewModel() {
11-
val userName = MutableStateFlow("")
12-
val password = MutableStateFlow("")
13+
data class LoginState(
14+
val userName: String,
15+
val password: String,
16+
val enableLogin: Boolean,
17+
val error: Failure?
18+
)
1319

14-
val error = MutableStateFlow<Failure?>(null)
20+
private var userName by mutableStateOf("")
21+
fun updateUserName(new: String) {
22+
userName = new
23+
}
24+
private var password by mutableStateOf("")
25+
fun updatePassword(new: String) {
26+
password = new
27+
}
28+
private var error by mutableStateOf<Failure?>(null)
29+
fun dismissError() {
30+
error = null
31+
}
32+
33+
fun state(clock: RecompositionClock = RecompositionClock.ContextClock): StateFlow<LoginState> = lifecycleScope.launchMolecule(clock) {
34+
val isError = userName.isNotEmpty() && password.isNotEmpty()
1535

16-
val enableLogin = userName.combine(password) { userName, password ->
17-
userName.isNotEmpty() && password.isNotEmpty()
36+
LoginState(
37+
userName = userName,
38+
password = password,
39+
enableLogin = isError,
40+
error = error
41+
)
1842
}
1943

2044
fun login() {
21-
error.value = null
45+
error = null
2246
lifecycleScope.launch {
2347
api.networkCall(
2448
action = {
25-
login(username = userName.value, password = password.value)
49+
login(username = userName, password = password)
2650
}, onSuccess = {
27-
error.value = null
51+
error = null
2852
onLogin(it)
2953
}
3054
) {
31-
error.value = it
55+
error = it
3256
}
3357
}
3458
}

clients/src/iosArm64Main/kotlin/app/softwork/composetodo/IosContainer.kt renamed to clients/src/darwinMain/kotlin/app/softwork/composetodo/IosContainer.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ import kotlinx.coroutines.flow.*
1616

1717
class IosContainer(
1818
protocol: URLProtocol,
19-
host: String
19+
host: String,
20+
storage: CookiesStorage
2021
) : AppContainer {
2122
private val db = TodoRepository.createDatabase(NativeSqliteDriver(ComposeTodoDB.Schema, "composetodo.db"))
2223

23-
constructor() : this(protocol = URLProtocol.HTTPS, host = "api.todo.softwork.app")
24+
constructor(storage: CookiesStorage) : this(protocol = URLProtocol.HTTPS, host = "api.todo.softwork.app", storage = storage)
2425

2526
override val client: HttpClient = HttpClient(Darwin) {
2627
install(HttpCookies) {
27-
storage = UserDefaultsCookieStorage()
28+
this.storage = storage
2829
}
2930
install(DefaultRequest) {
3031
url {

clients/src/darwinTest/kotlin/app/softwork/composetodo/FlowsTest.kt

Whitespace-only changes.

clients/src/iosArm64Main/kotlin/app/softwork/composetodo/UserDefaultsCookieStorage.kt

-18
This file was deleted.

clients/src/iosArm64Test/kotlin/app/softwork/composetodo/FlowsTest.kt

-51
This file was deleted.

clients/src/jsMain/kotlin/app/softwork/composetodo/viewmodels/ViewModel.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package app.softwork.composetodo.viewmodels
22

3+
import androidx.compose.runtime.*
34
import kotlinx.coroutines.*
45

56
actual abstract class ViewModel actual constructor() {
6-
val lifecycleScope: CoroutineScope = MainScope()
7+
val lifecycleScope: CoroutineScope = MainScope() + DefaultMonotonicFrameClock
78
}
89

910
actual val ViewModel.lifecycleScope: CoroutineScope

composeClients/src/commonMain/kotlin/app/softwork/composetodo/views/Login.kt

+7-12
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,26 @@ import app.softwork.composetodo.viewmodels.*
88

99
@Composable
1010
fun Login(viewModel: LoginViewModel) {
11+
val state by remember(viewModel) { viewModel.state() }.collectAsState()
1112
Column {
12-
val userName by remember { viewModel.userName }.collectAsState()
1313
TextField(
1414
label = "Username",
15-
value = userName,
16-
onValueChange = { viewModel.userName.value = it },
15+
value = state.userName,
16+
onValueChange = viewModel::updateUserName,
1717
isPassword = false,
1818
placeholder = "John Doe"
1919
)
20-
val password by remember { viewModel.password }.collectAsState()
2120
TextField(
2221
label = "Password",
23-
value = password,
24-
onValueChange = { viewModel.password.value = it },
22+
value = state.password,
23+
onValueChange = viewModel::updatePassword,
2524
isPassword = true,
2625
placeholder = ""
2726
)
2827

29-
val enableLogin by remember { viewModel.enableLogin }.collectAsState(false)
28+
Button("Login", enabled = state.enableLogin) { viewModel.login() }
3029

31-
Button("Login", enabled = enableLogin) { viewModel.login() }
32-
33-
val error by remember { viewModel.error }.collectAsState()
34-
35-
error?.let {
30+
state.error?.let {
3631
Text("ERROR: ${it.reason}")
3732
}
3833
}

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ org.gradle.jvmargs=-Xmx2048m
22
org.gradle.parallel=true
33

44
android.useAndroidX=true
5-
android.enableJetifier=true
65
android.nonTransitiveRClass=true
76
android.disableAutomaticComponentCreation=true
87

98
kotlin.code.style=official
109

10+
kotlin.native.cacheKind=none
1111
kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none

iosApp/Shared/AsyncStream.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ struct FlowStream<T>: AsyncSequence {
2121
let iterator: IteratorAsync
2222

2323
func next() async -> T? {
24-
return try! await withTaskCancellationHandler(handler: {
25-
iterator.cancel()
26-
}) {
24+
return try! await withTaskCancellationHandler {
2725
do {
2826
let next = try await iterator.next()
2927
if (next == nil) {
@@ -39,6 +37,8 @@ struct FlowStream<T>: AsyncSequence {
3937
throw error
4038
}
4139
}
40+
} onCancel: {
41+
iterator.cancel()
4242
}
4343
}
4444

0 commit comments

Comments
 (0)