Skip to content

Commit

Permalink
Merge pull request #21 from respawn-app/2.0.0-beta03
Browse files Browse the repository at this point in the history
2.0.0-beta03
  • Loading branch information
Nek-12 authored Aug 21, 2023
2 parents e6ae555 + 49a5bdf commit 5636ce5
Show file tree
Hide file tree
Showing 26 changed files with 147 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import pro.respawn.flowmvi.android.subscribe
import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.IntentReceiver
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
Expand All @@ -28,24 +28,12 @@ import kotlin.experimental.ExperimentalTypeInference
* An interface for the scope that provides magic [send] and [consume] functions inside your composable
*/
@Stable
public interface ConsumerScope<in I : MVIIntent, out A : MVIAction> {

/**
* Send a new intent for the store you used in [MVIComposable]
* @see pro.respawn.flowmvi.api.IntentReceiver.send
*/
public fun send(intent: I)

@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("sendAction")
/**
* @see send
*/
public fun I.send(): Unit = send(this)
public interface ConsumerScope<in I : MVIIntent, out A : MVIAction> : IntentReceiver<I> {

/**
* Collect [MVIAction]s that come from the [Store].
* Should only be called once per screen.
* Even if you do not have any Actions in your store you still **must** call this function to subscribe to the store
*/
@Composable
public fun consume(onAction: suspend CoroutineScope.(action: A) -> Unit)
Expand All @@ -55,39 +43,27 @@ public interface ConsumerScope<in I : MVIIntent, out A : MVIAction> {
internal fun <S : MVIState, I : MVIIntent, A : MVIAction> rememberConsumerScope(
store: Store<S, I, A>,
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
): ConsumerScopeImpl<S, I, A> {
val scope = remember(store) { ConsumerScopeImpl(store) }
scope.collect(lifecycleState)
return scope
}
): ConsumerScopeImpl<S, I, A> = remember(store) { ConsumerScopeImpl(store, lifecycleState) }

@Stable
internal class ConsumerScopeImpl<S : MVIState, in I : MVIIntent, A : MVIAction>(
internal data class ConsumerScopeImpl<S : MVIState, in I : MVIIntent, A : MVIAction>(
private val store: Store<S, I, A>,
private val lifecycleState: Lifecycle.State = Lifecycle.State.STARTED
) : ConsumerScope<I, A> {

internal val state = mutableStateOf(store.initial)
private val _actions = Channel<A>(Channel.UNLIMITED)

override fun send(intent: I) = store.send(intent)
override fun hashCode(): Int = store.hashCode()
override fun equals(other: Any?) = store == other
override suspend fun emit(intent: I) = store.emit(intent)

@Composable
override fun consume(onAction: suspend CoroutineScope.(action: A) -> Unit) {
LaunchedEffect(Unit) {
for (it in _actions) onAction(it)
}
}

@Composable
fun collect(lifecycleState: Lifecycle.State = Lifecycle.State.STARTED) {
val owner = LocalLifecycleOwner.current
LaunchedEffect(owner, lifecycleState) {
LaunchedEffect(this) {
owner.subscribe(
lifecycleState = lifecycleState,
store = store,
consume = { _actions.send(it) },
consume = { onAction(it) },
render = { state.value = it }
)
}
Expand All @@ -106,6 +82,7 @@ public fun <I : MVIIntent, A : MVIAction> EmptyScope(
): Unit = call(
object : ConsumerScope<I, A> {
override fun send(intent: I) = Unit
override suspend fun emit(intent: I) = Unit

@Composable
override fun consume(onAction: suspend CoroutineScope.(action: A) -> Unit) = Unit
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Job
import pro.respawn.flowmvi.android.subscribe
import pro.respawn.flowmvi.api.Consumer
import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
Expand Down Expand Up @@ -42,7 +43,7 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction> Fragment.subscribe(
@FlowMVIDSL
public fun <S : MVIState, I : MVIIntent, A : MVIAction, T> T.subscribe(
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
): Job where T : Fragment, T : MVIView<S, I, A> =
): Job where T : Fragment, T : Consumer<S, I, A> =
viewLifecycleOwner.subscribe(container.store, ::consume, ::render, lifecycleState)

/**
Expand All @@ -53,4 +54,4 @@ public fun <S : MVIState, I : MVIIntent, A : MVIAction, T> T.subscribe(
@FlowMVIDSL
public fun <S : MVIState, I : MVIIntent, A : MVIAction, T> T.subscribe(
lifecycleState: Lifecycle.State = Lifecycle.State.STARTED,
): Job where T : LifecycleOwner, T : MVIView<S, I, A> = subscribe(container.store, lifecycleState)
): Job where T : LifecycleOwner, T : Consumer<S, I, A> = subscribe(container.store, lifecycleState)
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public abstract class MVIViewModel<S : MVIState, I : MVIIntent, A : MVIAction>(
/**
* @see MVIStore.withState
*/
override suspend fun <R> withState(block: suspend S.() -> R): R = store.withState(block)
override suspend fun withState(block: suspend S.() -> Unit): Unit = store.withState(block)

/**
* @see launchRecovering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ sealed interface CounterIntent : MVIIntent {

data object ClickedCounter : CounterIntent
data object ClickedUndo : CounterIntent
data object ClickedBack : CounterIntent
}

@Immutable
sealed interface CounterAction : MVIAction {

data object ShowErrorMessage : CounterAction
data object ShowLambdaMessage : CounterAction
data object GoBack : CounterAction
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ComposeActivity : ComponentActivity() {

setContent {
MVISampleTheme {
ComposeScreen()
ComposeScreen { finish() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import pro.respawn.flowmvi.android.compose.EmptyScope
import pro.respawn.flowmvi.android.compose.MVIComposable
import pro.respawn.flowmvi.android.compose.StateProvider
import pro.respawn.flowmvi.sample.CounterAction
import pro.respawn.flowmvi.sample.CounterAction.GoBack
import pro.respawn.flowmvi.sample.CounterAction.ShowErrorMessage
import pro.respawn.flowmvi.sample.CounterAction.ShowLambdaMessage
import pro.respawn.flowmvi.sample.CounterIntent
import pro.respawn.flowmvi.sample.CounterIntent.ClickedBack
import pro.respawn.flowmvi.sample.CounterIntent.ClickedCounter
import pro.respawn.flowmvi.sample.CounterIntent.ClickedUndo
import pro.respawn.flowmvi.sample.CounterState
Expand All @@ -46,7 +48,7 @@ fun ScaffoldState.snackbar(text: String) = launch { snackbarHostState.showSnackb

@Composable
@Suppress("ComposableFunctionName")
fun ComposeScreen() = MVIComposable(
fun ComposeScreen(onBack: () -> Unit) = MVIComposable(
storeViewModel<CounterContainer, _, _, _> { parametersOf("I am a parameter") }
) { state: CounterState -> // this -> ConsumerScope

Expand All @@ -55,11 +57,12 @@ fun ComposeScreen() = MVIComposable(

consume { action ->
// This block is run in a new coroutine each time we consume a new action and the lifecycle is RESUMED.
// You can run suspending (but not blocking) code here safely
// You can run suspending code here safely
// consume() block will only be called when a new action is emitted (independent of recompositions)
when (action) {
is ShowLambdaMessage -> scaffoldState.snackbar(context.getString(R.string.lambda_message))
is ShowErrorMessage -> scaffoldState.snackbar(context.getString(R.string.error_message))
is GoBack -> onBack()
}
}

Expand All @@ -85,21 +88,23 @@ private fun Scope.ComposeScreenContent(
) {
Text(
text = stringResource(id = R.string.timer_template, state.timer),
// send() is available in ConsumerScope
modifier = Modifier.clickable { send(ClickedCounter) }
modifier = Modifier.clickable { intent(ClickedCounter) }
)
Text(
text = stringResource(id = R.string.counter_template, state.counter),
)

Text(text = state.param)

Button(onClick = { send(ClickedCounter) }) {
Button(onClick = { intent(ClickedCounter) }) {
Text(text = stringResource(id = R.string.counter_button_label))
}
Button(onClick = { send(ClickedUndo) }) {
Button(onClick = { intent(ClickedUndo) }) {
Text(text = stringResource(id = R.string.counter_undo_label))
}
Button(onClick = { intent(ClickedBack) }) {
Text(text = stringResource(id = R.string.counter_back_label))
}
}
is CounterState.Loading -> CircularProgressIndicator()
is CounterState.Error -> Text(state.e.message.toString())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package pro.respawn.flowmvi.sample.compose

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import pro.respawn.flowmvi.api.Container
Expand All @@ -16,8 +15,10 @@ import pro.respawn.flowmvi.plugins.register
import pro.respawn.flowmvi.plugins.undoRedo
import pro.respawn.flowmvi.plugins.whileSubscribed
import pro.respawn.flowmvi.sample.CounterAction
import pro.respawn.flowmvi.sample.CounterAction.GoBack
import pro.respawn.flowmvi.sample.CounterAction.ShowErrorMessage
import pro.respawn.flowmvi.sample.CounterIntent
import pro.respawn.flowmvi.sample.CounterIntent.ClickedBack
import pro.respawn.flowmvi.sample.CounterIntent.ClickedCounter
import pro.respawn.flowmvi.sample.CounterIntent.ClickedUndo
import pro.respawn.flowmvi.sample.CounterState
Expand Down Expand Up @@ -47,8 +48,7 @@ class CounterContainer(
}
reduce {
when (it) {
is ClickedCounter -> {
delay(1000)
is ClickedCounter -> launch {
require(Random.nextBoolean()) { "Oops, there was an error in a job" }
undoRedo(
redo = {
Expand All @@ -64,6 +64,7 @@ class CounterContainer(
)
}
is ClickedUndo -> undoRedo.undo()
is ClickedBack -> action(GoBack)
}
}
recover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import androidx.core.view.isVisible
import com.google.android.material.snackbar.Snackbar
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import pro.respawn.flowmvi.android.view.MVIView
import pro.respawn.flowmvi.android.view.subscribe
import pro.respawn.flowmvi.api.Consumer
import pro.respawn.flowmvi.sample.CounterAction
import pro.respawn.flowmvi.sample.CounterAction.ShowErrorMessage
import pro.respawn.flowmvi.sample.CounterAction.ShowLambdaMessage
Expand All @@ -20,7 +20,7 @@ import pro.respawn.flowmvi.sample.R
import pro.respawn.flowmvi.sample.compose.ComposeActivity
import pro.respawn.flowmvi.sample.databinding.ActivityBasicBinding

class CounterActivity : ComponentActivity(), MVIView<CounterState, CounterLambdaIntent, CounterAction> {
class CounterActivity : ComponentActivity(), Consumer<CounterState, CounterLambdaIntent, CounterAction> {

private var _b: ActivityBasicBinding? = null
private val binding get() = requireNotNull(_b)
Expand Down Expand Up @@ -49,6 +49,7 @@ class CounterActivity : ComponentActivity(), MVIView<CounterState, CounterLambda
when (action) {
is ShowErrorMessage -> Snackbar.make(binding.root, R.string.error_message, Snackbar.LENGTH_SHORT).show()
is ShowLambdaMessage -> Snackbar.make(binding.root, R.string.lambda_message, Snackbar.LENGTH_SHORT).show()
is CounterAction.GoBack -> finish()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import kotlinx.coroutines.flow.onEach
import pro.respawn.flowmvi.android.plugins.parcelizeState
import pro.respawn.flowmvi.api.Container
import pro.respawn.flowmvi.api.PipelineContext
import pro.respawn.flowmvi.dsl.intent
import pro.respawn.flowmvi.dsl.lazyStore
import pro.respawn.flowmvi.dsl.reduceLambdas
import pro.respawn.flowmvi.dsl.send
import pro.respawn.flowmvi.dsl.updateState
import pro.respawn.flowmvi.plugins.platformLoggingPlugin
import pro.respawn.flowmvi.plugins.whileSubscribed
Expand Down Expand Up @@ -53,7 +53,7 @@ class LambdaViewModel(
DisplayingCounter(timer, current?.counter ?: 0, param)
}

fun onClickCounter() = store.send {
fun onClickCounter() = store.intent {
action(ShowLambdaMessage)
updateState<DisplayingCounter, _> {
copy(counter = counter + 1)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
<string name="empty_message">Oops! Nothing here</string>
<string name="to_compose_button_label">To compose screen</string>
<string name="counter_undo_label">Undo</string>
<string name="counter_back_label">Back</string>
</resources>
10 changes: 0 additions & 10 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,6 @@ subprojects {
showExceptions = true
showCauses = true
showStackTraces = true
addTestListener(object : TestListener {
override fun beforeSuite(suite: TestDescriptor?) = Unit

override fun afterSuite(suite: TestDescriptor?, result: TestResult?) =
logger.info("${result?.testCount} tests completed")

override fun beforeTest(testDescriptor: TestDescriptor?) = Unit

override fun afterTest(testDescriptor: TestDescriptor?, result: TestResult?) = Unit
})
}
}
withType<AbstractDokkaTask> {
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object Config {
const val majorRelease = 2
const val minorRelease = 0
const val patch = 0
const val postfix = "beta02"
const val postfix = "beta03"
const val versionName = "$majorRelease.$minorRelease.$patch-$postfix"

// kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ plugins {
signing
}

kotlin {
explicitApi()
}

android {
configureAndroidLibrary(this)
publishAndroid(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ public interface MVIStore<S : MVIState, I : MVIIntent, A : MVIAction> :
* @See MVISubscriber
*/
@Deprecated(
"This interface has been moved to the android module",
ReplaceWith("pro.respawn.flowmvi.android.view.MVIView<S, I, A>")
"Renamed to Consumer",
ReplaceWith("Consumer<S, I, A>")
)
public interface MVIView<S : MVIState, I : MVIIntent, A : MVIAction> : MVISubscriber<S, A> {

Expand Down
Loading

0 comments on commit 5636ce5

Please sign in to comment.