Gunakan plugin library UI Mobil untuk membuat implementasi lengkap penyesuaian komponen di library UI Mobil, bukan menggunakan overlay resource runtime (RRO). RRO memungkinkan Anda hanya mengubah resource XML komponen library UI Mobil, yang membatasi cakupan hal yang dapat Anda sesuaikan.
Membuat plugin
Plugin library UI Mobil adalah APK yang berisi class yang mengimplementasikan kumpulan Plugin API. Plugin API dapat dikompilasi ke dalam plugin sebagai library statis.
Lihat contoh di Soong dan Gradle:
Soong
Perhatikan contoh Soong ini:
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
Lihat file build.gradle
ini:
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')
Plugin harus memiliki penyedia konten yang dideklarasikan dalam manifesnya yang memiliki atribut berikut:
android:authorities="com.android.car.ui.plugin"
android:enabled="true"
android:exported="true"
android:authorities="com.android.car.ui.plugin"
membuat plugin dapat ditemukan
oleh library UI Mobil. Penyedia harus diekspor agar dapat dikueri saat
runtime. Selain itu, jika atribut enabled
ditetapkan ke false
, implementasi
default akan digunakan, bukan implementasi plugin. Class penyedia
konten tidak harus ada. Dalam hal ini, pastikan untuk menambahkan
tools:ignore="MissingClass"
ke definisi penyedia. Lihat contoh
entri manifes di bawah:
<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>
Terakhir, sebagai langkah pengamanan, Tanda tangani aplikasi Anda.
Plugin sebagai library bersama
Tidak seperti library statis Android yang dikompilasi langsung ke dalam aplikasi, library bersama Android dikompilasi ke dalam APK mandiri yang direferensikan oleh aplikasi lain saat runtime.
Plugin yang diimplementasikan sebagai library bersama Android akan otomatis menambahkan class-nya ke classloader bersama antar-aplikasi. Saat aplikasi yang menggunakan library UI Mobil menentukan dependensi runtime pada library bersama plugin, loader class-nya dapat mengakses class library bersama plugin. Plugin yang diterapkan sebagai aplikasi Android normal (bukan library bersama) dapat berdampak negatif pada waktu cold start aplikasi.
Mengimplementasikan dan mem-build library bersama
Mengembangkan dengan library bersama Android hampir sama dengan aplikasi Android normal, dengan beberapa perbedaan utama.
- Gunakan tag
library
di bagian tagapplication
dengan nama paket plugin dalam manifes aplikasi plugin Anda:
<application>
<library android:name="com.chassis.car.ui.plugin" />
...
</application>
- Konfigurasikan aturan build
android_app
Soong (Android.bp
) dengan tanda AAPTshared-lib
, yang digunakan untuk mem-build library bersama:
android_app {
...
aaptflags: ["--shared-lib"],
...
}
Dependensi pada library bersama
Untuk setiap aplikasi di sistem yang menggunakan library UI Mobil, sertakan
tag uses-library
dalam manifes aplikasi di bawah
tag application
dengan nama paket plugin:
<manifest>
<application
android:name=".MyApp"
...>
<uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
...
</application>
</manifest>
Menginstal plugin
Plugin HARUS diprainstal di partisi sistem dengan menyertakan modul
di PRODUCT_PACKAGES
. Paket yang diinstal sebelumnya dapat diupdate dengan cara yang sama seperti
aplikasi terinstal lainnya.
Jika Anda mengupdate plugin yang ada di sistem, aplikasi apa pun yang menggunakan plugin tersebut akan otomatis ditutup. Setelah dibuka kembali oleh pengguna, mereka akan melihat perubahan yang diperbarui. Jika aplikasi tidak berjalan, saat dimulai lagi, aplikasi akan memiliki plugin yang telah diupdate.
Saat menginstal plugin dengan Android Studio, ada beberapa pertimbangan tambahan yang perlu dipertimbangkan. Pada saat penulisan, ada bug dalam proses penginstalan aplikasi Android Studio yang menyebabkan update pada plugin tidak berlaku. Hal ini dapat diperbaiki dengan memilih opsi Selalu instal dengan pengelola paket (menonaktifkan pengoptimalan deployment di Android 11 dan yang lebih baru) dalam konfigurasi build plugin.
Selain itu, saat menginstal plugin, Android Studio melaporkan error bahwa plugin tidak dapat menemukan aktivitas utama untuk diluncurkan. Hal ini wajar, karena plugin tidak memiliki aktivitas apa pun (kecuali intent kosong yang digunakan untuk me-resolve intent). Untuk menghilangkan error, ubah opsi Launch ke Nothing dalam konfigurasi build.
Gambar 1. Konfigurasi Android Studio plugin
Plugin proxy
Penyesuaian aplikasi menggunakan library UI Mobil memerlukan RRO yang menargetkan setiap aplikasi tertentu yang akan diubah, termasuk saat penyesuaian identik di seluruh aplikasi. Artinya, RRO per aplikasi diperlukan. Lihat aplikasi yang menggunakan library UI Mobil.
Plugin proxy library UI Mobil adalah contoh library bersama plugin yang mendelegasikan implementasi komponennya ke library UI Mobil versi statis. Plugin ini dapat ditargetkan dengan RRO, yang dapat digunakan sebagai satu titik penyesuaian untuk aplikasi yang menggunakan library UI Mobil tanpa perlu menerapkan plugin fungsional. Untuk informasi selengkapnya tentang RRO, lihat Mengubah nilai resource aplikasi saat runtime.
Plugin proxy hanyalah contoh dan titik awal untuk melakukan penyesuaian menggunakan plugin. Untuk penyesuaian di luar RRO, seseorang dapat menerapkan subset komponen plugin dan menggunakan plugin proxy untuk sisanya, atau menerapkan semua komponen plugin sepenuhnya dari awal.
Meskipun plugin proxy menyediakan satu titik penyesuaian RRO untuk aplikasi, aplikasi yang memilih tidak menggunakan plugin masih akan memerlukan RRO yang secara langsung menargetkan aplikasi itu sendiri.
Mengimplementasikan API plugin
Titik entri utama ke plugin adalah
class com.android.car.ui.plugin.PluginVersionProviderImpl
. Semua plugin harus
menyertakan class dengan nama dan nama paket yang sama persis ini. Class ini harus memiliki
konstruktor default dan mengimplementasikan antarmuka PluginVersionProviderOEMV1
.
Plugin CarUi harus berfungsi dengan aplikasi yang lebih lama atau lebih baru dari plugin. Untuk
memfasilitasi hal ini, semua API plugin diberi versi dengan V#
di akhir
nama class-nya. Jika versi baru library UI Mobil dirilis dengan fitur baru,
fitur tersebut merupakan bagian dari komponen versi V2
. Library UI Mobil melakukan
upaya terbaiknya untuk membuat fitur baru berfungsi dalam cakupan komponen plugin lama.
Misalnya, dengan mengonversi jenis tombol baru di toolbar menjadi MenuItems
.
Namun, aplikasi dengan library UI Mobil versi lama tidak dapat beradaptasi dengan plugin baru yang ditulis untuk API yang lebih baru. Untuk mengatasi masalah ini, kami mengizinkan plugin untuk menampilkan implementasi yang berbeda berdasarkan versi OEM API yang didukung oleh aplikasi.
PluginVersionProviderOEMV1
memiliki satu metode di dalamnya:
Object getPluginFactory(int maxVersion, Context context, String packageName);
Metode ini menampilkan objek yang mengimplementasikan versi tertinggi
PluginFactoryOEMV#
yang didukung oleh plugin, meskipun masih kurang dari atau
sama dengan maxVersion
. Jika plugin tidak memiliki implementasi
PluginFactory
yang lama, plugin tersebut dapat menampilkan null
. Dalam hal ini, implementasi komponen
CarUi yang ditautkan secara statis akan digunakan.
Untuk mempertahankan kompatibilitas mundur dengan aplikasi yang dikompilasi terhadap
versi lama library UI Mobil statis, sebaiknya dukung
maxVersion
2, 5, dan yang lebih tinggi dari dalam implementasi plugin
class PluginVersionProvider
. Versi 1, 3, dan 4 tidak didukung. Untuk mengetahui informasi selengkapnya, lihat PluginVersionProviderImpl
.
PluginFactory
adalah antarmuka yang membuat semua komponen CarUi
lainnya. Kode ini juga menentukan versi antarmuka yang harus digunakan. Jika
tidak mencoba menerapkan salah satu komponen ini, plugin dapat menampilkan
null
dalam fungsi pembuatannya (kecuali toolbar, yang memiliki
fungsi customizesBaseLayout()
terpisah).
pluginFactory
membatasi versi komponen CarUi yang dapat digunakan
bersama. Misalnya, tidak akan pernah ada pluginFactory
yang dapat membuat
Toolbar
versi 100 dan juga RecyclerView
versi 1, karena
tidak ada jaminan bahwa berbagai versi komponen akan
berfungsi bersama. Untuk menggunakan toolbar versi 100, developer diharapkan
memberikan implementasi versi pluginFactory
yang membuat
toolbar versi 100, yang kemudian membatasi opsi pada versi komponen
lain yang dapat dibuat. Versi komponen lain mungkin tidak
sama, misalnya pluginFactoryOEMV100
dapat membuat
ToolbarControllerOEMV100
dan RecyclerViewOEMV70
.
Toolbar
Layout dasar
Panel alat dan "tata letak dasar" sangat terkait erat, sehingga fungsi
yang membuat panel alat disebut installBaseLayoutAround
. Layout dasar
adalah konsep yang memungkinkan toolbar diposisikan di mana saja di sekitar konten
aplikasi, untuk memungkinkan toolbar di bagian atas/bawah aplikasi, secara vertikal
di sepanjang sisi, atau bahkan toolbar melingkar yang melingkupi seluruh aplikasi. Hal ini
dilakukan dengan meneruskan tampilan ke installBaseLayoutAround
agar toolbar/layout
dasar digabungkan.
Plugin harus mengambil tampilan yang disediakan, melepaskannya dari induknya, meng-inflate
tata letak plugin sendiri dalam indeks induk yang sama dan dengan
LayoutParams
yang sama dengan tampilan yang baru saja dilepaskan, lalu memasang kembali tampilan
di suatu tempat di dalam tata letak yang baru saja di-inflate. Tata letak yang di-inflate akan
berisi toolbar, jika diminta oleh aplikasi.
Aplikasi dapat meminta tata letak dasar tanpa toolbar. Jika demikian,
installBaseLayoutAround
akan menampilkan null. Untuk sebagian besar plugin, itulah yang
perlu dilakukan, tetapi jika penulis plugin ingin menerapkan misalnya dekorasi
di sekitar tepi aplikasi, hal itu masih dapat dilakukan dengan tata letak dasar. Dekorasi
ini sangat berguna untuk perangkat dengan layar non-persegi panjang, karena
dapat mendorong aplikasi ke ruang persegi panjang dan menambahkan transisi yang bersih ke
ruang non-persegi panjang.
installBaseLayoutAround
juga meneruskan Consumer<InsetsOEMV1>
. Konsumen
ini dapat digunakan untuk menyampaikan ke aplikasi bahwa plugin sebagian
menutupi konten aplikasi (dengan toolbar atau tidak). Aplikasi kemudian
akan mengetahui untuk terus menggambar di ruang ini, tetapi tidak menyertakan komponen kritis
yang dapat berinteraksi dengan pengguna. Efek ini digunakan dalam desain referensi kami, untuk membuat
toolbar semi-transparan, dan membuat daftar di bawahnya di-scroll. Jika fitur ini
tidak diterapkan, item pertama dalam daftar akan tertinggal di bawah toolbar
dan tidak dapat diklik. Jika efek ini tidak diperlukan, plugin dapat mengabaikan
Konsumen.
Gambar 2. Konten yang di-scroll di bawah toolbar
Dari perspektif aplikasi, saat plugin mengirim inset baru, plugin akan menerimanya
dari aktivitas atau fragmen yang menerapkan InsetsChangedListener
. Jika
aktivitas atau fragmen tidak menerapkan InsetsChangedListener
, library Ui
Mobil akan menangani inset secara default dengan menerapkan inset sebagai padding ke
Activity
atau FragmentActivity
yang berisi fragmen. Library tidak
menerapkan inset secara default ke fragmen. Berikut adalah contoh cuplikan
implementasi yang menerapkan inset sebagai padding pada RecyclerView
di
aplikasi:
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());
}
}
Terakhir, plugin diberi petunjuk fullscreen
, yang digunakan untuk menunjukkan apakah
tampilan yang harus digabungkan memenuhi seluruh aplikasi atau hanya sebagian kecil.
Hal ini dapat digunakan untuk menghindari penerapan beberapa dekorasi di sepanjang tepi yang
hanya masuk akal jika muncul di sepanjang tepi seluruh layar. Aplikasi
contoh yang menggunakan tata letak dasar non-layar penuh adalah Setelan, dengan setiap panel
tata letak panel ganda memiliki toolbarnya sendiri.
Karena installBaseLayoutAround
diharapkan menampilkan null saat
toolbarEnabled
adalah false
, agar plugin menunjukkan bahwa plugin tidak
ingin menyesuaikan tata letak dasar, plugin harus menampilkan false
dari
customizesBaseLayout
.
Tata letak dasar harus berisi FocusParkingView
dan FocusArea
untuk sepenuhnya
mendukung kontrol putar. Tampilan ini dapat dihilangkan di perangkat yang
tidak mendukung rotary. FocusParkingView/FocusAreas
diterapkan dalam
library CarUi statis, sehingga setRotaryFactories
digunakan untuk menyediakan factory guna
membuat tampilan dari konteks.
Konteks yang digunakan untuk membuat tampilan Fokus harus berupa konteks sumber, bukan
konteks plugin. FocusParkingView
harus sedekat mungkin dengan tampilan pertama
dalam hierarki, karena itulah yang difokuskan saat tidak
ada fokus yang terlihat oleh pengguna. FocusArea
harus menggabungkan toolbar dalam
tata letak dasar untuk menunjukkan bahwa toolbar tersebut adalah zona dorongan putar. Jika FocusArea
tidak
disediakan, pengguna tidak dapat membuka tombol apa pun di toolbar dengan
pengontrol putar.
Pengontrol toolbar
ToolbarController
yang sebenarnya ditampilkan harus jauh lebih mudah
diimplementasikan daripada tata letak dasar. Tugasnya adalah mengambil informasi yang diteruskan ke
penyetelnya dan menampilkannya dalam tata letak dasar. Lihat Javadoc untuk mengetahui informasi tentang
sebagian besar metode. Beberapa metode yang lebih kompleks dibahas di bawah.
getImeSearchInterface
digunakan untuk menampilkan hasil penelusuran di jendela IME (keyboard). Hal ini dapat berguna untuk menampilkan/mengoanimasi hasil penelusuran bersama
keyboard, misalnya jika keyboard hanya menempati setengah layar. Sebagian besar
fungsi diimplementasikan dalam library CarUi statis, antarmuka
penelusuran dalam plugin hanya menyediakan metode untuk library statis guna mendapatkan
callback TextView
dan onPrivateIMECommand
. Untuk mendukung hal ini, plugin
harus menggunakan subclass TextView
yang mengganti onPrivateIMECommand
dan meneruskan
panggilan ke pemroses yang disediakan sebagai TextView
kotak penelusurannya.
setMenuItems
hanya menampilkan MenuItem di layar, tetapi akan sering
dipanggil. Karena API plugin untuk MenuItem tidak dapat diubah, setiap kali
MenuItem diubah, panggilan setMenuItems
baru akan terjadi. Hal ini dapat
terjadi untuk hal-hal sepele seperti pengguna mengklik MenuItem tombol, dan
klik tersebut menyebabkan tombol beralih. Untuk alasan performa dan animasi,
sebaiknya hitung perbedaan antara daftar
MenuItem lama dan baru, dan hanya perbarui tampilan yang benar-benar berubah. MenuItem menyediakan kolom key
yang dapat membantu hal ini, karena kunci harus sama di seluruh panggilan yang berbeda ke setMenuItems
untuk MenuItem yang sama.
AppStyledView
AppStyledView
adalah penampung untuk tampilan yang sama sekali tidak disesuaikan. View ini
dapat digunakan untuk memberikan batas di sekitar tampilan yang membuatnya berbeda dari
bagian aplikasi lainnya, dan menunjukkan kepada pengguna bahwa ini adalah jenis antarmuka
yang berbeda. Tampilan yang digabungkan oleh AppStyledView diberikan di
setContent
. AppStyledView
juga dapat memiliki tombol kembali atau tutup seperti
yang diminta oleh aplikasi.
AppStyledView
tidak langsung menyisipkan tampilannya ke dalam hierarki tampilan
seperti yang dilakukan installBaseLayoutAround
, tetapi hanya menampilkan tampilannya ke
library statis melalui getView
, yang kemudian melakukan penyisipan. Posisi dan
ukuran AppStyledView
juga dapat dikontrol dengan menerapkan
getDialogWindowLayoutParam
.
Konteks
Plugin harus berhati-hati saat menggunakan Konteks, karena ada konteks plugin dan
"sumber". Konteks plugin diberikan sebagai argumen ke
getPluginFactory
, dan merupakan satu-satunya konteks yang memiliki
resource plugin di dalamnya. Artinya, ini adalah satu-satunya konteks yang dapat digunakan untuk
meng-inflate tata letak dalam plugin.
Namun, konteks plugin mungkin tidak memiliki konfigurasi yang benar. Untuk
mendapatkan konfigurasi yang benar, kami menyediakan konteks sumber dalam metode yang membuat
komponen. Konteks sumber biasanya berupa aktivitas, tetapi dalam beberapa kasus juga
dapat berupa Layanan atau komponen Android lainnya. Untuk menggunakan konfigurasi dari
konteks sumber dengan resource dari konteks plugin, konteks baru harus
dibuat menggunakan createConfigurationContext
. Jika konfigurasi yang benar tidak
digunakan, akan ada pelanggaran mode ketat Android, dan tampilan yang di-inflate mungkin
tidak memiliki dimensi yang benar.
Context layoutInflationContext = pluginContext.createConfigurationContext(
sourceContext.getResources().getConfiguration());
Perubahan mode
Beberapa plugin dapat mendukung beberapa mode untuk komponennya, seperti mode sport atau mode eco yang terlihat berbeda secara visual. Tidak ada dukungan bawaan untuk fungsi tersebut di CarUi, tetapi tidak ada yang menghentikan plugin untuk menerapkannya sepenuhnya secara internal. Plugin dapat memantau kondisi apa pun yang ingin diketahui kapan harus beralih mode, seperti mendengarkan siaran. Plugin tidak dapat memicu perubahan konfigurasi untuk mengubah mode, tetapi sebaiknya jangan mengandalkan perubahan konfigurasi karena memperbarui tampilan setiap komponen secara manual akan lebih lancar bagi pengguna dan juga memungkinkan transisi yang tidak mungkin dilakukan dengan perubahan konfigurasi.
Jetpack Compose
Plugin dapat diterapkan menggunakan Jetpack Compose, tetapi ini adalah fitur tingkat alfa dan tidak boleh dianggap stabil.
Plugin dapat menggunakan
ComposeView
untuk membuat platform yang kompatibel dengan Compose untuk dirender. ComposeView
ini akan
menjadi yang ditampilkan dari aplikasi ke metode getView
dalam komponen.
Salah satu masalah utama penggunaan ComposeView
adalah menetapkan tag pada tampilan root
dalam tata letak untuk menyimpan variabel global yang dibagikan di
ComposeViews yang berbeda dalam hierarki. Karena ID resource plugin tidak
memiliki namespace secara terpisah dari aplikasi, hal ini dapat menyebabkan konflik saat
aplikasi dan plugin menetapkan tag pada tampilan yang sama. ComposeViewWithLifecycle
kustom
yang memindahkan variabel global ini ke
ComposeView
disediakan di bawah. Sekali lagi, versi ini tidak boleh dianggap stabil.
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)
// }
}