Skip to content

Commit

Permalink
New ZiplineTreehouseUi.Host bridges all host functions (#1647)
Browse files Browse the repository at this point in the history
* New ZiplineTreehouseUi.Host bridges all host functions

At the moment we've got 4 host functions offered to each UI. In a follow-up
I'd like to introduce a 5th. I'd like the process of adding a 5th to not
require yet another start() overload, so I'm introducing this type to
make additional features easier to add later.

* Revert a parameter rename

* Update redwood-treehouse-guest/src/commonMain/kotlin/app/cash/redwood/treehouse/treehouseCompose.kt

Co-authored-by: Jake Wharton <[email protected]>

* Assignments are not expressions

---------

Co-authored-by: Jake Wharton <[email protected]>
  • Loading branch information
squarejesse and JakeWharton authored Oct 31, 2023
1 parent 9870537 commit 51ece61
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package app.cash.redwood.treehouse

import androidx.compose.runtime.saveable.SaveableStateRegistry
import app.cash.redwood.compose.RedwoodComposition
import app.cash.redwood.protocol.Change
import app.cash.redwood.protocol.EventSink
import app.cash.redwood.protocol.guest.ProtocolBridge
import app.cash.redwood.protocol.guest.ProtocolRedwoodComposition
Expand Down Expand Up @@ -57,35 +58,41 @@ private class RedwoodZiplineTreehouseUi(

private lateinit var saveableStateRegistry: SaveableStateRegistry

@Deprecated(
"Use `start` method that takes in an `OnBackPressedDispatcherService` instead.",
ReplaceWith("start(changesSink, TODO(), uiConfigurations, stateSnapshot)"),
)
@Suppress("OVERRIDE_DEPRECATION")
override fun start(
changesSink: ChangesSinkService,
uiConfigurations: StateFlow<UiConfiguration>,
stateSnapshot: StateSnapshot?,
) {
val onBackPressedDispatcher = object : OnBackPressedDispatcherService {
override fun addCallback(
onBackPressedCallback: OnBackPressedCallbackService,
): CancellableService {
return object : CancellableService {
override fun cancel() = Unit
}
}
}
start(changesSink, onBackPressedDispatcher, uiConfigurations, stateSnapshot)
start(changesSink, NullOnBackPressedDispatcherService, uiConfigurations, stateSnapshot)
}

@Suppress("OVERRIDE_DEPRECATION")
override fun start(
changesSink: ChangesSinkService,
onBackPressedDispatcher: OnBackPressedDispatcherService,
uiConfigurations: StateFlow<UiConfiguration>,
stateSnapshot: StateSnapshot?,
) {
val host = object : ZiplineTreehouseUi.Host {
override val uiConfigurations = uiConfigurations
override val stateSnapshot = stateSnapshot

override fun sendChanges(changes: List<Change>) {
changesSink.sendChanges(changes)
}

override fun addOnBackPressedCallback(
callback: OnBackPressedCallbackService,
): CancellableService = onBackPressedDispatcher.addCallback(callback)
}

start(host)
}

override fun start(host: ZiplineTreehouseUi.Host) {
this.saveableStateRegistry = SaveableStateRegistry(
restoredValues = stateSnapshot?.content,
restoredValues = host.stateSnapshot?.content,
// Note: values will only be restored by SaveableStateRegistry if `canBeSaved` returns true.
// With current serialization mechanism of stateSnapshot, this field is always true, an update
// to lambda of this field might be needed when serialization mechanism of stateSnapshot
Expand All @@ -97,10 +104,10 @@ private class RedwoodZiplineTreehouseUi(
scope = appLifecycle.coroutineScope + appLifecycle.frameClock,
bridge = bridge,
widgetVersion = appLifecycle.widgetVersion,
changesSink = changesSink,
onBackPressedDispatcher = onBackPressedDispatcher.asNonService(),
changesSink = host,
onBackPressedDispatcher = host.asOnBackPressedDispatcher(),
saveableStateRegistry = saveableStateRegistry,
uiConfigurations = uiConfigurations,
uiConfigurations = host.uiConfigurations,
)
this.composition = composition

Expand All @@ -119,24 +126,29 @@ private class RedwoodZiplineTreehouseUi(
}
}

private fun OnBackPressedDispatcherService.asNonService(): OnBackPressedDispatcher {
return object : OnBackPressedDispatcher {
override fun addCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable {
return this@asNonService.addCallback(onBackPressedCallback.asService())
}
private fun ZiplineTreehouseUi.Host.asOnBackPressedDispatcher() = object : OnBackPressedDispatcher {
override fun addCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable {
return this@asOnBackPressedDispatcher.addOnBackPressedCallback(
onBackPressedCallback.asService(),
)
}
}

private fun OnBackPressedCallback.asService(): OnBackPressedCallbackService {
return object : OnBackPressedCallbackService {
override var isEnabled: Boolean
get() = this@asService.isEnabled
set(value) {
this@asService.isEnabled = value
}

override fun handleOnBackPressed() {
this@asService.handleOnBackPressed()
private fun OnBackPressedCallback.asService() = object : OnBackPressedCallbackService {
override var isEnabled: Boolean
get() = this@asService.isEnabled
set(value) {
this@asService.isEnabled = value
}

override fun handleOnBackPressed() {
this@asService.handleOnBackPressed()
}
}

private object NullOnBackPressedDispatcherService : OnBackPressedDispatcherService {
override fun addCallback(onBackPressedCallback: OnBackPressedCallbackService) =
object : CancellableService {
override fun cancel() = Unit
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ private class ViewContentCodeBinding<A : AppService>(
private val json: Json,
private val onBackPressedDispatcher: OnBackPressedDispatcher,
firstUiConfiguration: StateFlow<UiConfiguration>,
) : EventSink, ChangesSinkService, TreehouseView.SaveCallback {
) : EventSink, ChangesSinkService, TreehouseView.SaveCallback, ZiplineTreehouseUi.Host {
private val uiConfigurationFlow = SequentialStateFlow(firstUiConfiguration)

private val bindingScope = CoroutineScope(SupervisorJob(appScope.coroutineContext.job))
Expand Down Expand Up @@ -302,6 +302,12 @@ private class ViewContentCodeBinding<A : AppService>(

private var initViewCalled: Boolean = false

/** The state to restore. Initialized in [start]. */
override var stateSnapshot: StateSnapshot? = null

override val uiConfigurations: StateFlow<UiConfiguration>
get() = uiConfigurationFlow

fun initView(view: TreehouseView<*>) {
dispatchers.checkUi()

Expand Down Expand Up @@ -388,50 +394,44 @@ private class ViewContentCodeBinding<A : AppService>(
val scopedAppService = serviceScope.apply(session.appService)
val treehouseUi = contentSource.get(scopedAppService)
treehouseUiOrNull = treehouseUi
val restoredId = viewOrNull?.stateSnapshotId
val restoredState = if (restoredId != null) codeHost.stateStore.get(restoredId.value.orEmpty()) else null
stateSnapshot = viewOrNull?.stateSnapshotId?.let {
codeHost.stateStore.get(it.value.orEmpty())
}
try {
treehouseUi.start(
changesSink = this@ViewContentCodeBinding,
onBackPressedDispatcher = onBackPressedDispatcherService,
uiConfigurations = uiConfigurationFlow,
stateSnapshot = restoredState,
)
treehouseUi.start(this@ViewContentCodeBinding)
} catch (e: ZiplineApiMismatchException) {
// Fall back to calling the function that doesn't have a back pressed dispatcher.
treehouseUi.start(
changesSink = this@ViewContentCodeBinding,
uiConfigurations = uiConfigurationFlow,
stateSnapshot = restoredState,
stateSnapshot = stateSnapshot,
)
}
}
}

private val onBackPressedDispatcherService = object : OnBackPressedDispatcherService {
override fun addCallback(
onBackPressedCallback: OnBackPressedCallbackService,
): CancellableService {
dispatchers.checkZipline()
val cancellable = onBackPressedDispatcher.addCallback(
object : OnBackPressedCallback(onBackPressedCallback.isEnabled) {
override fun handleOnBackPressed() {
bindingScope.launch(dispatchers.zipline) {
onBackPressedCallback.handleOnBackPressed()
}
override fun addOnBackPressedCallback(
callback: OnBackPressedCallbackService,
): CancellableService {
dispatchers.checkZipline()
val cancellable = onBackPressedDispatcher.addCallback(
object : OnBackPressedCallback(callback.isEnabled) {
override fun handleOnBackPressed() {
bindingScope.launch(dispatchers.zipline) {
callback.handleOnBackPressed()
}
},
)

return object : CancellableService {
override fun cancel() {
dispatchers.checkZipline()
cancellable.cancel()
}
},
)

override fun close() {
cancel()
}
return object : CancellableService {
override fun cancel() {
dispatchers.checkZipline()
cancellable.cancel()
}

override fun close() {
cancel()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,16 @@ class FakeZiplineTreehouseUi(
) : ZiplineTreehouseUi {
private var nextWidgetId = 1

private lateinit var changesSink: ChangesSinkService
private lateinit var host: ZiplineTreehouseUi.Host

override fun start(
changesSink: ChangesSinkService,
onBackPressedDispatcher: OnBackPressedDispatcherService,
uiConfigurations: StateFlow<UiConfiguration>,
stateSnapshot: StateSnapshot?,
) {
override fun start(host: ZiplineTreehouseUi.Host) {
eventLog += "$name.start()"
this.changesSink = changesSink
this.host = host
}

fun addWidget(label: String) {
val widgetId = Id(nextWidgetId++)
changesSink.sendChanges(
host.sendChanges(
listOf(
Create(widgetId, WidgetTag(1)),
PropertyChange(widgetId, PropertyTag(1), JsonPrimitive(label)),
Expand All @@ -64,6 +59,16 @@ class FakeZiplineTreehouseUi(
eventLog += "$name.sendEvent($event)"
}

@Suppress("OVERRIDE_DEPRECATION")
override fun start(
changesSink: ChangesSinkService,
onBackPressedDispatcher: OnBackPressedDispatcherService,
uiConfigurations: StateFlow<UiConfiguration>,
stateSnapshot: StateSnapshot?,
) {
error("unexpected call")
}

@Suppress("OVERRIDE_DEPRECATION")
override fun start(
changesSink: ChangesSinkService,
Expand Down
22 changes: 22 additions & 0 deletions redwood-treehouse/api/zipline-api.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,26 @@ functions = [

# fun start(app.cash.redwood.treehouse.ChangesSinkService, kotlinx.coroutines.flow.StateFlow<app.cash.redwood.ui.UiConfiguration>, app.cash.redwood.treehouse.StateSnapshot): kotlin.Unit
"FiVQXMQW",

# fun start(app.cash.redwood.treehouse.ZiplineTreehouseUi.Host): kotlin.Unit
"h9yZr91W",
]

[app.cash.redwood.treehouse.ZiplineTreehouseUi.Host]

functions = [
# fun addOnBackPressedCallback(app.cash.redwood.treehouse.OnBackPressedCallbackService): app.cash.redwood.treehouse.CancellableService
"2nRsPBzK",

# fun close(): kotlin.Unit
"moYx+T3e",

# fun sendChanges(kotlin.collections.List): kotlin.Unit
"W8qOuU0t",

# val stateSnapshot: app.cash.redwood.treehouse.StateSnapshot
"hIDyr4lf",

# val uiConfigurations: kotlinx.coroutines.flow.StateFlow<app.cash.redwood.ui.UiConfiguration>
"iAkCX7gg",
]
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import kotlinx.coroutines.flow.StateFlow
*/
@ObjCName("ZiplineTreehouseUi", exact = true)
public interface ZiplineTreehouseUi : ZiplineService, EventSink {
public fun start(host: Host)

@Deprecated("Use `start` method that takes in a Host")
public fun start(
changesSink: ChangesSinkService,
onBackPressedDispatcher: OnBackPressedDispatcherService,
Expand All @@ -46,4 +49,12 @@ public interface ZiplineTreehouseUi : ZiplineService, EventSink {
)

public fun snapshotState(): StateSnapshot? = null

/** Access to the host that presents this UI. */
public interface Host : ZiplineService, ChangesSinkService {
public val uiConfigurations: StateFlow<UiConfiguration>
public val stateSnapshot: StateSnapshot?

public fun addOnBackPressedCallback(callback: OnBackPressedCallbackService): CancellableService
}
}

0 comments on commit 51ece61

Please sign in to comment.