Auto-UI-Plug-ins

Verwenden Sie Plug-ins der Auto-UI-Bibliothek, um vollständige Implementierungen der Komponente zu erstellen. Anpassungen in der Auto-UI-Bibliothek vornehmen, anstatt Laufzeitressourcen-Overlays zu verwenden (RROs). Mit RROs können Sie nur die XML-Ressourcen der Car-UI-Bibliothek ändern -Komponenten, was den Umfang der Anpassungen begrenzt.

Plug-in erstellen

Ein Car UI Library-Plug-in ist ein APK, das Klassen enthält, die eine Reihe von Plug-in-APIs Die Plug-in-APIs können zu einem Plug-in als statische Bibliothek.

Beispiele in Soong und Gradle:

Lieder

Betrachten Sie dieses Soong-Beispiel:

android_app {
    name: "my-plugin",

    min_sdk_version: "28",
    target_sdk_version: "30",
    aaptflags: ["--shared-lib"],
    sdk_version: "current",

    manifest: "src/main/AndroidManifest.xml",
    srcs: ["src/main/java/**/*.java"],
    resource_dirs: ["src/main/res"],
    static_libs: [
        "car-ui-lib-oem-apis",
    ],
    // Disable optimization is mandatory to prevent R.java class from being
    // stripped out
    optimize: {
        enabled: false,
    },

    certificate: ":my-plugin-certificate",
}

Gradle

Sehen Sie sich diese build.gradle-Datei an:

apply plugin: 'com.android.application'

android {
  compileSdkVersion 30

  defaultConfig {
    minSdkVersion 28
    targetSdkVersion 30
  }

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

  signingConfigs {
    debug {
      storeFile file('chassis_upload_key.jks')
      storePassword 'chassis'
      keyAlias 'chassis'
      keyPassword 'chassis'
    }
  }
}

dependencies {
  implementation project(':oem-apis')
  // Or use the following if you'd like to use the maven artifact
  // implementation 'com.android.car.ui:car-ui-lib-plugin-apis:1.0.0'
}

Settings.gradle:

// You can remove the ':oem-apis' if you're using the maven artifact.
include ':oem-apis'
project(':oem-apis').projectDir = new File('./path/to/oem-apis')
include ':my-plugin'
project(':my-plugin').projectDir = new File('./my-plugin')

Im Manifest des Plug-ins muss ein Contentanbieter angegeben sein, der den Parameter folgende Attribute:

  android:authorities="com.android.car.ui.plugin"
  android:enabled="true"
  android:exported="true"

android:authorities="com.android.car.ui.plugin" macht das Plug-in sichtbar zur Auto-UI-Bibliothek hinzu. Der Anbieter muss exportiert werden, damit er abgefragt werden kann Laufzeit. Wenn das Attribut enabled auf false gesetzt ist, gilt außerdem der Standardwert statt der Plug-in-Implementierung verwendet. Inhalt muss nicht vorhanden sein. Fügen Sie in diesem Fall tools:ignore="MissingClass" der Anbieterdefinition. Beispiel ansehen Manifesteintrag unten:

    <application>
        <provider
            android:name="com.android.car.ui.plugin.PluginNameProvider"
            android:authorities="com.android.car.ui.plugin"
            android:enabled="false"
            android:exported="true"
            tools:ignore="MissingClass"/>
    </application>

Als Sicherheitsmaßnahme Signieren Sie Ihre App.

Plug-ins als gemeinsam genutzte Bibliothek

Im Gegensatz zu statischen Android-Bibliotheken, die direkt in Apps kompiliert werden, Geteilte Android-Bibliotheken werden zu einem eigenständigen APK kompiliert, auf das verwiesen wird. von anderen Apps zur Laufzeit.

Plug-ins, die als gemeinsam genutzte Android-Bibliothek implementiert sind, haben ihre Klassen. automatisch zwischen Apps zum freigegebenen Classloader hinzugefügt. Wenn eine App, die die Car UI-Bibliothek verwendet, gibt Laufzeitabhängigkeit von der gemeinsam genutzten Plug-in-Bibliothek, ihrer Classloader kann auf die Klassen der freigegebenen Plug-in-Bibliothek zugreifen. Implementierte Plug-ins da normale Android-Apps (keine gemeinsam genutzte Bibliothek) sich negativ auf Startzeiten festzulegen.

Gemeinsam genutzte Bibliotheken implementieren und erstellen

Die Entwicklung mit gemeinsam genutzten Android-Bibliotheken ähnelt der normalen Android-Version mit einigen wichtigen Unterschieden.

  • Verwenden Sie das Tag library unter dem Tag application mit dem Plug-in-Paket Name im App-Manifest Ihres Plug-ins:
    <application>
        <library android:name="com.chassis.car.ui.plugin" />
        ...
    </application>
  • Soong-android_app-Build-Regel (Android.bp) mit AAPT konfigurieren Das Flag shared-lib, das zum Erstellen einer gemeinsam genutzten Bibliothek verwendet wird:
android_app {
  ...
  aaptflags: ["--shared-lib"],
  ...
}

Abhängigkeiten von gemeinsam genutzten Bibliotheken

Geben Sie für jede App im System, die die Car UI-Bibliothek verwendet, den Wert uses-library im App-Manifest unter der application-Tag mit dem Plug-in-Paketnamen:

<manifest>
  <application
      android:name=".MyApp"
      ...>
    <uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
    ...
  </application>
</manifest>

Plug-in installieren

Plug-ins MÜSSEN auf der Systempartition durch Hinzufügen des Moduls vorinstalliert sein in PRODUCT_PACKAGES Das vorinstallierte Paket kann ähnlich aktualisiert werden einer anderen installierten App.

Wenn Sie ein vorhandenes Plug-in auf dem System aktualisieren, werden alle Apps, die dieses Plug-in verwenden, automatisch geschlossen werden. Nach dem erneuten Öffnen durch den Nutzer werden die aktualisierten Änderungen übernommen. Wenn die App nicht ausgeführt wurde, sind beim nächsten Start die aktualisierten .

Wenn Sie ein Plug-in mit Android Studio installieren, sind einige zusätzliche Überlegungen zu berücksichtigen sind. Beim Verfassen dieses Artikels trat ein Fehler in Installationsprozess der Android Studio App, der Updates für ein Plug-in verursacht nicht in Kraft treten. Sie können das Problem beheben, indem Sie die Option Immer installieren mit Paketmanager (deaktiviert die Bereitstellungsoptimierungen unter Android 11 und höher) in der Build-Konfiguration des Plug-ins.

Außerdem meldet Android Studio bei der Installation des Plug-ins einen Fehler, keine Hauptaktivität für den Start finden. Das ist zu erwarten, da das Plug-in keine Aktivitäten enthalten (außer dem leeren Intent, der zur Auflösung eines Intents verwendet wird). Bis Beheben Sie den Fehler, indem Sie die Option Launch im Build in Nothing ändern Konfiguration.

Plug-in Android Studio-Konfiguration Abbildung 1: Plug-in Android Studio-Konfiguration

Proxy-Plug-in

Anpassung von mit der Car UI Library erfordert eine RRO, die auf jede zu ändernde App abzielt, auch wenn die Personalisierungen in allen Apps identisch sind. Das bedeutet eine RRO pro App ist erforderlich. Sieh dir an, welche Apps die Car UI-Bibliothek verwenden.

Das Proxy-Plug-in für die Auto-UI-Bibliothek ist ein Beispiel. der gemeinsam genutzten Plug-in-Bibliothek, die ihre Komponentenimplementierungen an den statischen der Auto-UI-Bibliothek. Dieses Plug-in kann mit einem RRO angesteuert werden, Wird als einzelner Anpassungspunkt für Apps verwendet, die die Car-UI-Bibliothek verwenden ohne ein funktionierendes Plug-in implementieren zu müssen. Weitere Informationen zu RROs finden Sie unter Ändern des Werts von Ressourcen einer App unter Laufzeit.

Das Proxy-Plug-in ist nur ein Beispiel und Ausgangspunkt für die Anpassung mithilfe von ein Plug-in. Für Anpassungen über RROs hinaus kann eine Teilmenge von Plug-ins und das Proxy-Plug-in für den Rest verwenden, oder alle Plug-ins komplett von Grund auf neu.

Das Proxy-Plug-in bietet einen zentralen Punkt der RRO-Anpassung für Anwendungen, Für Apps, bei denen das Plug-in deaktiviert ist, ist weiterhin eine RRO erforderlich, die direkt auf die App selbst ausgerichtet ist.

Plug-in-APIs implementieren

Der Haupteinstiegspunkt für das Plug-in ist das Klasse com.android.car.ui.plugin.PluginVersionProviderImpl. Alle Plug-ins müssen eine Klasse mit genau diesem Namen und Paketnamen enthalten. Dieser Kurs muss eine Standardkonstruktor und implementieren die PluginVersionProviderOEMV1-Schnittstelle.

CarUi-Plug-ins müssen mit Apps funktionieren, die älter oder neuer als das Plug-in sind. Bis alle Plug-in-APIs mit einem V# am Ende ihrer Klassenname. Wenn eine neue Version der Auto-UI-Bibliothek mit neuen Funktionen veröffentlicht wird, Sie sind Teil der V2-Version der Komponente. Die Car UI Library um neue Funktionen im Rahmen einer älteren Plug-in-Komponente zum Laufen zu bringen. Beispielsweise durch Konvertieren einer neuen Art von Schaltfläche in der Symbolleiste in MenuItems.

Eine App mit einer älteren Version der Auto-UI-Bibliothek kann jedoch nicht an eine neue Plug-in, das für neuere APIs geschrieben wurde. Um dieses Problem zu lösen, erlauben wir Plug-ins, Rückgabe unterschiedlicher Implementierungen auf Basis der Version der OEM API die von den Apps unterstützt werden.

PluginVersionProviderOEMV1 enthält eine Methode:

Object getPluginFactory(int maxVersion, Context context, String packageName);

Diese Methode gibt ein Objekt zurück, das die höchste Version von implementiert. PluginFactoryOEMV# werden vom Plug-in unterstützt, sind aber kleiner als oder gleich maxVersion. Wenn ein Plug-in keine PluginFactory zurückgibt, wird möglicherweise null zurückgegeben. In diesem Fall wird das statisch- verknüpfte CarUi-Komponenten verwendet werden.

Aufrechterhaltung der Abwärtskompatibilität mit kompilierten Apps Versionen der statischen Auto-UI-Bibliothek verwenden, wird empfohlen, maxVersion von 2, 5 und höher aus der Plug-in-Implementierung von die Klasse PluginVersionProvider. Die Versionen 1, 3 und 4 werden nicht unterstützt. Für finden Sie unter PluginVersionProviderImpl

PluginFactory ist die Schnittstelle, mit der alle anderen CarUi erstellt werden. Komponenten. Außerdem wird definiert, welche Version der Benutzeroberflächen verwendet werden soll. Wenn nicht versucht, diese Komponenten zu implementieren, null in der Erstellungsfunktion verwenden (mit Ausnahme der Symbolleiste, die separate customizesBaseLayout()-Funktion).

Die pluginFactory schränkt ein, welche Versionen von CarUi-Komponenten verwendet werden können miteinander verbinden. Zum Beispiel wird es niemals ein pluginFactory geben, das Version 100 einer Toolbar und auch Version 1 von RecyclerView, wie es keine Garantie dafür, dass eine große Vielfalt von Komponentenversionen zusammenarbeiten können. Um die Toolbar-Version 100 zu verwenden, müssen Entwickler Implementierung einer Version von pluginFactory bereitstellen, die ein Toolbar-Version 100, wodurch die Optionen auf den Versionen anderer Komponenten, die erstellt werden können. Die Versionen anderer Komponenten ist, könnte z. B. mit pluginFactoryOEMV100 ein ToolbarControllerOEMV100 und RecyclerViewOEMV70.

Symbolleiste

Basislayout

Die Symbolleiste und das „Basislayout“ sind eng verwandt, daher ist die Funktion das die Symbolleiste erstellt, heißt installBaseLayoutAround. Die Basislayout ist ein Konzept, bei dem die Symbolleiste überall um die Inhalt, um eine Symbolleiste oben/unten in der App zu ermöglichen, vertikal an den Seiten oder sogar in einer runden Symbolleiste, die die gesamte App einschließt. Dies ist erreicht, indem eine Ansicht für die Symbolleiste/Basis an installBaseLayoutAround übergeben wird das Layout zu kürzen.

Das Plug-in sollte die angegebene Ansicht abrufen, vom übergeordneten Element trennen, Plug-in-Layout im selben Index des übergeordneten Elements und mit demselben LayoutParams als Ansicht, die gerade getrennt wurde, und hängen Sie sie dann wieder an. irgendwo im Layout, das einfach aufgebläht war. Das aufgeblähte Layout die die Symbolleiste enthalten, wenn dies von der App angefordert wird.

Die App kann ein Basislayout ohne Symbolleiste anfordern. Ist dies der Fall, installBaseLayoutAround sollte null zurückgeben. Bei den meisten Plug-ins ist das erforderlich ist, aber wenn der Plug-in-Autor z.B. eine Dekoration am Rand der App platzieren, könnte aber auch mit einem Basislayout durchgeführt werden. Diese Dekorationen sind besonders nützlich für Geräte mit nicht rechteckigen Bildschirmen, da können sie die App in einen rechteckigen Bereich verschieben und klare Übergänge hinzufügen. nicht rechteckigen Bereich.

installBaseLayoutAround wird auch ein Consumer<InsetsOEMV1> übergeben. Dieses kann verwendet werden, um der Anwendung mitzuteilen, dass das Plug-in teilweise installiert ist. App-Inhalt über die Symbolleiste oder anderweitig abdeckt. Die App wird können Sie in diesem Bereich weiter zeichnen, aber alle wichtigen Komponenten daraus. Dieser Effekt wird in unserem Referenzdesign verwendet, in der Symbolleiste halbtransparent angezeigt wird und Listen darunter scrollen. Bisherige nicht implementiert wurde, würde das erste Element in einer Liste unter der Symbolleiste hängen bleiben. und nicht anklickbar. Wenn dieser Effekt nicht benötigt wird, kann das Plug-in die Verbraucher:

Scrollen unter der Symbolleiste Abbildung 2: Scrollen unter der Symbolleiste

Aus Sicht der App erhält das Plug-in neue Einfügungen aus allen Aktivitäten oder Fragmenten, die InsetsChangedListener implementieren. Wenn für eine Aktivität oder ein Fragment InsetsChangedListener, die Auto-UI nicht implementiert wird werden in der Bibliothek Einsätze standardmäßig verarbeitet, indem die Einsätze als Auffüllung auf den Activity oder FragmentActivity, die das Fragment enthalten. Die Bibliothek enthält keine werden die Einsätze standardmäßig auf Fragmente angewendet. Hier sehen Sie ein Beispiel-Snippet -Implementierung, die die Einfügungen als Abstand auf ein RecyclerView im App:

public class MainActivity extends Activity implements InsetsChangedListener {
  @Override
  public void onCarUiInsetsChanged(Insets insets) {
    CarUiRecyclerView rv = requireViewById(R.id.recyclerview);
    rv.setPadding(insets.getLeft(), insets.getTop(),
                  insets.getRight(), insets.getBottom());
  }
}

Schließlich erhält das Plug-in einen fullscreen-Hinweis, mit dem angegeben wird, Die zu gruppierende Ansicht nimmt die gesamte App oder nur einen kleinen Bereich ein. So können Sie vermeiden, dass entlang der Kante Verzierungen angebracht werden, ergeben nur dann Sinn, wenn sie am Rand des gesamten Bildschirms erscheinen. Beispiel App, die Basislayouts verwendet, die nicht im Vollbildmodus angezeigt werden, sind die Einstellungen, in denen jeder Bereich des Das Dual-Fenster-Layout verfügt über eine eigene Symbolleiste.

Da erwartet wird, dass installBaseLayoutAround null zurückgibt, toolbarEnabled ist false, damit das Plug-in angibt, das Basislayout anpassen möchten, muss false von customizesBaseLayout.

Das Basislayout muss FocusParkingView und FocusArea enthalten, damit unterstützen Drehknöpfe. Diese Ansichten können auf Geräten weggelassen werden, unterstützen keinen Drehknopf. Die FocusParkingView/FocusAreas werden in der statische CarUi-Bibliothek, sodass eine setRotaryFactories verwendet wird, um Fabriken die Ansichten aus Kontexten erstellen.

Die Kontexte, die zum Erstellen von Fokusansichten verwendet werden, müssen der Quellkontext und nicht der Kontext des Plug-ins. FocusParkingView sollte der ersten Ansicht am nächsten sein. im Baum soweit möglich wie möglich, da dies der Fokus ist, für die Nutzenden nicht sichtbar sein. Der FocusArea muss die Symbolleiste im Basislayout, das angibt, dass es sich um eine angestoßene Drehzone handelt. Wenn FocusArea nicht ist es nicht möglich, mit der Funktion Drehregler.

Symbolleisten-Controller

Die tatsächliche ToolbarController, die zurückgegeben wird, sollte viel einfacher zu finden sein. als das Basislayout. Seine Aufgabe besteht darin, Informationen, die an seine eigene und im Basislayout anzuzeigen. Im Javadoc finden Sie Informationen zu für die meisten Methoden. Einige der komplexeren Methoden werden nachfolgend erläutert.

getImeSearchInterface wird zum Anzeigen von Suchergebnissen im IME (Tastatur) verwendet . Dies kann nützlich sein, um Suchergebnisse neben dem z. B. wenn die Tastatur nur die Hälfte des Bildschirms einnimmt. Die meisten die Funktionalität in der statischen CarUi-Bibliothek implementiert ist, im Plug-in gibt es nur Methoden für die statische Bibliothek, TextView- und onPrivateIMECommand-Callbacks. Um dies zu unterstützen, muss das Plug-in sollte eine abgeleitete TextView-Klasse verwenden, die onPrivateIMECommand und Karten/Tickets überschreibt Den Aufruf an den angegebenen Listener als TextView seiner Suchleiste.

setMenuItems zeigt einfach Menüelemente auf dem Bildschirm an, wird jedoch überraschend oft. Da die Plug-in-API für MenuItems unveränderlich ist, wird jedes Mal, wenn ein MenuItem geändert wird, erfolgt ein komplett neuer setMenuItems-Aufruf. Dies könnte für etwas so Einfaches wie das Klicken auf ein Switch-MenuItem. wurde die Umschaltfunktion aktiviert. Sowohl aus Leistungs- als auch aus Animationsgründen Daher empfiehlt es sich, die Differenz zwischen alten und neuen MenuItems werden nur die Ansichten aufgeführt, die sich tatsächlich geändert haben. MenuItems ein key-Feld, das dabei helfen kann, da der Schlüssel mit dem für verschiedene Aufrufe von setMenuItems für dasselbe MenuItem.

AppStyledView

AppStyledView ist ein Container für eine Ansicht, die nicht angepasst wurde. Es kann verwendet werden, um diese Ansicht mit einem Rahmen zu versehen, der sie von der der App und zeigen dem Nutzer, dass es sich um . Die von AppStyledView umschlossene Ansicht wird in setContent Die AppStyledView kann auch eine „Zurück“- oder „Schließen“-Schaltfläche haben: die von der App angefordert werden.

AppStyledView fügt seine Ansichten nicht sofort in die Ansichtshierarchie ein. wie bei installBaseLayoutAround, sondern nur die Ansicht an die statische Bibliothek über getView, das dann das Einfügen ausführt. Die Position und kann die Größe von AppStyledView auch gesteuert werden, indem getDialogWindowLayoutParam.

Kontexte

Das Plug-in muss bei der Verwendung von Kontexten vorsichtig sein, da sowohl plugin als auch „Quelle“ Kontexte. Der Plug-in-Kontext wird als Argument an getPluginFactory. Er ist der einzige Kontext, der den Wert die Ressourcen des Plug-ins enthält. Es ist also der einzige Kontext, der für Layout-Inflation im Plug-in vornehmen.

Für den Plug-in-Kontext ist jedoch möglicherweise nicht die richtige Konfiguration festgelegt. Bis wie Sie die richtige Konfiguration abrufen, stellen wir Quellkontexte in Methoden bereit, Komponenten. Der Quellkontext ist normalerweise eine Aktivität, kann aber in einigen Fällen auch ein Dienst oder eine andere Android-Komponente sein. Um die Konfiguration aus der Quellkontext mit den Ressourcen aus dem Plug-in-Kontext zu verknüpfen, muss ein neuer Kontext erstellt mit createConfigurationContext. Wenn nicht die richtige Konfiguration wird ein Verstoß gegen den strikten Android-Modus festgestellt und die überhöhten Aufrufe nicht die richtigen Abmessungen haben.

Context layoutInflationContext = pluginContext.createConfigurationContext(
        sourceContext.getResources().getConfiguration());

Modusänderungen

Einige Plug-ins unterstützen mehrere Modi für ihre Komponenten, z. B. Sportmodus oder Eco-Modus verwenden, die sich optisch unterscheiden. Es gibt keine diese Funktionen in CarUi bereits integriert sind. damit das Plug-in nicht vollständig intern implementiert wird. Das Plug-in kann unter welchen Bedingungen der Modus gewechselt werden soll, z. B. auf Nachrichten zu warten. Das Plug-in kann keine Konfigurationsänderung auslösen zum Ändern von Modi. Es wird jedoch nicht empfohlen, sich auf Konfigurationsänderungen da die manuelle Aktualisierung des Erscheinungsbildes der einzelnen Komponenten reibungsloser ist. und ermöglicht auch Übergänge, die mit Konfigurationsänderungen.

Jetpack Compose

Plug-ins können mit Jetpack Compose implementiert werden. Dies ist jedoch eine Alphaebene. und nicht als stabil gelten.

Plug-ins können ComposeView um eine Compose-fähige Oberfläche für das Rendering zu erstellen. Dieser ComposeView wäre was von der getView-Methode in Komponenten an die App zurückgegeben wird.

Ein großes Problem bei der Verwendung von ComposeView besteht darin, dass Tags in der Stammansicht festgelegt werden. im Layout ändern, um globale Variablen zu speichern, die von allen ComposeViews in der Hierarchie erstellen. Da die Ressourcen-IDs des Plug-ins nicht getrennt von dem der App, kann dies zu Konflikten führen, wenn sowohl das App und das Plug-in legen Tags für dieselbe Datenansicht fest. Ein benutzerdefinierter ComposeViewWithLifecycle definiert, mit dem diese globalen Variablen ComposeView finden Sie unten. Auch dies sollte nicht als stabil betrachtet werden.

ComposeViewWithLifecycle:

class ComposeViewWithLifecycle @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null,
  defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
    LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {

  private val lifeCycle = LifecycleRegistry(this)
  private val modelStore = ViewModelStore()
  private val savedStateRegistryController = SavedStateRegistryController.create(this)
  private var composeView: ComposeView? = null
  private var content = @Composable {}

  init {
    ViewTreeLifecycleOwner.set(this, this)
    ViewTreeViewModelStoreOwner.set(this, this)
    ViewTreeSavedStateRegistryOwner.set(this, this)
    compositionContext = createCompositionContext()
  }

  fun setContent(content: @Composable () -> Unit) {
    this.content = content
    composeView?.setContent(content)
  }

  override fun getLifecycle(): Lifecycle {
    return lifeCycle
  }

  override fun getViewModelStore(): ViewModelStore {
    return modelStore
  }

  override fun getSavedStateRegistry(): SavedStateRegistry {
    return savedStateRegistryController.savedStateRegistry
  }

  override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    savedStateRegistryController.performRestore(Bundle())
    lifeCycle.currentState = Lifecycle.State.RESUMED
    composeView = ComposeView(context)
    composeView?.setContent(content)
    addView(composeView, LayoutParams(
      LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    lifeCycle.currentState = Lifecycle.State.DESTROYED
    modelStore.clear()
    removeAllViews()
    composeView = null
  }

  // Exact copy of View.createCompositionContext() in androidx's WindowRecomposer.android.kt
  private fun createCompositionContext(): CompositionContext {
    val currentThreadContext = AndroidUiDispatcher.CurrentThread
    val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
      PausableMonotonicFrameClock(it).apply { pause() }
    }
    val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
    val recomposer = Recomposer(contextWithClock)
    val runRecomposeScope = CoroutineScope(contextWithClock)
    val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
      "ViewTreeLifecycleOwner not found from $this"
    }
    viewTreeLifecycleOwner.lifecycle.addObserver(
      LifecycleEventObserver { _, event ->
        @Suppress("NON_EXHAUSTIVE_WHEN")
        when (event) {
          Lifecycle.Event.ON_CREATE ->
            // Undispatched launch since we've configured this scope
            // to be on the UI thread
            runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
              recomposer.runRecomposeAndApplyChanges()
            }
          Lifecycle.Event.ON_START -> pausableClock?.resume()
          Lifecycle.Event.ON_STOP -> pausableClock?.pause()
          Lifecycle.Event.ON_DESTROY -> {
            recomposer.cancel()
          }
        }
      }
    )
    return recomposer
  }

//  TODO: ComposeViewWithLifecycle should handle saving state and other lifecycle things
//  override fun onSaveInstanceState(): Parcelable? {
//    val superState = super.onSaveInstanceState()
//    val bundle = Bundle()
//    savedStateRegistryController.performSave(bundle)
//  }
}