Skip to content

Commit

Permalink
3.1.0-beta03 (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nek-12 authored Nov 27, 2024
2 parents 8597974 + 9764261 commit e80b879
Show file tree
Hide file tree
Showing 99 changed files with 2,828 additions and 670 deletions.
26 changes: 14 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinMultiplatform
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import com.vanniktech.maven.publish.MavenPublishBasePlugin
import com.vanniktech.maven.publish.SonatypeHost
import nl.littlerobots.vcu.plugin.versionCatalogUpdate
import nl.littlerobots.vcu.plugin.versionSelector
Expand All @@ -13,7 +14,7 @@ import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension

plugins {
alias(libs.plugins.detekt)
alias(libs.plugins.gradleDoctor)
// alias(libs.plugins.gradleDoctor)
alias(libs.plugins.version.catalog.update)
alias(libs.plugins.atomicfu)
// alias(libs.plugins.dependencyAnalysis)
Expand All @@ -36,16 +37,16 @@ subprojects {
plugins.withType<ComposeCompilerGradleSubplugin>().configureEach {
the<ComposeCompilerGradlePluginExtension>().apply {
featureFlags.addAll(ComposeFeatureFlag.OptimizeNonSkippingGroups)
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_definitions.txt")
stabilityConfigurationFiles.add(rootProject.layout.projectDirectory.file("stability_definitions.txt"))
if (properties["enableComposeCompilerReports"] == "true") {
val metricsDir = layout.buildDirectory.dir("compose_metrics")
metricsDestination = metricsDir
reportsDestination = metricsDir
}
}
}
afterEvaluate {
extensions.findByType<MavenPublishBaseExtension>()?.run {
plugins.withType<MavenPublishBasePlugin> {
the<MavenPublishBaseExtension>().apply {
val isReleaseBuild = properties["release"]?.toString().toBoolean()
configure(
KotlinMultiplatform(
Expand Down Expand Up @@ -91,14 +92,15 @@ subprojects {
}
}

doctor {
warnWhenJetifierEnabled = true
warnWhenNotUsingParallelGC = true
disallowMultipleDaemons = false
javaHome {
ensureJavaHomeMatches.set(false)
}
}
// TODO: Incompatible with gradle isolated projects
// doctor {
// warnWhenJetifierEnabled = true
// warnWhenNotUsingParallelGC = true
// disallowMultipleDaemons = false
// javaHome {
// ensureJavaHomeMatches.set(false)
// }
// }
//
// dependencyAnalysis {
// structure {
Expand Down
84 changes: 61 additions & 23 deletions buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
"MemberVisibilityCanBePrivate",
"MissingPackageDeclaration",
"UndocumentedPublicClass",
"UndocumentedPublicProperty"
"UndocumentedPublicProperty",
"MaxLineLength"
)

import org.gradle.api.JavaVersion
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

object Config {
Expand All @@ -15,11 +17,12 @@ object Config {

const val artifactId = "$group.$artifact"

const val versionCode = 8
const val majorRelease = 3
const val minorRelease = 1
const val patch = 0
const val postfix = "-beta02" // include dash (-)
const val postfix = "-beta03" // include dash (-)

const val versionCode = 8
const val majorVersionName = "$majorRelease.$minorRelease.$patch"
const val versionName = "$majorVersionName$postfix"
const val url = "https://github.com/respawn-app/FlowMVI"
Expand All @@ -29,29 +32,14 @@ object Config {
const val licenseUrl = "https://www.apache.org/licenses/LICENSE-2.0.txt"
const val scmUrl = "https://github.com/respawn-app/FlowMVI.git"
const val docsUrl = "https://opensource.respawn.pro/FlowMVI/#/"
const val description = """A Kotlin Multiplatform MVI library based on coroutines with a powerful plugin system"""
const val description =
"""A Kotlin Multiplatform architecture framework based on coroutines with a powerful plugin system."""
const val supportEmail = "hello@respawn.pro"
const val vendorName = "Respawn Open Source Team"
const val vendorId = "respawn-app"
const val name = "FlowMVI"

object Debugger {

const val namespace = "${Config.namespace}.debugger"
const val appDescription = "A debugger tool for FlowMVI - $description"
const val name = "FlowMVI Debugger"
const val appId = "fd36c0cc-ae50-4aad-8579-f37e1e8af99c"
}

object Sample {

const val namespace = "${Config.namespace}.sample"
const val appDescription = "Sample app for FlowMVI - $description"
const val name = "FlowMVI Sample"
const val appId = "a7f6783f-2bb5-433d-9e5c-9f608ddd42d5"
}
// kotlin

// endregion
val optIns = listOf(
"kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.coroutines.FlowPreview",
Expand All @@ -64,7 +52,10 @@ object Config {
"-Xbackend-threads=0", // parallel IR compilation
"-Xexpect-actual-classes",
"-Xwasm-use-new-exception-proposal",
"-Xconsistent-data-class-copy-visibility"
"-Xconsistent-data-class-copy-visibility",
"-Xsuppress-warning=NOTHING_TO_INLINE",
"-Xsuppress-warning=UNUSED_ANONYMOUS_PARAMETER",
"-Xwasm-debugger-custom-formatters"
)
val jvmCompilerArgs = buildList {
addAll(compilerArgs)
Expand All @@ -75,7 +66,6 @@ object Config {
}

val jvmTarget = JvmTarget.JVM_11
val idePluginJvmTarget = JvmTarget.JVM_17
val javaVersion = JavaVersion.VERSION_11
const val compileSdk = 35
const val targetSdk = compileSdk
Expand All @@ -101,4 +91,52 @@ object Config {
val includedFiles = listOf("**/*.kt", "**/*.kts")
val excludedFiles = listOf("**/resources/**", "**/build/**", "**/.idea/**")
}

object Debugger {

const val namespace = "${Config.namespace}.debugger"
const val appDescription = "A debugger tool for FlowMVI - $description"
const val name = "FlowMVI Debugger"
const val appId = "fd36c0cc-ae50-4aad-8579-f37e1e8af99c"
}

object Sample {

const val namespace = "${Config.namespace}.sample"
const val appDescription = "Sample app for FlowMVI - $description"
const val name = "FlowMVI Sample"
const val appId = "a7f6783f-2bb5-433d-9e5c-9f608ddd42d5"
}

// region Plugin
object Plugin {

const val id = "$artifactId.ideplugin"
const val name = Config.name
const val minIdeaVersion = "241"
const val certPath = "certificates/plugin_certificate_chain.crt"
val jvmTarget = JvmTarget.JVM_17

@Language("HTML")
const val description = """
IDE Plugin for FlowMVI - ${Config.description}
<br/>
This plugin aids in development with the library using:
<ul>
<li>An integrated debugger tool window with time travel and logging.</li>
<li>Live templates for creating stores, models, screens, plugins, and more.</li>
<li>Manipulating the store remotely.</li>
<li>Additional lint and safety checks.</li>
</ul>
<br/>
Missing feature? Found a bug? Open an issue on <a href="$url">Github</a>.
<br/>
Learn how to start using the framework in the <a href="$docsUrl/quickstart">Quickstart</a> guide.
<br/>
To use the debugging feature of the plugin, you need to have your app configured correctly. Learn how to do this in the <a href="$docsUrl/debugging">Documentation</a>.
<br/>
Note - the plugin's version (latest is $versionName) is synced with the version of the library it expects.
If you are using a severely outdated version of either library or the plugin, you may run into issues.
"""
}
}
13 changes: 7 additions & 6 deletions buildSrc/src/main/kotlin/ConfigureMultiplatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ fun Project.configureMultiplatform(
explicitApi()
applyDefaultHierarchyTemplate(configure)
withSourcesJar(true)
compilerOptions {
extraWarnings.set(true)
}

if (linux) {
linuxX64()
Expand Down Expand Up @@ -64,12 +67,10 @@ fun Project.configureMultiplatform(
}
}

if (jvm) jvm().compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(Config.jvmTarget)
freeCompilerArgs.addAll(Config.jvmCompilerArgs)
}
if (jvm) jvm {
compilerOptions {
jvmTarget.set(Config.jvmTarget)
freeCompilerArgs.addAll(Config.jvmCompilerArgs)
}
}

Expand Down
72 changes: 49 additions & 23 deletions core/src/commonMain/kotlin/pro/respawn/flowmvi/StoreImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import pro.respawn.flowmvi.annotation.NotIntendedForInheritance
import pro.respawn.flowmvi.api.ActionProvider
import pro.respawn.flowmvi.api.ActionReceiver
import pro.respawn.flowmvi.api.DelicateStoreApi
import pro.respawn.flowmvi.api.IntentReceiver
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Provider
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.api.StoreConfiguration
import pro.respawn.flowmvi.api.StorePlugin
import pro.respawn.flowmvi.api.context.ShutdownContext
import pro.respawn.flowmvi.exceptions.NonSuspendingSubscriberException
import pro.respawn.flowmvi.exceptions.SubscribeBeforeStartException
import pro.respawn.flowmvi.exceptions.UnhandledIntentException
import pro.respawn.flowmvi.modules.ActionModule
import pro.respawn.flowmvi.modules.IntentModule
import pro.respawn.flowmvi.impl.plugin.PluginInstance
import pro.respawn.flowmvi.modules.RecoverModule
import pro.respawn.flowmvi.modules.RestartableLifecycle
import pro.respawn.flowmvi.modules.StateModule
Expand All @@ -30,59 +36,71 @@ import pro.respawn.flowmvi.modules.restartableLifecycle
import pro.respawn.flowmvi.modules.stateModule
import pro.respawn.flowmvi.modules.subscriptionModule

@OptIn(NotIntendedForInheritance::class)
internal class StoreImpl<S : MVIState, I : MVIIntent, A : MVIAction>(
private val config: StoreConfiguration<S>,
plugin: StorePlugin<S, I, A>,
override val config: StoreConfiguration<S>,
private val plugin: PluginInstance<S, I, A>,
recover: RecoverModule<S, I, A> = recoverModule(plugin),
subs: SubscriptionModule = subscriptionModule(),
states: StateModule<S> = stateModule(config.initial, config.atomicStateUpdates),
intents: IntentModule<I> = intentModule(config.parallelIntents, config.intentCapacity, config.onOverflow),
actions: ActionModule<A> = actionModule(config.actionShareBehavior),
) : Store<S, I, A>,
Provider<S, I, A>,
ShutdownContext<S, I, A>,
IntentReceiver<I>,
ActionProvider<A>,
ActionReceiver<A>,
RestartableLifecycle by restartableLifecycle(),
StorePlugin<S, I, A> by plugin,
RecoverModule<S, I, A> by recover,
SubscriptionModule by subs,
StateModule<S> by states,
IntentModule<I> by intents,
ActionModule<A> by actions {
StateModule<S> by states {

override val name by config::name
private val intents = intentModule<I>(
parallel = config.parallelIntents,
capacity = config.intentCapacity,
overflow = config.onOverflow,
onUndeliveredIntent = plugin.onUndeliveredIntent?.let { { intent -> it(this, intent) } },
)

private val _actions = actionModule<A>(
behavior = config.actionShareBehavior,
onUndeliveredAction = plugin.onUndeliveredAction?.let { { action -> it(this, action) } }
)

override suspend fun emit(intent: I) = intents.emit(intent)
override fun intent(intent: I) = intents.intent(intent)

// region pipeline
override fun start(scope: CoroutineScope) = launchPipeline(
parent = scope,
storeConfig = config,
onAction = { action -> onAction(action)?.let { this@StoreImpl.action(it) } },
onTransformState = { transform ->
this@StoreImpl.updateState { onState(this, transform()) ?: this }
},
onStop = {
close() // makes sure to also clear the reference from RestartableLifecycle
onStop(it)
},
onAction = { action -> onAction(action)?.let { _actions.action(it) } },
onTransformState = { transform -> this@StoreImpl.updateState { onState(this, transform()) ?: this } },
onStop = { e -> close().also { plugin.onStop?.invoke(this, e) } },
onStart = { lifecycle ->
beginStartup(lifecycle)
launch intents@{
coroutineScope {
// run onStart plugins first to not let subscribers appear before the store is started fully
catch { onStart() }
launch {
if (plugin.onStart != null) catch { onStart() }
if (plugin.onSubscribe != null || plugin.onUnsubscribe != null) launch {
observeSubscribers(
onSubscribe = { catch { onSubscribe(it) } },
onUnsubscribe = { catch { onUnsubscribe(it) } }
)
}
launch {
awaitIntents {
catch { if (onIntent(it) != null && config.debuggable) throw UnhandledIntentException() }
if (plugin.onIntent != null) intents.awaitIntents {
catch {
val result = onIntent(it)
if (result != null && config.debuggable) throw UnhandledIntentException(result)
}
}
lifecycle.completeStartup()
}
}
}
)
// endregion

override fun CoroutineScope.subscribe(
block: suspend Provider<S, I, A>.() -> Unit
Expand All @@ -94,11 +112,19 @@ internal class StoreImpl<S : MVIState, I : MVIIntent, A : MVIAction>(
cancel()
}

// region contract
override val name by config::name
override val actions: Flow<A> by _actions::actions

@DelicateStoreApi
override fun send(action: A) = _actions.send(action)
override suspend fun action(action: A) = _actions.action(action)
override fun hashCode() = name?.hashCode() ?: super.hashCode()
override fun toString(): String = name ?: super.toString()
override fun equals(other: Any?): Boolean {
if (other !is Store<*, *, *>) return false
if (other.name == null && name == null) return other === this
return name == other.name
}
// endregion
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package pro.respawn.flowmvi.api
package pro.respawn.flowmvi.annotation

private const val Message = """
This API is experimental - it has no automated testing and breaking changes are very likely. Use at your own risk.
This is not a "regular" experimental annotation you can just opt-in to without consideration. You have been warned.
This API is experimental - it has no automated testing and breaking changes are very likely in the next releases.
Use at your own risk. This is not a "regular" experimental annotation you can just opt-in to without consideration.
You have been warned.
"""

/**
Expand All @@ -11,4 +12,4 @@ This is not a "regular" experimental annotation you can just opt-in to without c
@RequiresOptIn(message = Message)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
public annotation class ExperimentalStoreApi
public annotation class ExperimentalFlowMVIAPI
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package pro.respawn.flowmvi.api
package pro.respawn.flowmvi.annotation

private const val Message = """
Internal FlowMVI API. Do not use this API directly as public API already contains everything you should need.
This API is internal to the library.
Do not use this API directly as public API already exists for the same thing.
If you have the use-case for this api you can't avoid, please submit an issue.
"""

/**
Expand Down
Loading

0 comments on commit e80b879

Please sign in to comment.