-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP Reimplemented Workers using side effects.
This fixes #82, which is the Kotlin half of square/workflow#1021. Also fixes square/workflow#1197.
- Loading branch information
1 parent
e931852
commit 724e219
Showing
25 changed files
with
504 additions
and
637 deletions.
There are no files selected for viewing
This file contains 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 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 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 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
44 changes: 44 additions & 0 deletions
44
workflow-core/src/main/java/com/squareup/workflow/WorkerKType.kt
This file contains 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,44 @@ | ||
/* | ||
* Copyright 2020 Square Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
@file:JvmMultifileClass | ||
@file:JvmName("Workflows") | ||
|
||
package com.squareup.workflow | ||
|
||
import kotlin.reflect.KClassifier | ||
import kotlin.reflect.KType | ||
import kotlin.reflect.KTypeProjection | ||
import kotlin.reflect.KVariance.OUT | ||
|
||
/** | ||
* An implementation of [KType] whose [classifier][KType.classifier] is the `KClass` of the worker | ||
* itself, and which has a type parameter of the [Worker.outputType] of the worker. | ||
*/ | ||
internal data class WorkerKType( | ||
override val classifier: KClassifier, | ||
val outputTypeProjection: KTypeProjection | ||
) : KType { | ||
constructor(worker: Worker<*>) : this(worker::class, worker.outputTypeProjection) | ||
|
||
override val arguments: List<KTypeProjection> get() = listOf(outputTypeProjection) | ||
override val annotations: List<Annotation> get() = emptyList() | ||
override val isMarkedNullable: Boolean get() = false | ||
|
||
override fun toString(): String = "Worker<${outputTypeProjection.type}>" | ||
} | ||
|
||
private val Worker<*>.outputTypeProjection: KTypeProjection | ||
get() = outputType?.let { KTypeProjection(OUT, it) } ?: KTypeProjection(null, null) |
99 changes: 99 additions & 0 deletions
99
workflow-core/src/main/java/com/squareup/workflow/WorkerWorkflow.kt
This file contains 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,99 @@ | ||
/* | ||
* Copyright 2020 Square Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
@file:JvmMultifileClass | ||
@file:JvmName("Workflows") | ||
|
||
package com.squareup.workflow | ||
|
||
import kotlinx.coroutines.CoroutineName | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.withContext | ||
|
||
/** | ||
* TODO write documentation | ||
*/ | ||
@OptIn(ExperimentalWorkflowApi::class) | ||
internal class WorkerWorkflow<OutputT>( | ||
workerType: WorkerKType, | ||
private val key: String | ||
) : | ||
StatefulWorkflow<Worker<OutputT>, Int, OutputT, Unit>(), | ||
ImpostorWorkflow { | ||
|
||
override val realIdentifier: WorkflowIdentifier = unsnapshottableIdentifier(workerType) | ||
|
||
override fun initialState( | ||
props: Worker<OutputT>, | ||
snapshot: Snapshot? | ||
): Int = 0 | ||
|
||
override fun onPropsChanged( | ||
old: Worker<OutputT>, | ||
new: Worker<OutputT>, | ||
state: Int | ||
): Int = if (!old.doesSameWorkAs(new)) state + 1 else state | ||
|
||
override fun render( | ||
props: Worker<OutputT>, | ||
state: Int, | ||
context: RenderContext<Worker<OutputT>, Int, OutputT> | ||
) { | ||
context.runningSideEffect(state.toString()) { | ||
runWorker(props, key, context.actionSink) | ||
} | ||
} | ||
|
||
override fun snapshotState(state: Int): Snapshot = Snapshot.EMPTY | ||
} | ||
|
||
/** | ||
* TODO write kdoc | ||
* | ||
* Visible for testing. | ||
*/ | ||
@OptIn(ExperimentalWorkflowApi::class) | ||
internal suspend fun <OutputT> runWorker( | ||
worker: Worker<OutputT>, | ||
renderKey: String, | ||
actionSink: Sink<WorkflowAction<Worker<OutputT>, Int, OutputT>> | ||
) { | ||
withContext(CoroutineName(worker.debugName(renderKey))) { | ||
worker.runWithNullCheck() | ||
.collectToSink(actionSink) { output -> | ||
action { setOutput(output) } | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* In unit tests, if you use a mocking library to create a Worker, the run method will return null | ||
* even though the return type is non-nullable in Kotlin. Kotlin helps out with this by throwing an | ||
* NPE before before any kotlin code gets the null, but the NPE that it throws includes an almost | ||
* completely useless stacktrace and no other details. | ||
* | ||
* This method does an explicit null check and throws an exception with a more helpful message. | ||
* | ||
* See [#842](https://github.com/square/workflow/issues/842). | ||
*/ | ||
@Suppress("USELESS_ELVIS") | ||
private fun <T> Worker<T>.runWithNullCheck(): Flow<T> = | ||
run() ?: throw NullPointerException( | ||
"Worker $this returned a null Flow. " + | ||
"If this is a test mock, make sure you mock the run() method!" | ||
) | ||
|
||
private fun Worker<*>.debugName(key: String) = | ||
toString().let { if (key.isBlank()) it else "$it:$key" } |
51 changes: 51 additions & 0 deletions
51
workflow-core/src/test/java/com/squareup/workflow/NullFlowWorker.java
This file contains 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,51 @@ | ||
/* | ||
* Copyright 2019 Square Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.squareup.workflow; | ||
|
||
import kotlin.reflect.KType; | ||
import kotlinx.coroutines.flow.Flow; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
/** | ||
* Worker that incorrectly returns null from {@link #run}, to simulate the default behavior of some | ||
* mocking libraries. | ||
* | ||
* See <a href="https://github.com/square/workflow/issues/842">#842</a>. | ||
*/ | ||
class NullFlowWorker implements Worker { | ||
|
||
@Nullable @Override public KType getOutputType() { | ||
return null; | ||
} | ||
|
||
@NotNull @Override public Flow run() { | ||
//noinspection ConstantConditions | ||
return null; | ||
} | ||
|
||
@Override public boolean doesSameWorkAs(@NotNull Worker otherWorker) { | ||
//noinspection unchecked | ||
return Worker.DefaultImpls.doesSameWorkAs(this, otherWorker); | ||
} | ||
|
||
/** | ||
* Override this to make writing assertions on exception messages easier. | ||
*/ | ||
@Override public String toString() { | ||
return "NullFlowWorker.toString"; | ||
} | ||
} |
Oops, something went wrong.