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 Tagapplication
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 Flagshared-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.
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:
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)
// }
}