Araba kullanıcı arayüzü eklentileri

Bileşenin eksiksiz uygulamalarını oluşturmak için Araba kullanıcı arayüzü kitaplığı eklentilerini kullanma çalışma zamanında kaynak yer paylaşımları kullanmak yerine Araba kullanıcı arayüzü kitaplığında özelleştirmeler (RRO'lar). RRO'lar, yalnızca Araba Kullanıcı Arayüzü kitaplığının XML kaynaklarını değiştirmenize olanak tanır Bu da, özelleştirebileceğiniz unsurların kapsamını sınırlandırır.

Eklenti oluşturun

Araba kullanıcı arayüzü kitaplığı eklentisi, Eklenti API'leri. Eklenti API'leri statik bir kitaplık olarak ekler.

Yakındag ve Gradle ile ilgili örnekleri inceleyin:

Yakında

Şu Shorts örneğine bakalım:

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

Bu build.gradle dosyasını görüntüleyin:

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')

Eklentinin manifest dosyasında, şu özellikleri kullanın:

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

android:authorities="com.android.car.ui.plugin", eklentiyi bulunabilir hale getirir araba kullanıcı arayüzü kitaplığına ekleyebilir. Şu konumda sorgulanabilmesi için sağlayıcının dışa aktarılması gerekiyor: belirler. Ayrıca, enabled özelliği false değerine ayarlanırsa varsayılan değer uygulaması, eklenti uygulaması yerine kullanılır. İçerik provider sınıfının mevcut olması gerekmez. Bu durumda, Sağlayıcı tanımına tools:ignore="MissingClass". Örneğe göz atın aşağıdaki manifest girişini yapabilirsiniz:

    <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>

Son olarak, güvenlik önlemi olarak Uygulamanızı imzalayın.

Paylaşılan kitaplık olarak eklentiler

Doğrudan uygulamalarda derlenen Android statik kitaplıklarının aksine Android'de paylaşılan kitaplıklar, referans verilen bağımsız bir APK olarak derlenir diğer uygulamalar tarafından yürütülür.

Android paylaşılan kitaplığı olarak uygulanan eklentilerin kendi sınıfları vardır uygulamalar arasında paylaşılan sınıf yükleyiciye otomatik olarak eklenir. Bir uygulamanın Araç Kullanıcı Arayüzü kitaplığının çalışma zamanı bağımlılığı, eklentinin paylaşılan kitaplığına classloader, eklentinin paylaşılan kitaplığının sınıflarına erişebilir. Eklentiler uygulandı normal Android uygulamaları (paylaşılan kitaplık değil), uygulamaların soğuk algılanmasını olumsuz yönde etkileyebileceğinden başlangıç zamanları.

Paylaşılan kitaplıkları uygulama ve derleme

Android paylaşılan kitaplıklarla geliştirme, normal Android'e çok benzer aralarında birkaç temel farkın

  • Eklenti paketiyle application etiketi altında library etiketini kullanın ad:
    <application>
        <library android:name="com.chassis.car.ui.plugin" />
        ...
    </application>
  • Shortg android_app derleme kuralınızı (Android.bp) AAPT ile yapılandırma paylaşılan bir kitaplık oluşturmak için kullanılan shared-lib flag'i:
android_app {
  ...
  aaptflags: ["--shared-lib"],
  ...
}

Paylaşılan kitaplıklara bağımlılıklar

Sistemde Araba kullanıcı arayüzü kitaplığını kullanan her uygulama için uses-library etiketi, Eklenti paket adını içeren application etiketi:

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

Eklenti yükleme

Eklentilerin, modül dahil edilerek sistem bölümüne önceden yüklenmesi ZORUNLUDUR PRODUCT_PACKAGES içinde. Önceden yüklenmiş paket aşağıdakine benzer şekilde güncellenebilir: yüklü diğer uygulamalar.

Sistemde mevcut bir eklentiyi güncelliyorsanız o eklentiyi kullanan tüm uygulamalar otomatik olarak kapanmasını sağlar. Kullanıcı tarafından yeniden açıldığında güncellenmiş değişiklikler olur. Uygulama çalışmıyorsa bir sonraki başlatılmasında güncellenmiştir eklentisidir.

Android Studio ile bir eklenti yüklerken, dikkat edilecek noktalara değineceğiz. Bu yazının yazıldığı sırada bir hata bir eklentinin güncellenmesine neden olan Android Studio uygulama yükleme işlemi değiştirebilirsiniz. Bu sorun, Her zaman yükle paket yöneticisiyle (Android 11 ve sonraki sürümlerde dağıtım optimizasyonlarını devre dışı bırakır) yapılandırdığınızdan emin olun.

Ayrıca, Android Studio eklentiyi yüklerken, başlatılacak ana etkinlik bulamıyor. Eklenti, çalışmadığından bu beklenen bir durumdur. herhangi bir etkinliğe (bir amacı çözümlemek için kullanılan boş amaç hariç) sahip olmalıdır. Alıcı: Hatayı ortadan kaldırmak için, derleme işleminde Launch (Başlat) seçeneğini, Nothing (Hiçbir şey) olarak değiştirin yapılandırma.

Android Studio eklentisi yapılandırması. Şekil 1. Android Studio eklentisi yapılandırması

Proxy eklentisi

Özelleştirme Araba kullanıcı arayüzü kitaplığını kullanan uygulamalar düzenlenecek her uygulamayı hedefleyen bir RRO gerektirir. Örneğin, özelleştirmelerin uygulamalar genelinde aynı olduğu durumlar dahil. Bu, müşteri başına RRO uygulamanız gerekiyor. Hangi uygulamaların Araba kullanıcı arayüzü kitaplığını kullandığını görün.

Araba kullanıcı arayüzü kitaplığı proxy eklentisi örnek olarak bileşen uygulamalarını statik sürümünü kullanabilirsiniz. Bu eklenti, RRO ile hedeflenebilir. Araba kullanıcı arayüzü kitaplığını kullanan uygulamalar için tek bir özelleştirme noktası olarak kullanılır işlevsel bir eklenti uygulanmasına gerek kalmadan. Daha fazla bilgi için RRO'lar için Uygulama kaynaklarının değerini şurada değiştirme: çalışma zamanı.

Proxy eklentisi yalnızca bir örnek ve bir eklentidir. RRO'ların ötesinde özelleştirme için, bir eklentinin alt kümesi uygulanabilir ve kalanlar için proxy eklentisini kullanın veya tamamen sıfırdan yapabilirsiniz.

Proxy eklentisi uygulamalar için tek bir RRO özelleştirme noktası sağlar, ancak uygulamaları devre dışı bırakan uygulamalar için de doğrudan uygulamanın kendisini hedefler.

Eklenti API'lerini uygulayın

Eklentinin ana giriş noktası com.android.car.ui.plugin.PluginVersionProviderImpl sınıf. Tüm eklentiler tam olarak bu ada ve paket adına sahip bir sınıf ekle. Bu sınıfta bir oluşturucuyu ekleyebilir ve PluginVersionProviderOEMV1 arayüzünü uygulayabilirsiniz.

CarUi eklentileri, eklentiden daha eski veya daha yeni uygulamalarla çalışmalıdır. Alıcı: Bunu kolaylaştırır, tüm eklenti API'leri API'lerin sonunda V# sınıf adı. Araba kullanıcı arayüzü kitaplığının yeni özellikleri içeren yeni bir sürümü yayınlanırsa onlar bileşenin V2 sürümündedir. Araba kullanıcı arayüzü kitaplığı en iyi yöntemin, yeni özelliklerin eski bir eklenti bileşeni kapsamında çalışması gerekir. Örneğin, araç çubuğundaki yeni bir düğme türünü MenuItems biçimine dönüştürebilirsiniz.

Ancak, Araba kullanıcı arayüzü kitaplığının eski sürümüne sahip bir uygulama yeni bir sürüme eklentisidir. Bu sorunu çözmek için eklentilerin OEM API'nin sürümüne bağlı olarak, kendi uygulamalarının farklı uygulamalarını döndürebilir ve uygulamalar tarafından destekleniyor.

PluginVersionProviderOEMV1, bir yönteme sahip:

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

Bu yöntem, en yüksek sürümünü uygulayan bir nesne PluginFactoryOEMV#, eklenti tarafından desteklenir ancak hâlâ daha az veya maxVersion değerine eşit. Bir eklenti, PluginFactory daha eskiyse null değerini döndürebilir. Bu durumda, ve CarUi bileşenlerinin bağlantılı uygulanması olduğundan emin olun.

Şuna karşı derlenen uygulamalarla geriye dönük uyumluluğu sürdürmek: statik Araba Kullanıcı Arayüzü kitaplığının eski sürümlerini desteklememesi önerilir. Eklentinizin uygulanmasından elde edilen 2, 5 ve daha yüksek maxVersion değerleri PluginVersionProvider sınıfı. Sürüm 1, 3 ve 4 desteklenmez. Örneğin, daha fazla bilgi için PluginVersionProviderImpl.

PluginFactory, diğer tüm CarUi öğelerini oluşturan arayüzdür bileşenlerine ayıralım. Ayrıca, arayüzlerinin hangi sürümünün kullanılması gerektiğini de tanımlar. Eğer eklenti bu bileşenlerden hiçbirini uygulamaz. Bu bileşenlerden herhangi birini uygulamaya null oluşturma işlevinde ( ayrı bir customizesBaseLayout() fonksiyonu).

pluginFactory, CarUi bileşenlerinin hangi sürümlerinin kullanılabileceğini sınırlandırır birlikte. Örneğin, hiçbir zaman bir pluginFactory Toolbar sürümünün 100 sürümü ve ayrıca RecyclerView sürüm 1 bileşenlerin çok çeşitli sürümlerinin yüksek kalitede olacağınızın yardımcı olabilir. Araç çubuğu sürüm 100'ü kullanmak için geliştiricilerin aşağıdakileri oluşturan bir pluginFactory sürümü sağlayın: Bu durumda diğer bileşenleri bulunuyor. Diğer bileşenlerin sürümleri farklı eşittir. Örneğin pluginFactoryOEMV100, ToolbarControllerOEMV100 ve RecyclerViewOEMV70.

Araç Çubuğu

Temel düzen

Araç çubuğu ve "temel düzen" Bunlar birbiriyle yakından ilişkilidir. installBaseLayoutAround adlı bir araç çubuğu oluşturur. İlgili içeriği oluşturmak için kullanılan temel düzen Araç çubuğunun, uygulamanın etrafındaki herhangi bir yere yerleştirilmesine olanak tanıyan bir kavramdır. uygulamanın üst/alt kısmında dikey olarak bir araç çubuğu sağlamak için şeklinde dairesel bir araç çubuğuyla gösteriliyor. Bu araç çubuğu/taban için installBaseLayoutAround öğesine bir görünüm geçirilerek gerçekleştirilir farklı bir düzene denk gelir.

Eklenti, sağlanan görünümü almalı, üst öğesinden ayırmalı, şişirmelidir. eklentisine ait aynı dizinde ve aynı LayoutParams çıkarıp görünümü tekrar ekleyin bir yerde çerçevelenmiş durumda. Şişmiş düzen, Uygulama tarafından istenirse araç çubuğunu içermelidir.

Uygulama, araç çubuğu olmadan temel düzen isteyebilir. Çalışıyorsa installBaseLayoutAround, null değerini döndürmelidir. Çoğu eklenti için bu olması gerekir, ancak eklentinin yazarı uygulamak isterse, ör. bir süsleme kenarlara kaydırılabilir. Bu da temel bir düzenle yapılabilir. Bu Süslemeler dikdörtgen olmayan ekranı olan cihazlar için özellikle kullanışlıdır uygulamayı dikdörtgen bir alana aktarabilir ve ekran görüntüsüne temiz geçişler alanı inceleyelim.

installBaseLayoutAround için de Consumer<InsetsOEMV1> sertifikası geçildi. Bu tüketici, uygulamaya eklentinin kısmen göründüğünü bildirmek için kullanılabilir Kapsayacak şekilde (araç çubuğuyla veya başka bir yöntemle) Uygulama, bu alanda çizim yapmaya devam edin ancak kullanıcının etkileşimde bulunabileceği her türlü çıkarmanız gerekir. Bu efekt, referans tasarımımızda şeffaf hale getirir ve altında listelerin kaydırılması sağlanır. Bu özellik uygulanmadığında, listedeki ilk öğe araç çubuğunun altında kalır ve tıklanamaz. Bu efekt gerekli değilse eklenti, Tüketici.

Araç çubuğunun altında kayan içerikler. Şekil 2. Araç çubuğunun altında kayan içerikler

Uygulama açısından bakıldığında eklenti, yeni ek öğeleri gönderdiğinde bunları, InsetsChangedListener uygulayan etkinliklerden veya parçalardan uzaklaştırır. Eğer bir etkinlik veya parça InsetsChangedListener, Car kullanıcı arayüzü kitaplığı, alt öğeleri varsayılan olarak Parçayı içeren Activity veya FragmentActivity. Kitaplıkta alt öğeleri varsayılan olarak parçalara uygular. Burada örnek bir snippet Bir RecyclerView öğesine dolgu olarak eklenerek uygulama:

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());
  }
}

Son olarak, eklentiye bir fullscreen ipucu verilir. Bu ipucu, uygulamanın tamamını veya yalnızca küçük bir bölümünü kaplar. Bu, kenar boyunca süslemelerin uygulanmasını önlemek için kullanılabilir. yalnızca tüm ekranın kenarı boyunca göründüklerinde bir anlam ifade eder. Örnek tam ekran olmayan temel düzenleri kullanan bir uygulama olan Ayarlar'dır. Buradaki İki bölmeli düzenin kendi araç çubuğu vardır.

Şu durumda installBaseLayoutAround öğesinin null değeri döndürmesi beklendiğinden: Eklentinin, false değerine sahip olduğunu belirtmek için toolbarEnabled temel düzeni özelleştirmek istiyorsanız şuradan false değerini döndürmelidir: customizesBaseLayout.

Temel düzen, tam olarak bir FocusParkingView ve FocusArea içermelidir çevirme kontrollerini destekler. Şu cihazlarda bu görünümler atlanabilir: çevirme desteklenmiyor. FocusParkingView/FocusAreas, statik CarUi kitaplığıdır; bu nedenle fabrikalara verileri sağlamak içinsetRotaryFactories bağlamlardan görünümler oluşturabilirsiniz.

Odak görünümleri oluşturmak için kullanılan bağlamlar bağlamına bakacağız. FocusParkingView, ilk görünüme en yakın olmalıdır ağaçta kullanmak istediğinize karar vermeniz gerekir, kullanıcıya görünür olmamalıdır. FocusArea, araç çubuğunu döner kaydırma bölgesi olduğunu belirtmek için alt taban düzeni. FocusArea sağlandığında kullanıcı, araç çubuğunda bulunan döner kumanda.

Araç çubuğu denetleyicisi

Döndürülen ToolbarController tutarının çok daha basit olması temel düzene göre daha fazladır. Sisteminin görevi, temel düzende görüntülemenizi sağlar. Daha fazla bilgi için bkz. Javadoc birçok yöntem. Daha karmaşık yöntemlerden bazıları aşağıda açıklanmıştır.

getImeSearchInterface, IME'de (klavye) arama sonuçlarını göstermek için kullanılır penceresini kapatın. Bu, arama sonuçlarının yanında Örneğin klavye, ekranın yalnızca yarısını kapladıysa. Çoğu Bu işlev statik CarUi kitaplığında uygulanmıştır, arayüzündeki arayüz yalnızca statik kitaplığın TextView ve onPrivateIMECommand geri arama. Bunu desteklemek için eklenti onPrivateIMECommand değerini geçersiz kılarak ve geçen bir TextView alt sınıfı kullanmalıdır. sağlanan dinleyiciye arama çubuğunun TextView olarak çağrılması.

setMenuItems, yalnızca ekranda Menü Öğeleri'ni gösterir, ancak menü öğeleri çağrılır çok sık oluyor. MenuItems eklentisi API'si sabit olduğundan, Menü Öğesi değiştirilir ve tamamen yeni bir setMenuItems çağrısı gerçekleşir. Bu kullanıcının bir Menü Öğesi düğmesini tıklaması gibi basit bir şey olursa tıklama, anahtarın açılmasına neden oldu. Hem performans hem de animasyon açısından Dolayısıyla, eskiyle yenisi arasındaki farkı hesaplamanız önerilir. sadece gerçekten değişen görünümleri güncelleyin. Menü Öğeleri Anahtar aynı olması gerektiğinden, bu konuda yardımcı olabilecek bir key alanı sağlayın aynı Menü Öğesi için setMenuItems için yapılan farklı çağrılarda geçerlidir.

UygulamaStilliGörünüm

AppStyledView, hiç özelleştirilmemiş bir görünümün kapsayıcısıdır. Google görünümün diğer yönlerden ayırt edilmesini sağlayan bir kenarlık sağlamak için kullanılabilir. ve kullanıcıya bunun farklı türde bir uygulama arayüzü. AppStyledView tarafından sarmalanan görünüm setContent AppStyledView cihazındaki geri veya kapat düğmesi de tarafından istenmesi gerekir.

AppStyledView, görünümlerini görünüm hiyerarşisine hemen eklemez installBaseLayoutAround yapar, bunun yerine sadece statik kitaplığı getView ile değiştirir, ardından ekleme işlemini yapar. Konum ve AppStyledView boyutu uygulanarak ayrıca kontrol edilebilir. getDialogWindowLayoutParam.

Bağlamlar

Eklenti, hem eklenti hem de Bağlamlar'ı kullanırken "kaynak" bağlamlar. Eklenti bağlamı, getPluginFactory ve kaynaklarını inceleyebilirsiniz. Yani, projenizin durumu hakkında .

Ancak, eklenti bağlamı için doğru yapılandırma ayarlanmamış olabilir. Alıcı: sağlamak için, kaynak bağlamlarını doğru yapılandırmaya yönelik yöntemler kullanarak bileşenlerine ayıralım. Kaynak bağlam genellikle bir etkinliktir ancak bazı durumlarda aynı zamanda bir Hizmet veya başka bir Android bileşeni olmalıdır. Yapılandırmayı kaynak bağlamını, eklenti bağlamındaki kaynaklarla birlikte kullanıldığında yeni bir bağlam createConfigurationContext kullanılarak oluşturuldu. Doğru yapılandırma kullanılırsa Android katı modu ihlali olur ve artan görüntülemeler , doğru boyutlara sahip olmamalıdır.

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

Mod değişiklikleri

Bazı eklentiler, bileşenleri için birden fazla modu destekleyebilir: Görsel olarak farklı görünen bir spor modu veya eko modu. Hayır, CarUi'de bu tür işlevler için yerleşik destek var ancak tamamen dahili olarak uygulanmasını engeller. Eklenti, nasıl değiştireceğini öğrenmek için kullandığı dinlemektedir. Eklenti, yapılandırma değişikliklerini tetikleyemez Ancak yapılandırma değişikliklerine bağlı olmanız önerilmez her bileşenin görünümünü manuel olarak güncellemek daha kolay olduğu için, ve aynı zamanda dönüşümlerde mümkün olmayan geçişlere yapılandırma değişiklikleridir.

Jetpack Compose

Eklentiler Jetpack Compose kullanılarak uygulanabilir, ancak bu alfa düzeyidir ve kararlı olarak kabul edilmemelidir.

Eklentiler kullanabileceğiniz: ComposeView oluşturmak için Compose'un etkin olduğu bir yüzey oluşturun. Bu ComposeView: getView yönteminden uygulamaya ne döndürüldüğünü gösterir.

ComposeView kullanımıyla ilgili önemli sorunlardan biri, etiketleri kök görünümünde ayarlamasıdır. paylaşılan genel değişkenleri depolamak için düzene farklı ComposeView'lar oluşturabilirsiniz. Eklentinin kaynak kimlikleri bir alan adına sahip olduğunu ve uygulamanın uygulamanız ve eklenti, etiketleri aynı görünüme ayarladı. Özel ComposeViewWithLifecycle. Bu genel değişkenleri ComposeView aşağıda sağlanmıştır. Bu da tutarlı olarak kabul edilmemelidir.

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)
//  }
}