-
Notifications
You must be signed in to change notification settings - Fork 63
NodeConnector improvements and support for Coroutines #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mapm14
wants to merge
4
commits into
bumble-tech:2.x
Choose a base branch
from
mapm14:node-connector-improvements
base: 2.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
1150d68
Create interop-coroutines module (missing tests)
mapm14 c85a3c5
Add Input caching to Rx implementations and remove NodeLifecycleAware…
mapm14 fc38144
Merge branch '2.x' into node-connector-improvements
mapm14 44b2b2e
Replace NodeLifecycleAware by Tooling in Connectable API
mapm14 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| plugins { | ||
| id("com.bumble.appyx.multiplatform") | ||
| id("com.android.library") | ||
| id("appyx-publish-multiplatform") | ||
| } | ||
|
|
||
| appyx { | ||
| androidNamespace.set("com.bumble.appyx.utils.interop.coroutines") | ||
| } | ||
|
|
||
| kotlin { | ||
| androidTarget { | ||
| publishLibraryVariants("release") | ||
| } | ||
| jvm("desktop") { | ||
| compilations.all { | ||
| kotlinOptions.jvmTarget = libs.versions.jvmTarget.get() | ||
| } | ||
| } | ||
| js(IR) { | ||
| // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 | ||
| moduleName = "appyx-utils-coroutines" | ||
| browser() | ||
| } | ||
|
|
||
| iosX64() | ||
| iosArm64() | ||
| iosSimulatorArm64() | ||
|
|
||
| sourceSets { | ||
| val commonMain by getting { | ||
| dependencies { | ||
| api(project(":appyx-navigation:appyx-navigation")) | ||
| } | ||
| } | ||
| val commonTest by getting { | ||
| dependencies { | ||
| implementation(kotlin("test")) | ||
| implementation(libs.kotlin.coroutines.test) | ||
| } | ||
| } | ||
| val androidMain by getting | ||
| val desktopMain by getting | ||
| val jsMain by getting | ||
| val iosX64Main by getting | ||
| val iosArm64Main by getting | ||
| val iosSimulatorArm64Main by getting | ||
| val iosMain by creating { | ||
| dependsOn(commonMain) | ||
| iosX64Main.dependsOn(this) | ||
| iosArm64Main.dependsOn(this) | ||
| iosSimulatorArm64Main.dependsOn(this) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <issues format="6" by="lint 7.3.1" type="baseline" client="gradle" dependencies="false" name="AGP (7.3.1)" variant="all" version="7.3.1"> | ||
|
|
||
| </issues> |
8 changes: 8 additions & 0 deletions
8
...rc/commonMain/kotlin/com/bumble/appyx/utils/interop/coroutines/connectable/Connectable.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.bumble.appyx.utils.interop.coroutines.connectable | ||
|
|
||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||
|
|
||
| interface Connectable<Input, Output> { | ||
| val input: MutableSharedFlow<Input> | ||
| val output: MutableSharedFlow<Output> | ||
| } |
113 changes: 113 additions & 0 deletions
113
.../commonMain/kotlin/com/bumble/appyx/utils/interop/coroutines/connectable/NodeConnector.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| package com.bumble.appyx.utils.interop.coroutines.connectable | ||
|
|
||
| import com.bumble.appyx.navigation.lifecycle.Lifecycle | ||
| import com.bumble.appyx.navigation.plugin.NodeLifecycleAware | ||
| import kotlinx.coroutines.CoroutineScope | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| import kotlinx.coroutines.Job | ||
| import kotlinx.coroutines.flow.FlowCollector | ||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| class NodeConnector<Input, Output : Any> : Connectable<Input, Output>, NodeLifecycleAware { | ||
|
|
||
| override fun onCreate(lifecycle: Lifecycle) { | ||
| flushInput() | ||
| flushOutput() | ||
| } | ||
|
|
||
| // region Input | ||
| private fun flushInput() { | ||
| if (!isInputFlushed) { | ||
| val coroutineScope = CoroutineScope(Dispatchers.Default + Job()) | ||
| _inputReplayCache.forEach { | ||
| coroutineScope.launch { _input.emit(it) } | ||
| } | ||
| } | ||
| isInputFlushed = true | ||
| _inputReplayCache.clear() | ||
| } | ||
|
|
||
| private var isInputFlushed = false | ||
| private val _input = MutableSharedFlow<Input>() | ||
| private val _inputReplayCache = mutableListOf<Input>() | ||
| override val input: MutableSharedFlow<Input> = object : MutableSharedFlow<Input> { | ||
|
|
||
| override val replayCache: List<Input> = _inputReplayCache | ||
|
|
||
| override val subscriptionCount: StateFlow<Int> = _input.subscriptionCount | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| override fun resetReplayCache() { | ||
| _inputReplayCache.clear() | ||
| } | ||
|
|
||
| override fun tryEmit(value: Input): Boolean = | ||
| if (isInputFlushed) { | ||
| _input.tryEmit(value) | ||
| } else { | ||
| _inputReplayCache.add(value) | ||
| } | ||
|
|
||
| override suspend fun emit(value: Input) { | ||
| if (isInputFlushed) { | ||
| _input.emit(value) | ||
| } else { | ||
| _inputReplayCache.add(value) | ||
| } | ||
| } | ||
|
|
||
| override suspend fun collect(collector: FlowCollector<Input>): Nothing = | ||
| _input.collect(collector) | ||
| } | ||
| // endregion | ||
|
|
||
| // region Output | ||
| private fun flushOutput() { | ||
| if (!isOutputFlushed) { | ||
| val coroutineScope = CoroutineScope(Dispatchers.Default + Job()) | ||
| _outputReplayCache.forEach { | ||
| coroutineScope.launch { _output.emit(it) } | ||
| } | ||
| } | ||
| isOutputFlushed = true | ||
| _outputReplayCache.clear() | ||
| } | ||
|
|
||
| private var isOutputFlushed = false | ||
| private val _output = MutableSharedFlow<Output>() | ||
| private val _outputReplayCache = mutableListOf<Output>() | ||
| override val output: MutableSharedFlow<Output> = object : MutableSharedFlow<Output> { | ||
|
|
||
| override val replayCache: List<Output> = _outputReplayCache | ||
|
|
||
| override val subscriptionCount: StateFlow<Int> = _output.subscriptionCount | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| override fun resetReplayCache() { | ||
| _outputReplayCache.clear() | ||
| } | ||
|
|
||
| override fun tryEmit(value: Output): Boolean = | ||
| if (isOutputFlushed) { | ||
| _output.tryEmit(value) | ||
| } else { | ||
| _outputReplayCache.add(value) | ||
| } | ||
|
|
||
| override suspend fun emit(value: Output) { | ||
| if (isOutputFlushed) { | ||
| _output.emit(value) | ||
| } else { | ||
| _outputReplayCache.add(value) | ||
| } | ||
| } | ||
|
|
||
| override suspend fun collect(collector: FlowCollector<Output>): Nothing = | ||
| _output.collect(collector) | ||
| } | ||
| // endregion | ||
|
|
||
| } |
13 changes: 13 additions & 0 deletions
13
...rc/commonMain/kotlin/com/bumble/appyx/utils/interop/coroutines/plugin/DisposeOnDestroy.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.bumble.appyx.utils.interop.coroutines.plugin | ||
|
|
||
| import com.bumble.appyx.interactions.core.plugin.Plugin | ||
| import com.bumble.appyx.navigation.plugin.Destroyable | ||
| import kotlinx.coroutines.Job | ||
|
|
||
| private class DisposeOnDestroy(private val jobs: List<Job>) : Destroyable { | ||
| override fun destroy() { | ||
| jobs.forEach { it.cancel() } | ||
| } | ||
| } | ||
|
|
||
| fun disposeOnDestroyPlugin(vararg jobs: Job): Plugin = DisposeOnDestroy(jobs.toList()) |
34 changes: 34 additions & 0 deletions
34
...onMain/kotlin/com/bumble/appyx/utils/interop/coroutines/store/RetainedInstanceStoreExt.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.bumble.appyx.utils.interop.coroutines.store | ||
|
|
||
| import com.bumble.appyx.navigation.modality.NodeContext | ||
| import com.bumble.appyx.navigation.store.RetainedInstanceStore | ||
| import com.bumble.appyx.navigation.store.getRetainedInstance | ||
| import kotlinx.coroutines.Job | ||
|
|
||
| /** | ||
| * Obtains or creates an instance of a class via the [get] extension. | ||
| * The Job will be cancelled when the disposer function is called. | ||
| */ | ||
| inline fun <reified T : Job> RetainedInstanceStore.getJob( | ||
| storeId: String, | ||
| key: String, | ||
| noinline factory: () -> T | ||
| ): T = get( | ||
| storeId = storeId, | ||
| disposer = { it.cancel() }, | ||
| factory = factory, | ||
| key = key, | ||
| ) | ||
|
|
||
| /** | ||
| * Obtains or creates an instance of a class via the [getRetainedInstance] extension. | ||
| * The Job will be cancelled when the disposer function is called. | ||
| */ | ||
| inline fun <reified T : Job> NodeContext.getRetainedDisposable( | ||
| key: String, | ||
| noinline factory: () -> T | ||
| ) = getRetainedInstance( | ||
| key = key, | ||
| disposer = { it.cancel() }, | ||
| factory = factory, | ||
| ) |
45 changes: 45 additions & 0 deletions
45
...kotlin/com/bumble/appyx/utils/interop/coroutines/plugin/CoroutinesDisposeOnDestroyTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.bumble.appyx.utils.interop.coroutines.plugin | ||
|
|
||
| import com.bumble.appyx.navigation.plugin.Destroyable | ||
| import kotlinx.coroutines.Job | ||
| import kotlin.test.assertFalse | ||
| import kotlin.test.assertIs | ||
| import kotlin.test.assertTrue | ||
| import kotlin.test.Test | ||
|
|
||
| internal class CoroutinesDisposeOnDestroyTest { | ||
| @Test | ||
| fun `WHEN dispose on destroy plugin created THEN verify is destroyable type`() { | ||
| assertIs<Destroyable>(disposeOnDestroyPlugin()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `GIVEN dispose on destroy plugin created with job WHEN destroy THEN job is cancelled`() { | ||
| val job = Job() | ||
| val disposeOnDestroyPlugin = disposeOnDestroyPlugin(job) | ||
|
|
||
| (disposeOnDestroyPlugin as Destroyable).destroy() | ||
|
|
||
| assertTrue(job.isCancelled) | ||
| } | ||
|
|
||
| @Test | ||
| fun `GIVEN dispose on destroy plugin created with multiple jobs WHEN destroy THEN all jobs are cancelled`() { | ||
| val job1 = Job() | ||
| val job2 = Job() | ||
| val disposeOnDestroyPlugin = disposeOnDestroyPlugin(job1, job2) | ||
|
|
||
| (disposeOnDestroyPlugin as Destroyable).destroy() | ||
|
|
||
| assertTrue(job1.isCancelled) | ||
| assertTrue(job2.isCancelled) | ||
| } | ||
|
|
||
| @Test | ||
| fun `WHEN dispose on destroy plugin created with job THEN job is not cancelled`() { | ||
| val job = Job() | ||
| disposeOnDestroyPlugin(job) | ||
|
|
||
| assertFalse(job.isCancelled) | ||
| } | ||
| } |
4 changes: 2 additions & 2 deletions
4
...interop-rx2/src/main/kotlin/com/bumble/appyx/utils/interop/rx2/connectable/Connectable.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| package com.bumble.appyx.utils.interop.rx2.connectable | ||
|
|
||
| import com.bumble.appyx.navigation.plugin.NodeLifecycleAware | ||
| import com.bumble.appyx.navigation.plugin.Tooling | ||
| import com.jakewharton.rxrelay2.Relay | ||
|
|
||
| interface Connectable<Input, Output> : NodeLifecycleAware { | ||
| interface Connectable<Input, Output> : Tooling { | ||
| val input: Relay<Input> | ||
| val output: Relay<Output> | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / detekt
Wildcard imports should be replaced with imports using fully qualified class names. Wildcard imports can lead to naming conflicts. A library update can introduce naming clashes with your classes which results in compilation errors.