All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.pro.respawn.flowmvi.plugins.AwaitSubscribersPlugin.kt Maven / Gradle / Ivy

Go to download

A Kotlin Multiplatform MVI library based on plugins that is simple, fast, powerful & flexible

There is a newer version: 3.0.0
Show newest version
package pro.respawn.flowmvi.plugins

import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.StorePlugin
import pro.respawn.flowmvi.dsl.StoreBuilder
import pro.respawn.flowmvi.dsl.plugin
import kotlin.time.Duration

/**
 * A class that manages the job that waits for subscribers to appear.
 * This job is set by the [awaitSubscribersPlugin] when the store is launched.
 * Use [await] or [complete] to manage the job.
 * You can only complete the job once per one start of the store.
 */
public class SubscriberManager {

    internal companion object {

        internal const val Name = "AwaitSubscribersPlugin"
    }

    private var subscriber by atomic(null)

    /**
     * Start waiting for subscribers, suspending until the given number of subscribers arrive.
     * After the subscribers arrived, this call will return immediately
     */
    public suspend fun await() {
        subscriber?.join()
    }

    /**
     * Complete the wait period, freeing the store and coroutines that called [await] to continue.
     */
    public fun complete() {
        subscriber?.cancel()
    }

    /**
     * Same as [complete], but suspends until completion
     */
    public suspend fun completeAndWait(): Unit? = subscriber?.cancelAndJoin()

    /**
     * Starts waiting for the subscribers until either [this] [CoroutineScope] is cancelled or [complete] is called.
     * Usually not called manually but rather launched by the [awaitSubscribersPlugin].
     */
    public fun CoroutineScope.launch(timeout: Duration) {
        subscriber = launch {
            subscriber?.cancelAndJoin()
            withTimeout(timeout) {
                awaitCancellation()
            }
        }.apply {
            invokeOnCompletion {
                subscriber = null
            }
        }
    }
}

/**
 * Installs a new [awaitSubscribersPlugin]
 * @see awaitSubscribersPlugin
 */
@FlowMVIDSL
public fun  StoreBuilder.awaitSubscribers(
    manager: SubscriberManager,
    minSubs: Int = 1,
    suspendStore: Boolean,
    timeout: Duration = Duration.INFINITE,
    name: String = SubscriberManager.Name,
): Unit = install(awaitSubscribersPlugin(manager, minSubs, suspendStore, timeout, name))

/**
 * Installs a new plugin using [manager] that will start waiting for new subscribers when the store launches.
 * The plugin will wait for [minSubs] subscribers for a maximum duration of [timeout].
 * If [suspendStore] is true, then the store will not process any [MVIIntent]s while it waits for subscribers.
 * This plugin starts waiting **after** plugins installed before have finished their [StorePlugin.onStart] block.
 * By default, cannot be installed multiple times, but [name] can be overridden to allow that,
 * provided that you do not reuse the [manager].
 */
@FlowMVIDSL
public fun  awaitSubscribersPlugin(
    manager: SubscriberManager,
    minSubs: Int = 1,
    suspendStore: Boolean = true,
    timeout: Duration = Duration.INFINITE,
    name: String = SubscriberManager.Name,
): StorePlugin = plugin {
    this.name = name
    onStart {
        with(manager) {
            launch(timeout)
            if (suspendStore) await()
        }
    }
    onStop {
        manager.complete()
    }
    onSubscribe { _, subscriberCount ->
        if (minSubs == subscriberCount + 1) manager.complete()
    }
}