complete rework of states

This commit is contained in:
InsanusMokrassar 2024-05-12 21:34:17 +06:00
parent a1854b68d8
commit 4901a8844c
7 changed files with 80 additions and 108 deletions

View File

@ -16,5 +16,19 @@ kotlin {
api project(":micro_utils.common.compose") api project(":micro_utils.common.compose")
} }
} }
// androidUnitTest {
// dependencies {
// implementation libs.ui.test.junit4
// implementation libs.ui.test.manifest
// implementation libs.android.compose.material3
// }
// }
// jvmTest {
// dependencies {
// implementation libs.ui.test.junit4
// implementation libs.ui.test.manifest
// implementation libs.android.compose.material3
// }
// }
} }
} }

View File

@ -1 +0,0 @@
<manifest/>

View File

@ -1,46 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow]
*/
@Deprecated("Will be removed soon")
class FlowState<T>(
initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableState<T>,
SpecialMutableStateFlow<T>(initial, internalScope) {
private var internalValue: T = initial
override var value: T
get() = internalValue
set(value) {
internalValue = value
tryEmit(value)
}
override fun onChangeWithoutSync(value: T) {
internalValue = value
super.onChangeWithoutSync(value)
}
override fun component1(): T = value
override fun component2(): (T) -> Unit = { tryEmit(it) }
override fun tryEmit(value: T): Boolean {
internalValue = value
return super.tryEmit(value)
}
override suspend fun emit(value: T) {
internalValue = value
super.emit(value)
}
}
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)

View File

@ -0,0 +1,24 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.test.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
class FlowStateTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun simpleTest() = runComposeUiTest {
val flowState = SpecialMutableStateFlow(0)
setContent {
Button({ flowState.value++ }) { Text("Click") }
Text(flowState.collectAsState().value.toString())
}
onNodeWithText(0.toString()).assertExists()
onNodeWithText("Click").performClick()
onNodeWithText(1.toString()).assertExists()
}
}

View File

@ -18,75 +18,49 @@ import kotlin.coroutines.CoroutineContext
* each active subscriber * each active subscriber
*/ */
open class SpecialMutableStateFlow<T>( open class SpecialMutableStateFlow<T>(
initialValue: T, initialValue: T
internalScope: CoroutineScope
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class) @OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject() private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow( protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1, replay = 1,
extraBufferCapacity = 1, extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST onBufferOverflow = BufferOverflow.DROP_OLDEST
) )
protected var _value: T = initialValue @OptIn(InternalCoroutinesApi::class)
override var value: T override var value: T = initialValue
get() = _value
set(value) { set(value) {
internalSharedFlow.tryEmit(value) synchronized(syncObject) {
if (field != value) {
field = value
sharingFlow.tryEmit(value)
}
}
} }
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
}
override val replayCache: List<T> override val replayCache: List<T>
get() = publicSharedFlow.replayCache get() = sharingFlow.replayCache
override val subscriptionCount: StateFlow<Int> override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount get() = sharingFlow.subscriptionCount
constructor(
initialValue: T,
internalContext: CoroutineContext = Dispatchers.Default
) : this(initialValue, CoroutineScope(internalContext))
@OptIn(InternalCoroutinesApi::class)
override fun compareAndSet(expect: T, update: T): Boolean { override fun compareAndSet(expect: T, update: T): Boolean {
return synchronized(syncObject) { if (expect == value) {
if (expect == _value && update != _value) { value = update
doOnChangeAction(update)
}
expect == _value
}
}
protected open fun onChangeWithoutSync(value: T) {
_value = value
publicSharedFlow.tryEmit(value)
}
@OptIn(InternalCoroutinesApi::class)
protected open fun doOnChangeAction(value: T) {
synchronized(syncObject) {
if (_value != value) {
onChangeWithoutSync(value)
}
} }
return expect == value
} }
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache() override fun resetReplayCache() = sharingFlow.resetReplayCache()
override fun tryEmit(value: T): Boolean { override fun tryEmit(value: T): Boolean {
return internalSharedFlow.tryEmit(value) return compareAndSet(this.value, value)
} }
override suspend fun emit(value: T) { override suspend fun emit(value: T) {
internalSharedFlow.emit(value) compareAndSet(this.value, value)
} }
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector) override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
} }

View File

@ -1,35 +1,33 @@
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.subscribe import dev.inmo.micro_utils.coroutines.subscribe
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SpecialMutableStateFlowTests { class SpecialMutableStateFlowTests {
@Test @Test
fun simpleTest() { fun simpleTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0) val specialMutableStateFlow = SpecialMutableStateFlow(0)
runTest { specialMutableStateFlow.value = 1
specialMutableStateFlow.value = 1 specialMutableStateFlow.first { it == 1 }
specialMutableStateFlow.first { it == 1 }
}
assertEquals(1, specialMutableStateFlow.value) assertEquals(1, specialMutableStateFlow.value)
} }
@Test @Test
fun specialTest() { fun specialTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0) val specialMutableStateFlow = SpecialMutableStateFlow(0)
runTest { lateinit var subscriberJob: Job
lateinit var subscriberJob: Job subscriberJob = specialMutableStateFlow.subscribe(this) {
subscriberJob = specialMutableStateFlow.subscribe(this) { when (it) {
when (it) { 1 -> specialMutableStateFlow.value = 2
1 -> specialMutableStateFlow.value = 2 2 -> subscriberJob.cancel()
2 -> subscriberJob.cancel()
}
} }
specialMutableStateFlow.value = 1
subscriberJob.join()
} }
specialMutableStateFlow.value = 1
subscriberJob.join()
assertEquals(2, specialMutableStateFlow.value) assertEquals(2, specialMutableStateFlow.value)
} }
} }

View File

@ -28,8 +28,8 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
api libs.kt.serialization
implementation compose.runtime implementation compose.runtime
api libs.kt.serialization
} }
} }
commonTest { commonTest {
@ -47,6 +47,7 @@ kotlin {
jvmTest { jvmTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation compose.uiTest
} }
} }
jsMain { jsMain {
@ -61,6 +62,14 @@ kotlin {
} }
} }
androidUnitTest { androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidInstrumentedTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit