Skip to content
This repository was archived by the owner on Feb 5, 2021. It is now read-only.

Make renderAsState public, make WorkflowContainer take a ViewEnvironment. #32

Merged
merged 1 commit into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions core-compose/api/core-compose.api
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,31 @@ public final class com/squareup/workflow/ui/compose/CompositionRootKt {
public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewRegistry;
}

public final class com/squareup/workflow/ui/compose/RenderAsStateKt {
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
}

public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt {
public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V
public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V
}

public final class com/squareup/workflow/ui/compose/WorkflowContainerKt {
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
}

public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/*
* 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry")

package com.squareup.workflow.ui.compose

import androidx.compose.FrameManager
import androidx.compose.Providers
import androidx.compose.mutableStateOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.ui.savedinstancestate.UiSavedStateRegistry
import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient
import androidx.ui.test.createComposeRule
import androidx.ui.test.runOnIdleCompose
import androidx.ui.test.waitForIdle
import com.google.common.truth.Truth.assertThat
import com.squareup.workflow.RenderContext
import com.squareup.workflow.Snapshot
import com.squareup.workflow.StatefulWorkflow
import com.squareup.workflow.Workflow
import com.squareup.workflow.action
import com.squareup.workflow.parse
import com.squareup.workflow.readUtf8WithLength
import com.squareup.workflow.stateless
import com.squareup.workflow.ui.compose.RenderAsStateTest.SnapshottingWorkflow.SnapshottedRendering
import com.squareup.workflow.writeUtf8WithLength
import okio.ByteString
import okio.ByteString.Companion.decodeBase64
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RenderAsStateTest {

@Rule @JvmField val composeRule = createComposeRule()

@Test fun passesPropsThrough() {
val workflow = Workflow.stateless<String, Nothing, String> { it }
lateinit var initialRendering: String

composeRule.setContent {
initialRendering = workflow.renderAsState("foo").value
}

runOnIdleCompose {
assertThat(initialRendering).isEqualTo("foo")
}
}

@Test fun seesPropsAndRenderingUpdates() {
val workflow = Workflow.stateless<String, Nothing, String> { it }
val props = mutableStateOf("foo")
lateinit var rendering: String

composeRule.setContent {
rendering = workflow.renderAsState(props.value).value
}

waitForIdle()
assertThat(rendering).isEqualTo("foo")
FrameManager.framed {
props.value = "bar"
}
waitForIdle()
assertThat(rendering).isEqualTo("bar")
}

@Test fun invokesOutputCallback() {
val workflow = Workflow.stateless<Unit, String, (String) -> Unit> {
{ string -> actionSink.send(action { setOutput(string) }) }
}
val receivedOutputs = mutableListOf<String>()
lateinit var rendering: (String) -> Unit

composeRule.setContent {
rendering = workflow.renderAsState(onOutput = { receivedOutputs += it }).value
}

waitForIdle()
assertThat(receivedOutputs).isEmpty()
rendering("one")

waitForIdle()
assertThat(receivedOutputs).isEqualTo(listOf("one"))
rendering("two")

waitForIdle()
assertThat(receivedOutputs).isEqualTo(listOf("one", "two"))
}

@Test fun savesSnapshot() {
val workflow = SnapshottingWorkflow()
val savedStateRegistry = UiSavedStateRegistry(emptyMap()) { true }
lateinit var rendering: SnapshottedRendering

composeRule.setContent {
Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) {
rendering = renderAsStateImpl(
workflow,
props = Unit,
onOutput = {},
diagnosticListener = null,
snapshotKey = SNAPSHOT_KEY
).value
}
}

waitForIdle()
assertThat(rendering.string).isEmpty()
rendering.updateString("foo")

waitForIdle()
val savedValues = FrameManager.framed {
savedStateRegistry.performSave()
}
println("saved keys: ${savedValues.keys}")
// Relying on the int key across all runtimes is brittle, so use an explicit key.
val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray))
println("snapshot: ${snapshot.base64()}")
assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT)
}

@Test fun restoresSnapshot() {
val workflow = SnapshottingWorkflow()
val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray())
val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true }
lateinit var rendering: SnapshottedRendering

composeRule.setContent {
Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) {
rendering = renderAsStateImpl(
workflow,
props = Unit,
onOutput = {},
diagnosticListener = null,
snapshotKey = "workflow-snapshot"
).value
}
}

waitForIdle()
assertThat(rendering.string).isEqualTo("foo")
}

@Test fun restoresFromSnapshotWhenWorkflowChanged() {
val workflow1 = SnapshottingWorkflow()
val workflow2 = SnapshottingWorkflow()
val currentWorkflow = mutableStateOf(workflow1)
lateinit var rendering: SnapshottedRendering

var compositionCount = 0
var lastCompositionCount = 0
fun assertWasRecomposed() {
assertThat(compositionCount).isGreaterThan(lastCompositionCount)
lastCompositionCount = compositionCount
}

composeRule.setContent {
compositionCount++
rendering = currentWorkflow.value.renderAsState().value
}

// Initialize the first workflow.
waitForIdle()
assertThat(rendering.string).isEmpty()
assertWasRecomposed()
rendering.updateString("one")
waitForIdle()
assertWasRecomposed()
assertThat(rendering.string).isEqualTo("one")

// Change the workflow instance being rendered. This should restart the runtime, but restore
// it from the snapshot.
FrameManager.framed {
currentWorkflow.value = workflow2
}

waitForIdle()
assertWasRecomposed()
assertThat(rendering.string).isEqualTo("one")
}

private companion object {
const val SNAPSHOT_KEY = "workflow-snapshot"

/** Golden value from [savesSnapshot]. */
val EXPECTED_SNAPSHOT = "AAAABwAAAANmb28AAAAA".decodeBase64()!!
}

// Seems to be a problem accessing Workflow.stateful.
private class SnapshottingWorkflow :
StatefulWorkflow<Unit, String, Nothing, SnapshottedRendering>() {

data class SnapshottedRendering(
val string: String,
val updateString: (String) -> Unit
)

override fun initialState(
props: Unit,
snapshot: Snapshot?
): String = snapshot?.bytes?.parse { it.readUtf8WithLength() } ?: ""

override fun render(
props: Unit,
state: String,
context: RenderContext<String, Nothing>
) = SnapshottedRendering(
string = state,
updateString = { newString -> context.actionSink.send(updateString(newString)) }
)

override fun snapshotState(state: String): Snapshot =
Snapshot.write { it.writeUtf8WithLength(state) }

private fun updateString(newString: String) = action {
nextState = newString
}
}
}
Loading