Repository: cashapp/redwood
Pull request #859 (2023-03-18 04:01:16)

Drop type parameters on TreehouseView and WidgetSystem
These are no longer necessary.


Patch:
diff --git a/redwood-treehouse-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt b/redwood-treehouse-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt
index 8da69caae..c5314697d 100644
--- a/redwood-treehouse-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt
+++ b/redwood-treehouse-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/composeui/TreehouseContent.kt
@@ -29,6 +29,7 @@ import app.cash.redwood.treehouse.TreehouseContentSource
 import app.cash.redwood.treehouse.TreehouseView
 import app.cash.redwood.treehouse.TreehouseView.CodeListener
 import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.bindWhenReady
 import app.cash.redwood.widget.compose.ComposeWidgetChildren
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,7 +37,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
 @Composable
 public fun <A : AppService> TreehouseContent(
   treehouseApp: TreehouseApp<A>,
-  widgetSystem: TreehouseView.WidgetSystem<A>,
+  widgetSystem: WidgetSystem,
   codeListener: CodeListener = CodeListener(),
   contentSource: TreehouseContentSource<A>,
 ) {
@@ -46,13 +47,13 @@ public fun <A : AppService> TreehouseContent(
 
   val rememberedCodeListener = rememberUpdatedState(codeListener)
   val treehouseView = remember(widgetSystem) {
-    object : TreehouseView<A> {
+    object : TreehouseView {
       override val codeListener get() = rememberedCodeListener.value
       override val children = ComposeWidgetChildren()
       override val hostConfiguration = MutableStateFlow(hostConfiguration)
       override val widgetSystem = widgetSystem
       override val readyForContent = true
-      override var readyForContentChangeListener: ReadyForContentChangeListener<A>? = null
+      override var readyForContentChangeListener: ReadyForContentChangeListener? = null
       override fun reset() = children.remove(0, children.widgets.size)
     }
   }
diff --git a/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseWidgetView.kt b/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseWidgetView.kt
index 00d578cf5..5fe6debba 100644
--- a/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseWidgetView.kt
+++ b/redwood-treehouse-host/src/androidMain/kotlin/app/cash/redwood/treehouse/TreehouseWidgetView.kt
@@ -25,32 +25,20 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.widget.FrameLayout
 import app.cash.redwood.treehouse.TreehouseView.CodeListener
 import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.widget.ViewGroupChildren
 import app.cash.redwood.widget.Widget
-import kotlin.DeprecationLevel.ERROR
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
 @SuppressLint("ViewConstructor")
-public class TreehouseWidgetView<A : AppService>(
+public class TreehouseWidgetView(
   context: Context,
-  override val widgetSystem: TreehouseView.WidgetSystem<A>,
-) : FrameLayout(context), TreehouseView<A> {
-  @Deprecated(
-    message = "TreehouseView no longer owns a TreehouseApp. Instead, call app.renderTo(view).",
-    replaceWith = ReplaceWith("TreehouseWidgetView(context, widgetSystem).also(treehouseApp::renderTo)"),
-    level = ERROR,
-  )
-  @Suppress("UNUSED_PARAMETER")
-  public constructor(
-    context: Context,
-    treehouseApp: TreehouseApp<A>,
-    widgetSystem: TreehouseView.WidgetSystem<A>,
-  ) : this(context, widgetSystem)
-
+  override val widgetSystem: WidgetSystem,
+) : FrameLayout(context), TreehouseView {
   public override var codeListener: CodeListener = CodeListener()
 
-  override var readyForContentChangeListener: ReadyForContentChangeListener<A>? = null
+  override var readyForContentChangeListener: ReadyForContentChangeListener? = null
     set(value) {
       check(value == null || field == null) { "View already bound to a listener" }
       field = value
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt
index 3685ac566..ded7219a1 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/Content.kt
@@ -37,7 +37,7 @@ public interface Content<A : AppService> {
    *
    * This function may only be invoked on [TreehouseDispatchers.ui].
    */
-  public fun bind(view: TreehouseView<A>)
+  public fun bind(view: TreehouseView)
 
   /**
    * Calling [unbind] without a bound view is safe.
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt
index 912528048..43472c23b 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/ContentBinding.kt
@@ -25,8 +25,8 @@ import okio.Closeable
  *
  * Returns a closeable that unbinds from the content and stops tracking the ready state.
  */
-public fun <A : AppService> Content<A>.bindWhenReady(view: TreehouseView<A>): Closeable {
-  val listener = ReadyForContentChangeListener<A> {
+public fun <A : AppService> Content<A>.bindWhenReady(view: TreehouseView): Closeable {
+  val listener = ReadyForContentChangeListener {
     if (view.readyForContent) {
       bind(view)
     } else {
@@ -46,7 +46,7 @@ public fun <A : AppService> Content<A>.bindWhenReady(view: TreehouseView<A>): Cl
 }
 
 public fun <A : AppService> TreehouseContentSource<A>.bindWhenReady(
-  view: TreehouseView<A>,
+  view: TreehouseView,
   app: TreehouseApp<A>,
 ): Closeable {
   val content = app.createContent(this)
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseApp.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseApp.kt
index 02a6368e9..7243e98ee 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseApp.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseApp.kt
@@ -35,8 +35,7 @@ import okio.Path
 /**
  * This class binds downloaded code to on-screen views.
  *
- * It updates the binding when the views change via [TreehouseView.OnStateChangeListener], and when
- * new code is available in [onCodeChanged].
+ * It updates the content when new code is available in [onCodeChanged].
  */
 public class TreehouseApp<A : AppService> private constructor(
   private val factory: Factory,
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseContent.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseContent.kt
index 8d6872a48..d1a27f09f 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseContent.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseContent.kt
@@ -21,10 +21,10 @@ internal class TreehouseContent<A : AppService>(
 ) : Content<A> {
   private val dispatchers = treehouseApp.dispatchers
 
-  private var view: TreehouseView<A>? = null
+  private var view: TreehouseView? = null
   private var binding: Binding? = null
 
-  override fun bind(view: TreehouseView<A>) {
+  override fun bind(view: TreehouseView) {
     treehouseApp.dispatchers.checkUi()
 
     // Binding the bound view does nothing. This is necessary so that listeners don't need to
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt
index 7b3f35f63..694ef1d8e 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseView.kt
@@ -21,26 +21,26 @@ import app.cash.redwood.widget.Widget
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.serialization.json.Json
 
-public interface TreehouseView<A : AppService> {
+public interface TreehouseView {
   public val children: Widget.Children<*>
   public val hostConfiguration: StateFlow<HostConfiguration>
-  public val widgetSystem: WidgetSystem<A>
+  public val widgetSystem: WidgetSystem
   public val codeListener: CodeListener
   public val readyForContent: Boolean
-  public var readyForContentChangeListener: ReadyForContentChangeListener<A>?
+  public var readyForContentChangeListener: ReadyForContentChangeListener?
 
   /** Invoked when new code is loaded. This should at minimum clear all [children]. */
   public fun reset()
 
-  public fun interface ReadyForContentChangeListener<A : AppService> {
+  public fun interface ReadyForContentChangeListener {
     /** Called when [TreehouseView.readyForContent] has changed. */
-    public fun onReadyForContentChanged(view: TreehouseView<A>)
+    public fun onReadyForContentChanged(view: TreehouseView)
   }
 
-  public fun interface WidgetSystem<A : AppService> {
+  public fun interface WidgetSystem {
     /** Returns a widget factory for encoding and decoding changes to the contents of [view]. */
     public fun widgetFactory(
-      app: TreehouseApp<A>,
+      app: TreehouseApp<*>,
       json: Json,
       protocolMismatchHandler: ProtocolMismatchHandler,
     ): DiffConsumingNode.Factory<*>
diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/bindings.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/bindings.kt
index 9a1c6399c..2b16d2200 100644
--- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/bindings.kt
+++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/bindings.kt
@@ -40,8 +40,7 @@ internal object LoadingBinding : Binding {
 }
 
 /**
- * Connects a single widget, a single [TreehouseView.boundContentSource], and a single
- * [ZiplineSession].
+ * Connects a [TreehouseView], a single [TreehouseContentSource], and a single [ZiplineSession].
  *
  * Canceled if the code changes, the widget's content changes, or the widget is detached from
  * screen.
@@ -56,14 +55,14 @@ internal class RealBinding<A : AppService>(
   val eventPublisher: EventPublisher,
   val contentSource: TreehouseContentSource<A>,
   session: ZiplineSession<A>,
-  view: TreehouseView<A>,
+  view: TreehouseView,
 ) : Binding, EventSink, DiffSinkService {
   private val isInitialLaunch: Boolean = session.isInitialLaunch
 
   private val bindingScope = CoroutineScope(SupervisorJob(appScope.coroutineContext.job))
 
   /** Only accessed on [TreehouseDispatchers.ui]. Null after [cancel]. */
-  private var viewOrNull: TreehouseView<A>? = view
+  private var viewOrNull: TreehouseView? = view
 
   /** Only accessed on [TreehouseDispatchers.ui]. Null after [cancel]. */
   @Suppress("UNCHECKED_CAST") // We don't have a type parameter for the widget type.
@@ -112,7 +111,7 @@ internal class RealBinding<A : AppService>(
     }
   }
 
-  fun start(session: ZiplineSession<A>, view: TreehouseView<A>) {
+  fun start(session: ZiplineSession<A>, view: TreehouseView) {
     bindingScope.launch(app.dispatchers.zipline) {
       val scopedAppService = session.appService.withScope(ziplineScope)
       val treehouseUi = contentSource.get(scopedAppService)
diff --git a/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIKitView.kt b/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIKitView.kt
index 600248d5d..c5b32d0f6 100644
--- a/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIKitView.kt
+++ b/redwood-treehouse-host/src/iosMain/kotlin/app/cash/redwood/treehouse/TreehouseUIKitView.kt
@@ -17,9 +17,9 @@ package app.cash.redwood.treehouse
 
 import app.cash.redwood.treehouse.TreehouseView.CodeListener
 import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.widget.UIViewChildren
 import app.cash.redwood.widget.Widget
-import kotlin.DeprecationLevel.ERROR
 import kotlinx.cinterop.ObjCAction
 import kotlinx.cinterop.cValue
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,21 +33,13 @@ import platform.UIKit.setFrame
 import platform.UIKit.subviews
 import platform.UIKit.superview
 
-public class TreehouseUIKitView<A : AppService>(
-  override val widgetSystem: TreehouseView.WidgetSystem<A>,
-) : TreehouseView<A> {
-  @Deprecated(
-    message = "TreehouseView no longer owns a TreehouseApp. Instead, call app.renderTo(view).",
-    replaceWith = ReplaceWith("TreehouseUIKitView(widgetSystem).also(treehouseApp::renderTo)"),
-    level = ERROR,
-  )
-  @Suppress("UNUSED_PARAMETER")
-  public constructor(treehouseApp: TreehouseApp<A>, widgetSystem: TreehouseView.WidgetSystem<A>) : this(widgetSystem)
-
+public class TreehouseUIKitView(
+  override val widgetSystem: WidgetSystem,
+) : TreehouseView {
   public val view: UIView = RootUiView(this)
   public override var codeListener: CodeListener = CodeListener()
 
-  override var readyForContentChangeListener: ReadyForContentChangeListener<A>? = null
+  override var readyForContentChangeListener: ReadyForContentChangeListener? = null
     set(value) {
       check(value == null || field == null) { "View already bound to a listener" }
       field = value
@@ -92,7 +84,7 @@ private fun computeHostConfiguration(
 
 @Suppress("unused") // cinterop erroneously exposes these as extension functions.
 private class RootUiView(
-  private val treehouseView: TreehouseUIKitView<*>,
+  private val treehouseView: TreehouseUIKitView,
 ) : UIView(cValue { CGRectZero }) {
   @ObjCAction fun layoutSubviews() {
     subviews.forEach {
diff --git a/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiLazyColumn.kt b/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiLazyColumn.kt
index 4ac73f4f0..d7e59db54 100644
--- a/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiLazyColumn.kt
+++ b/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiLazyColumn.kt
@@ -29,14 +29,14 @@ import androidx.compose.ui.unit.dp
 import app.cash.redwood.LayoutModifier
 import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.composeui.TreehouseContent
 import app.cash.redwood.treehouse.lazylayout.api.LazyListIntervalContent
 import app.cash.redwood.treehouse.lazylayout.widget.LazyColumn
 
 internal class ComposeUiLazyColumn<A : AppService>(
   treehouseApp: TreehouseApp<A>,
-  widgetSystem: TreehouseView.WidgetSystem<A>,
+  widgetSystem: WidgetSystem,
 ) : LazyColumn<@Composable () -> Unit> {
   private var intervals by mutableStateOf<List<LazyListIntervalContent>>(emptyList())
 
diff --git a/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt b/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt
index 2c6595ec4..c7b049416 100644
--- a/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt
+++ b/redwood-treehouse-lazylayout-composeui/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/composeui/ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory.kt
@@ -18,13 +18,13 @@ package app.cash.redwood.treehouse.lazylayout.composeui
 import androidx.compose.runtime.Composable
 import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.lazylayout.widget.LazyColumn
 import app.cash.redwood.treehouse.lazylayout.widget.RedwoodTreehouseLazyLayoutWidgetFactory
 
 public class ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory<A : AppService>(
   private val treehouseApp: TreehouseApp<A>,
-  private val widgetSystem: TreehouseView.WidgetSystem<A>,
+  private val widgetSystem: WidgetSystem,
 ) : RedwoodTreehouseLazyLayoutWidgetFactory<@Composable () -> Unit> {
   override fun LazyColumn(): LazyColumn<@Composable () -> Unit> = ComposeUiLazyColumn(treehouseApp, widgetSystem)
 }
diff --git a/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewLazyColumn.kt b/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewLazyColumn.kt
index 6b1e40627..c99832cf7 100644
--- a/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewLazyColumn.kt
+++ b/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewLazyColumn.kt
@@ -20,7 +20,7 @@ import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
 import app.cash.redwood.treehouse.TreehouseContentSource
 import app.cash.redwood.treehouse.TreehouseUIKitView
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.ZiplineTreehouseUi
 import app.cash.redwood.treehouse.bindWhenReady
 import app.cash.redwood.treehouse.lazylayout.api.LazyListIntervalContent
@@ -40,7 +40,7 @@ import platform.darwin.NSInteger
 
 internal class UIViewLazyColumn<A : AppService>(
   treehouseApp: TreehouseApp<A>,
-  widgetSystem: TreehouseView.WidgetSystem<A>,
+  widgetSystem: WidgetSystem,
 ) : LazyColumn<UIView> {
   private val dataSource = TableViewDataSource(treehouseApp, widgetSystem)
   private val root = UITableView().apply {
@@ -59,7 +59,7 @@ internal class UIViewLazyColumn<A : AppService>(
 
 private class TableViewDataSource<A : AppService>(
   private val treehouseApp: TreehouseApp<A>,
-  private val widgetSystem: TreehouseView.WidgetSystem<A>,
+  private val widgetSystem: WidgetSystem,
 ) : UITableViewDiffableDataSource() {
   var intervals = emptyList<LazyListIntervalContent>()
 
diff --git a/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewRedwoodLayoutWidgetFactory.kt b/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewRedwoodLayoutWidgetFactory.kt
index 9689922ea..6cc91eb72 100644
--- a/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewRedwoodLayoutWidgetFactory.kt
+++ b/redwood-treehouse-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/treehouse/lazylayout/uiview/UIViewRedwoodLayoutWidgetFactory.kt
@@ -17,14 +17,14 @@ package app.cash.redwood.treehouse.lazylayout.uiview
 
 import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.lazylayout.widget.LazyColumn
 import app.cash.redwood.treehouse.lazylayout.widget.RedwoodTreehouseLazyLayoutWidgetFactory
 import platform.UIKit.UIView
 
 public class UIViewRedwoodTreehouseLazyLayoutWidgetFactory<A : AppService>(
   private var treehouseApp: TreehouseApp<A>,
-  private var widgetSystem: TreehouseView.WidgetSystem<A>,
+  private var widgetSystem: WidgetSystem,
 ) : RedwoodTreehouseLazyLayoutWidgetFactory<UIView> {
   override fun LazyColumn(): LazyColumn<UIView> = UIViewLazyColumn(treehouseApp, widgetSystem)
 }
diff --git a/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewLazyColumn.kt b/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewLazyColumn.kt
index 96f82d9d5..454f4b940 100644
--- a/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewLazyColumn.kt
+++ b/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewLazyColumn.kt
@@ -30,7 +30,7 @@ import app.cash.redwood.LayoutModifier
 import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
 import app.cash.redwood.treehouse.TreehouseContentSource
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.TreehouseWidgetView
 import app.cash.redwood.treehouse.bindWhenReady
 import app.cash.redwood.treehouse.lazylayout.api.LazyListIntervalContent
@@ -44,7 +44,7 @@ private data class LazyContentItem(
 
 internal class ViewLazyColumn<A : AppService>(
   treehouseApp: TreehouseApp<A>,
-  widgetSystem: TreehouseView.WidgetSystem<A>,
+  widgetSystem: WidgetSystem,
   override val value: RecyclerView,
 ) : LazyColumn<View> {
   override var layoutModifiers: LayoutModifier = LayoutModifier
@@ -79,17 +79,17 @@ internal class ViewLazyColumn<A : AppService>(
 
   private class LazyContentItemListAdapter<A : AppService>(
     private val treehouseApp: TreehouseApp<A>,
-    private val widgetSystem: TreehouseView.WidgetSystem<A>,
+    private val widgetSystem: WidgetSystem,
     private val contentHeight: Int,
-  ) : ListAdapter<LazyContentItem, ViewHolder<A>>(LazyContentItemDiffCallback) {
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<A> {
+  ) : ListAdapter<LazyContentItem, ViewHolder>(LazyContentItemDiffCallback) {
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
       val container = FrameLayout(parent.context).apply {
         layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, contentHeight)
       }
       return ViewHolder(container, widgetSystem)
     }
 
-    override fun onBindViewHolder(holder: ViewHolder<A>, position: Int) {
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
       val itemContent = currentList[position]
       val itemContentSource = TreehouseContentSource<A> {
         itemContent.item.get(itemContent.index)
@@ -103,9 +103,9 @@ internal class ViewLazyColumn<A : AppService>(
     }
   }
 
-  private class ViewHolder<A : AppService>(
+  private class ViewHolder(
     container: FrameLayout,
-    widgetSystem: TreehouseView.WidgetSystem<A>,
+    widgetSystem: WidgetSystem,
   ) : RecyclerView.ViewHolder(container) {
     var widgetContentBinding: Closeable? = null
     val treehouseWidgetView = TreehouseWidgetView(container.context, widgetSystem)
diff --git a/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewRedwoodTreehouseLazyLayoutWidgetFactory.kt b/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewRedwoodTreehouseLazyLayoutWidgetFactory.kt
index 8a3503e62..ca1f04c54 100644
--- a/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewRedwoodTreehouseLazyLayoutWidgetFactory.kt
+++ b/redwood-treehouse-lazylayout-view/src/main/kotlin/app/cash/redwood/treehouse/lazylayout/view/ViewRedwoodTreehouseLazyLayoutWidgetFactory.kt
@@ -20,14 +20,14 @@ import android.view.View
 import androidx.recyclerview.widget.RecyclerView
 import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.TreehouseApp
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.lazylayout.widget.LazyColumn
 import app.cash.redwood.treehouse.lazylayout.widget.RedwoodTreehouseLazyLayoutWidgetFactory
 
 public class ViewRedwoodTreehouseLazyLayoutWidgetFactory<A : AppService>(
   private val context: Context,
   private val treehouseApp: TreehouseApp<A>,
-  private val widgetSystem: TreehouseView.WidgetSystem<A>,
+  private val widgetSystem: WidgetSystem,
 ) : RedwoodTreehouseLazyLayoutWidgetFactory<View> {
   public override fun LazyColumn(): LazyColumn<View> = ViewLazyColumn(treehouseApp, widgetSystem, RecyclerView(context))
 }
diff --git a/samples/emoji-search/android-composeui/src/main/java/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt b/samples/emoji-search/android-composeui/src/main/java/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt
index 790a960e1..196fa4c95 100644
--- a/samples/emoji-search/android-composeui/src/main/java/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt
+++ b/samples/emoji-search/android-composeui/src/main/java/com/example/redwood/emojisearch/android/composeui/EmojiSearchActivity.kt
@@ -26,7 +26,7 @@ import app.cash.redwood.protocol.widget.ProtocolMismatchHandler
 import app.cash.redwood.treehouse.TreehouseApp
 import app.cash.redwood.treehouse.TreehouseAppFactory
 import app.cash.redwood.treehouse.TreehouseContentSource
-import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.composeui.TreehouseContent
 import app.cash.redwood.treehouse.lazylayout.composeui.ComposeUiRedwoodTreehouseLazyLayoutWidgetFactory
 import app.cash.zipline.loader.ManifestVerifier
@@ -52,9 +52,9 @@ class EmojiSearchActivity : ComponentActivity() {
     val treehouseApp = createTreehouseApp()
     val treehouseContentSource = TreehouseContentSource(EmojiSearchPresenter::launch)
 
-    val widgetSystem = object : TreehouseView.WidgetSystem<EmojiSearchPresenter> {
+    val widgetSystem = object : WidgetSystem {
       override fun widgetFactory(
-        app: TreehouseApp<EmojiSearchPresenter>,
+        app: TreehouseApp<*>,
         json: Json,
         protocolMismatchHandler: ProtocolMismatchHandler,
       ) = EmojiSearchDiffConsumingNodeFactory<@Composable () -> Unit>(
diff --git a/samples/emoji-search/android-views/src/main/java/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt b/samples/emoji-search/android-views/src/main/java/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt
index 3042f1392..d27023a9d 100644
--- a/samples/emoji-search/android-views/src/main/java/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt
+++ b/samples/emoji-search/android-views/src/main/java/com/example/redwood/emojisearch/android/views/EmojiSearchActivity.kt
@@ -50,9 +50,9 @@ class EmojiSearchActivity : ComponentActivity() {
     val treehouseApp = createTreehouseApp()
     val treehouseContentSource = TreehouseContentSource(EmojiSearchPresenter::launch)
 
-    val widgetSystem = object : TreehouseView.WidgetSystem<EmojiSearchPresenter> {
+    val widgetSystem = object : TreehouseView.WidgetSystem {
       override fun widgetFactory(
-        app: TreehouseApp<EmojiSearchPresenter>,
+        app: TreehouseApp<*>,
         json: Json,
         protocolMismatchHandler: ProtocolMismatchHandler,
       ) = EmojiSearchDiffConsumingNodeFactory(
diff --git a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt
index c32beb156..48d6da274 100644
--- a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt
+++ b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/exposed.kt
@@ -23,6 +23,7 @@ import app.cash.redwood.treehouse.AppService
 import app.cash.redwood.treehouse.Content
 import app.cash.redwood.treehouse.TreehouseUIKitView
 import app.cash.redwood.treehouse.TreehouseView
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.treehouse.bindWhenReady
 import app.cash.redwood.treehouse.lazylayout.uiview.UIViewRedwoodTreehouseLazyLayoutWidgetFactory
 import com.example.redwood.emojisearch.widget.EmojiSearchDiffConsumingNodeFactory
@@ -37,10 +38,10 @@ import platform.Foundation.NSData
 fun exposedTypes(
   emojiSearchLauncher: EmojiSearchLauncher,
   emojiSearchWidgetFactory: EmojiSearchWidgetFactory<*>,
-  treehouseUIKitView: TreehouseUIKitView<*>,
+  treehouseUIKitView: TreehouseUIKitView,
   uiViewRedwoodLayoutWidgetFactory: UIViewRedwoodLayoutWidgetFactory,
   uiViewRedwoodTreehouseLazyLayoutWidgetFactory: UIViewRedwoodTreehouseLazyLayoutWidgetFactory<*>,
-  widgetSystem: TreehouseView.WidgetSystem<*>,
+  widgetSystem: WidgetSystem,
   widgetFactories: EmojiSearchWidgetFactories<*>,
   diffConsumingNodeFactory: EmojiSearchDiffConsumingNodeFactory<*>,
 ) {
@@ -53,5 +54,5 @@ fun layoutModifier(): LayoutModifier = LayoutModifier
 
 fun <A : AppService> bindWhenReady(
   content: Content<A>,
-  view: TreehouseView<A>,
+  view: TreehouseView,
 ): Closeable = content.bindWhenReady(view)
diff --git a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift
index bf6f4bffb..e877b1922 100644
--- a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift
+++ b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift
@@ -36,7 +36,7 @@ class EmojiSearchViewController : UIViewController {
         let emojiSearchLauncher = EmojiSearchLauncher(nsurlSession: urlSession, hostApi: IosHostApi())
         let treehouseApp = emojiSearchLauncher.createTreehouseApp()
         let widgetSystem = EmojiSearchWidgetSystem()
-        let treehouseView = Redwood_treehouse_hostTreehouseUIKitView<Presenter_treehouseEmojiSearchPresenter>(widgetSystem: widgetSystem)
+        let treehouseView = Redwood_treehouse_hostTreehouseUIKitView(widgetSystem: widgetSystem)
         let content = treehouseApp.createContent(source: EmojiSearchContent())
         ExposedKt.bindWhenReady(content: content, view: treehouseView)
         view = treehouseView.view
@@ -51,7 +51,8 @@ class EmojiSearchContent : Redwood_treehouse_hostTreehouseContentSource {
 }
 
 class EmojiSearchWidgetSystem : Redwood_treehouse_hostTreehouseViewWidgetSystem {
-    func widgetFactory(app: Redwood_treehouse_hostTreehouseApp<Redwood_treehouseAppService>, json: Kotlinx_serialization_jsonJson, protocolMismatchHandler: Redwood_protocol_widgetProtocolMismatchHandler) -> Redwood_protocol_widgetDiffConsumingNodeFactory {
+    func widgetFactory(app: Redwood_treehouse_hostTreehouseApp<AnyObject>, json: Kotlinx_serialization_jsonJson, protocolMismatchHandler:
+        Redwood_protocol_widgetProtocolMismatchHandler) -> Redwood_protocol_widgetDiffConsumingNodeFactory {
         return ProtocolEmojiSearchDiffConsumingNodeFactory<UIView>(
             provider: WidgetEmojiSearchWidgetFactories<UIView>(
                 EmojiSearch: IosEmojiSearchWidgetFactory(treehouseApp: app, widgetSystem: self),


Test patch:
diff --git a/redwood-treehouse-host/src/androidInstrumentedTest/kotlin/app/cash/redwood/treehouse/TreehouseWidgetViewTest.kt b/redwood-treehouse-host/src/androidInstrumentedTest/kotlin/app/cash/redwood/treehouse/TreehouseWidgetViewTest.kt
index 15a4e8529..7e080cb74 100644
--- a/redwood-treehouse-host/src/androidInstrumentedTest/kotlin/app/cash/redwood/treehouse/TreehouseWidgetViewTest.kt
+++ b/redwood-treehouse-host/src/androidInstrumentedTest/kotlin/app/cash/redwood/treehouse/TreehouseWidgetViewTest.kt
@@ -22,6 +22,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
 import android.view.View
 import android.view.ViewGroup
 import app.cash.redwood.LayoutModifier
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.widget.ViewGroupChildren
 import app.cash.redwood.widget.Widget
 import app.cash.turbine.test
@@ -118,5 +119,5 @@ class TreehouseWidgetViewTest {
   }
 
   private val throwingWidgetSystem =
-    TreehouseView.WidgetSystem<Nothing> { _, _, _ -> throw UnsupportedOperationException() }
+    WidgetSystem { _, _, _ -> throw UnsupportedOperationException() }
 }
diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt
index 62f5b0af7..4d87a62dd 100644
--- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt
+++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/CountingReadyForContentChangeListener.kt
@@ -17,10 +17,10 @@ package app.cash.redwood.treehouse
 
 import app.cash.redwood.treehouse.TreehouseView.ReadyForContentChangeListener
 
-class CountingReadyForContentChangeListener : ReadyForContentChangeListener<Nothing> {
+class CountingReadyForContentChangeListener : ReadyForContentChangeListener {
   var count = 0
 
-  override fun onReadyForContentChanged(view: TreehouseView<Nothing>) {
+  override fun onReadyForContentChanged(view: TreehouseView) {
     count++
   }
 }
diff --git a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIKitViewTest.kt b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIKitViewTest.kt
index 6347e48d9..2f13d4599 100644
--- a/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIKitViewTest.kt
+++ b/redwood-treehouse-host/src/iosTest/kotlin/app/cash/redwood/treehouse/TreehouseUIKitViewTest.kt
@@ -16,6 +16,7 @@
 package app.cash.redwood.treehouse
 
 import app.cash.redwood.LayoutModifier
+import app.cash.redwood.treehouse.TreehouseView.WidgetSystem
 import app.cash.redwood.widget.UIViewChildren
 import app.cash.redwood.widget.Widget
 import app.cash.turbine.test
@@ -119,5 +120,5 @@ class TreehouseUIKitViewTest {
   }
 
   private val throwingWidgetSystem =
-    TreehouseView.WidgetSystem<Nothing> { _, _, _ -> throw UnsupportedOperationException() }
+    WidgetSystem { _, _, _ -> throw UnsupportedOperationException() }
 }