diff --git a/README.md b/README.md
index 33fb18e4..8cf06394 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
FlowMVI is a Kotlin Multiplatform architectural framework based on coroutines.
It enables you to extend your business logic with reusable plugins, handle errors,
-achieve thread-safety, and more. It takes about 10 minutes to try it.
+achieve thread-safety, and more. It takes about 10 minutes and 50 lines of code to get started.
## Quickstart:
@@ -90,61 +90,80 @@ Instead, this library focuses on building a supporting infrastructure to enable
Here's what you get:
* Powerful Plug-In system to automate processes and **reuse any business logic** you desire
- * Create automatic analytics handlers, websocket connections, error handling mechanisms, or anything else once and
- reuse them throughout your whole project automatically
-* Automatically **recover from any errors** and prevent crashes
+* Automatically **recover from any errors** and report them to analytics.
* Build fully **async, reactive and parallel apps** - with no manual thread synchronization required!
-* Create business logic components with pluggable UI using **0 platform code**
+* Create **multiplatform business logic** components with pluggable UI
* Automatic multiplatform system **lifecycle handling**
-* Out of the box **debugging, logging, testing, undo/redo, caching and long-running tasks** support
-* Debounce, retry, batch, throttle, conflate any operations automatically.
+* Out of the box **debugging, logging, caching and long-running tasks** support
+* Debounce, retry, batch, throttle, conflate, monitor, **modify any operations** automatically
* **Compress, persist, and restore state** automatically on any platform
-* No base classes, complicated interfaces, or factories of factories - logic is **declarative and built with a DSL**
-* Restartable, reusable business logic components with no external dependencies or dedicated lifecycles.
-* Create compile-time safe state machines with a readable DSL. Forget about casts, inconsistent states, and `null`s
-* First class Compose Multiplatform support optimized for performance and ease of use
-* Use both MVVM+ (functional) or MVI (model-driven) style of programming
-* Share, distribute, or disable side-effects based on your team's needs
-* Dedicated remote debugger and code generation IDEA/AS plugin and app for Windows, Linux, MacOS
-* Integration with popular libraries, such as [Decompose (Essenty)](https://github.com/arkivanov/Decompose)
-* The core library depends on kotlin coroutines. Nothing else
-* Core library is fully covered by tests
-* Minimal performance overhead, equal to using a simple Channel, with regular benchmarking
+* **No base classes, complicated interfaces**, or factories of factories - logic is declarative and built with a DSL
+* Build **Restartable, reusable business logic components** with no external dependencies or dedicated lifecycles
+* Create **compile-time safe state machines** with a readable DSL. Forget about casts, inconsistent states, and `null`s
+* First class **Compose Multiplatform support** optimized for performance and ease of use
+* Use both **MVVM+** (functional) or **MVI** (model-driven) style of programming
+* Share, distribute, disable, **manage side-effects** based on your team's needs
+* Dedicated **IDE Plugin for debugging and codegen** and app for Windows, Linux, MacOS
+* **Integration with popular libraries**, such as [Decompose (Essenty)](https://github.com/arkivanov/Decompose)
+* The core **library has no dependencies** except kotlin coroutines.
+* Core library is fully covered by **hundreds of tests**
+* **Minimal performance overhead**, equal to using a simple Channel, with regular benchmarking
+* Collect, monitor and report **performance metrics** automatically (upcoming).
+* **Test any business logic** using clean, declarative DSL.
* Learn more by exploring the [sample app](https://opensource.respawn.pro/FlowMVI/sample/) in your browser
## How does it look?
-
-Define a contract
+It is insanely **easy to get started**. All you have to do is:
+
+### 1. Define a contract:
```kotlin
-sealed interface CounterState : MVIState {
- data object Loading : CounterState
- data class Error(val e: Exception) : CounterState
-
- @Serializable
- data class DisplayingCounter(
- val timer: Int,
- val counter: Int,
- ) : CounterState
+data class State(
+ val counter: Int = 0,
+) : MVIState
+
+sealed interface Intent : MVIIntent {
+ data object ClickedCounter : Intent
}
-sealed interface CounterIntent : MVIIntent {
- data object ClickedCounter : CounterIntent
+// or use mvvm+ style
+typealias IntentIntent = LambdaIntent
+
+sealed interface Action : MVIAction {
+ data class ShowMessage(val message: String) : Action
}
+```
+
+### 2. Declare your business logic:
-sealed interface CounterAction : MVIAction {
- data class ShowMessage(val message: String) : CounterAction
+```kotlin
+val store = store<_, _>(initial = State(), scope = coroutineScope) {
+
+ reduce { intent ->
+ when (intent) {
+ is ClickedCounter -> updateState {
+ action(ShowMessage("Added to counter!"))
+
+ copy(counter = counter + 1)
+ }
+ }
+ }
}
+
+store.intent(ClickedCounter)
+
```
-
+Want to have advanced configuration with tons of features, persistent state, or interceptors?
+No problem, your logic's complexity now scales **linearly**. Adding a new feature is as simple as calling a function.
-Then define your business logic:
+
+Example advanced configuration
```kotlin
class CounterContainer(
- private val repo: CounterRepository,
+ private val repo: CounterRepository, // inject dependencies
) {
val store = store(initial = Loading) {
@@ -152,39 +171,40 @@ class CounterContainer(
actionShareBehavior = ActionShareBehavior.Distribute()
debuggable = true
- // makes the store fully async, parallel and thread-safe
+ // make the store fully async, parallel and thread-safe
parallelIntents = true
coroutineContext = Dispatchers.Default
atomicStateUpdates = true
}
+ // out of the box logging and IDE debugging
enableLogging()
enableRemoteDebugging()
- // allows to undo any operation
+ // undo / redo any operation
val undoRedo = undoRedo()
- // manages long-running jobs
+ // manage long-running jobs
val jobManager = manageJobs()
- // saves and restores the state automatically
+ // save and restore the state automatically
serializeState(
path = repo.cacheFile("counter"),
serializer = DisplayingCounter.serializer(),
)
- // performs long-running tasks on startup
+ // perform long-running tasks on startup
init {
repo.startTimer()
}
- // handles any errors
+ // handle any errors
recover { e: Exception ->
action(ShowMessage(e.message))
null
}
- // saves resources when there are no subscribers
+ // observe streams and save resources when there are no subscribers
whileSubscribed {
repo.timer.collect {
updateState {
@@ -193,7 +213,7 @@ class CounterContainer(
}
}
- // lazily evaluates and caches values, even when the method is suspending.
+ // lazily evaluate and cache values, even when the method is suspending.
val pagingData by cache {
repo.getPagedDataSuspending()
}
@@ -201,21 +221,26 @@ class CounterContainer(
// testable reducer as a function
reduce { intent: CounterIntent ->
when (intent) {
+ // typed state update prevents races and allows using sealed class hierarchies for LCE
is ClickedCounter -> updateState {
copy(counter = counter + 1)
}
}
}
- // builds custom plugins on the fly
+ // build custom plugins on the fly
install {
onStop { repo.stopTimer() }
}
+
+ // and 50+ more options to choose from...
}
}
```
-### Extend your logic with plugins!
+
+
+### 3. Extend your logic with plugins!
Powerful DSL allows you to hook into various events and amend any part of your logic:
@@ -230,12 +255,6 @@ fun analyticsPlugin(analytics: Analytics) = plugin
analytics.logError(e)
}
- onSubscribe {
- analytics.logEngagementStart()
- }
- onUnsubscribe {
- analytics.logEngagementEnd()
- }
onStop {
analytics.logScreenLeave()
}
@@ -244,7 +263,7 @@ fun analyticsPlugin(analytics: Analytics) = plugin store(
}
}
+/**
+ * * Build a new [Store] using [StoreBuilder] but disallow using [MVIAction]s.
+ * The store is **not** launched, but is created eagerly, with all its plugins.
+ *
+ * If your code doesn't compile, you are looking for another overload with three type parameters, i.e:
+ * `store<_, _, _>()`
+ */
+@FlowMVIDSL
+@JvmName("noActionStore")
+// https://youtrack.jetbrains.com/issue/KT-16255
+@Suppress(
+ "INVISIBLE_MEMBER",
+ "INVISIBLE_REFERENCE",
+)
+@kotlin.internal.LowPriorityInOverloadResolution
+public inline fun store(
+ initial: S,
+ scope: CoroutineScope,
+ @BuilderInference configure: BuildStore,
+): Store = store(initial, scope) {
+ configure()
+ configure {
+ actionShareBehavior = ActionShareBehavior.Disabled
+ }
+}
+
/**
* Build a new [Store] using [StoreBuilder].
* The store is created lazily, with all its plugins.
@@ -80,3 +107,6 @@ public inline fun lazyStore(
mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED,
@BuilderInference crossinline configure: BuildStore,
): Lazy> = lazy(mode) { store(initial, configure).apply { start(scope) } }
+
+public inline val Store.immutable: ImmutableStore
+ get() = this
diff --git a/core/src/commonMain/kotlin/pro/respawn/flowmvi/util/CappedMutableList.kt b/core/src/commonMain/kotlin/pro/respawn/flowmvi/util/CappedMutableList.kt
index f69ceabd..a01b941f 100644
--- a/core/src/commonMain/kotlin/pro/respawn/flowmvi/util/CappedMutableList.kt
+++ b/core/src/commonMain/kotlin/pro/respawn/flowmvi/util/CappedMutableList.kt
@@ -35,8 +35,7 @@ internal class CappedMutableList(
}
private fun removeOverflowing() {
- while (size > maxSize) {
- removeFirst()
- }
+ // do not use removeFirst because of https://jakewharton.com/kotlins-jdk-release-compatibility-flag/
+ while (size > maxSize) removeAt(0)
}
}
diff --git a/debugger/ideplugin/build.gradle.kts b/debugger/ideplugin/build.gradle.kts
index 899296d5..16ae1d52 100644
--- a/debugger/ideplugin/build.gradle.kts
+++ b/debugger/ideplugin/build.gradle.kts
@@ -41,7 +41,7 @@ intellijPlatform {
pluginVerification {
ides {
props["plugin.local.ide.path"]?.toString()?.let(::local) ?: ide(
- IntelliJPlatformType.AndroidStudio,
+ IntelliJPlatformType.IntellijIdeaCommunity,
libs.versions.intellij.idea.get()
)
}
diff --git a/docs/plugins/prebuilt.md b/docs/plugins/prebuilt.md
index 17639011..a717baa8 100644
--- a/docs/plugins/prebuilt.md
+++ b/docs/plugins/prebuilt.md
@@ -1,5 +1,15 @@
# Getting started with plugins
+FlowMVI is built entirely based on Plugins!
+Plugins form a chain of responsibility (called _Pipeline_) and
+execute _in the order they were installed_ into the Store.
+This allows you to assemble business logic like a lego by placing the "bricks" in the order you want, and transparently
+inject some logic into any store at any point.
+
+Here's how the Plugin chain works:
+
+![](../images/chart.png)
+
## Plugin Ordering
!> The order of plugins matters! Changing the order of plugins may completely change how your store works.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 33e0fa9c..89da8527 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -14,8 +14,6 @@ First of all, here's how the library works:
----
-![](images/chart.png)
-
## Step 1: Configure the library
### 1.1: Add dependencies