diff --git a/coroutines/compose/build.gradle b/coroutines/compose/build.gradle index 7405e30efa0..898c2da2de9 100644 --- a/coroutines/compose/build.gradle +++ b/coroutines/compose/build.gradle @@ -16,5 +16,19 @@ kotlin { 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 +// } +// } } } diff --git a/coroutines/compose/src/androidMain/AndroidManifest.xml b/coroutines/compose/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cff..00000000000 --- a/coroutines/compose/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt b/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt deleted file mode 100644 index c3729ac32cf..00000000000 --- a/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt +++ /dev/null @@ -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( - initial: T, - internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) : MutableState, - SpecialMutableStateFlow(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 MutableState.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope) diff --git a/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt b/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt new file mode 100644 index 00000000000..2f4ad81e9f0 --- /dev/null +++ b/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt @@ -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() + } +} \ No newline at end of file diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt index ac892987fc9..5114da6db10 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt @@ -18,75 +18,49 @@ import kotlin.coroutines.CoroutineContext * each active subscriber */ open class SpecialMutableStateFlow( - initialValue: T, - internalScope: CoroutineScope + initialValue: T ) : MutableStateFlow, FlowCollector, MutableSharedFlow { @OptIn(InternalCoroutinesApi::class) private val syncObject = SynchronizedObject() - protected val internalSharedFlow: MutableSharedFlow = MutableSharedFlow( - replay = 0, - extraBufferCapacity = 2, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - protected val publicSharedFlow: MutableSharedFlow = MutableSharedFlow( + protected val sharingFlow: MutableSharedFlow = MutableSharedFlow( replay = 1, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST ) - protected var _value: T = initialValue - override var value: T - get() = _value + @OptIn(InternalCoroutinesApi::class) + override var value: T = initialValue 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 - get() = publicSharedFlow.replayCache + get() = sharingFlow.replayCache override val subscriptionCount: StateFlow - 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 { - return synchronized(syncObject) { - if (expect == _value && update != _value) { - 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) - } + if (expect == value) { + value = update } + return expect == value } @ExperimentalCoroutinesApi - override fun resetReplayCache() = publicSharedFlow.resetReplayCache() + override fun resetReplayCache() = sharingFlow.resetReplayCache() override fun tryEmit(value: T): Boolean { - return internalSharedFlow.tryEmit(value) + return compareAndSet(this.value, value) } override suspend fun emit(value: T) { - internalSharedFlow.emit(value) + compareAndSet(this.value, value) } - override suspend fun collect(collector: FlowCollector) = publicSharedFlow.collect(collector) + override suspend fun collect(collector: FlowCollector) = sharingFlow.collect(collector) } diff --git a/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt b/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt index 36501bdf2e7..8e3aa105621 100644 --- a/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt +++ b/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt @@ -1,35 +1,33 @@ import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow +import dev.inmo.micro_utils.coroutines.asDeferred import dev.inmo.micro_utils.coroutines.subscribe import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class SpecialMutableStateFlowTests { @Test - fun simpleTest() { + fun simpleTest() = runTest { val specialMutableStateFlow = SpecialMutableStateFlow(0) - runTest { - specialMutableStateFlow.value = 1 - specialMutableStateFlow.first { it == 1 } - } + specialMutableStateFlow.value = 1 + specialMutableStateFlow.first { it == 1 } assertEquals(1, specialMutableStateFlow.value) } @Test - fun specialTest() { + fun specialTest() = runTest { val specialMutableStateFlow = SpecialMutableStateFlow(0) - runTest { - lateinit var subscriberJob: Job - subscriberJob = specialMutableStateFlow.subscribe(this) { - when (it) { - 1 -> specialMutableStateFlow.value = 2 - 2 -> subscriberJob.cancel() - } + lateinit var subscriberJob: Job + subscriberJob = specialMutableStateFlow.subscribe(this) { + when (it) { + 1 -> specialMutableStateFlow.value = 2 + 2 -> subscriberJob.cancel() } - specialMutableStateFlow.value = 1 - subscriberJob.join() } + specialMutableStateFlow.value = 1 + subscriberJob.join() assertEquals(2, specialMutableStateFlow.value) } } \ No newline at end of file diff --git a/mppProjectWithSerializationAndCompose.gradle b/mppProjectWithSerializationAndCompose.gradle index a9be6b2cd7c..31bc1e0a5dc 100644 --- a/mppProjectWithSerializationAndCompose.gradle +++ b/mppProjectWithSerializationAndCompose.gradle @@ -28,8 +28,8 @@ kotlin { commonMain { dependencies { implementation kotlin('stdlib') - api libs.kt.serialization implementation compose.runtime + api libs.kt.serialization } } commonTest { @@ -47,6 +47,7 @@ kotlin { jvmTest { dependencies { implementation kotlin('test-junit') + implementation compose.uiTest } } jsMain { @@ -61,6 +62,14 @@ kotlin { } } androidUnitTest { + dependencies { + implementation kotlin('test-junit') + implementation libs.android.test.junit + implementation libs.android.espresso + implementation compose.uiTest + } + } + androidInstrumentedTest { dependencies { implementation kotlin('test-junit') implementation libs.android.test.junit