कार यूआई प्लगइन्स

रनटाइम संसाधन ओवरले (आरआरओ) का उपयोग करने के बजाय car-ui-lib में घटक अनुकूलन के पूर्ण कार्यान्वयन के लिए car-ui-lib प्लगइन्स का उपयोग करें। आरआरओ आपको car-ui-lib घटकों के केवल एक्सएमएल संसाधनों को बदलने में सक्षम बनाता है, जो उस सीमा तक सीमित करता है जिसे आप अनुकूलित कर सकते हैं।

एक प्लगइन बनाना

car-ui-lib प्लगइन एक एपीके है जिसमें ऐसे वर्ग शामिल हैं जो प्लगइन एपीआई के एक सेट को लागू करते हैं। प्लगइन एपीआई packages/apps/Car/libs/car-ui-lib/oem-apis में स्थित हैं और एक स्थिर पुस्तकालय के रूप में एक प्लगइन में संकलित किया जा सकता है।

नीचे दिए गए सूंग और ग्रैडल उदाहरण देखें:

सूंग

इस सूंग उदाहरण पर विचार करें:

android_app {
    name: "my-plugin",

    min_sdk_version: "28",
    target_sdk_version: "30",
    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",
    ],

    optimize: {
        enabled: false,
    },

    certificate: ":my-plugin-certificate",

ग्रेडल

यह build.gradle फ़ाइल देखें:

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

प्लगइन के पास अपने मेनिफेस्ट में घोषित सामग्री प्रदाता होना चाहिए जिसमें निम्नलिखित विशेषताएं हों:

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

android:authorities="com.android.car.ui.plugin" प्लगइन को car-ui-lib में खोजने योग्य बनाता है। प्रदाता को निर्यात किया जाना है ताकि इसे रनटाइम पर पूछताछ की जा सके। साथ ही, यदि enabled विशेषता को false पर सेट किया गया है, तो प्लगइन कार्यान्वयन के बजाय डिफ़ॉल्ट कार्यान्वयन का उपयोग किया जाएगा। सामग्री प्रदाता वर्ग मौजूद नहीं है। इस मामले में, प्रदाता परिभाषा में tools:ignore="MissingClass" जोड़ना सुनिश्चित करें। नीचे नमूना मेनिफेस्ट प्रविष्टि देखें:

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

अंत में, सुरक्षा उपाय के रूप में, अपने ऐप पर हस्ताक्षर करें

एक प्लगइन स्थापित करना

एक बार जब आप प्लगइन बना लेते हैं, तो इसे किसी अन्य ऐप की तरह इंस्टॉल किया जा सकता है, जैसे इसे PRODUCT_PACKAGES में जोड़ना या adb install का उपयोग करना। हालांकि, अगर यह प्लगइन का एक नया, ताजा इंस्टॉल है, तो परिवर्तनों को प्रभावी होने के लिए ऐप्स को पुनरारंभ करना होगा। यह किसी विशिष्ट ऐप के लिए पूर्ण adb reboot , या adb shell am force-stop package.name निष्पादित करके किया जा सकता है।

यदि आप सिस्टम पर किसी मौजूदा car-ui-lib प्लगइन को अपडेट कर रहे हैं, तो उस प्लग-इन का उपयोग करने वाला कोई भी ऐप अपने आप बंद हो जाता है और, उपयोगकर्ता द्वारा एक बार फिर से खोलने पर, अपडेट किए गए परिवर्तन होते हैं। यदि ऐप्स उस समय अग्रभूमि में हों तो यह क्रैश जैसा लगता है। यदि ऐप नहीं चल रहा था, तो अगली बार शुरू होने पर इसमें अपडेटेड प्लगइन होता है।

एंड्रॉइड स्टूडियो के साथ एक प्लगइन स्थापित करते समय, ध्यान में रखने के लिए कुछ अतिरिक्त विचार हैं। लेखन के समय, एंड्रॉइड स्टूडियो ऐप इंस्टॉलेशन प्रक्रिया में एक बग है जो एक प्लगइन के अपडेट को प्रभावी नहीं होने का कारण बनता है। इसे प्लगइन के बिल्ड कॉन्फ़िगरेशन में हमेशा पैकेज मैनेजर के साथ इंस्टॉल करें विकल्प का चयन करके तय किया जा सकता है (एंड्रॉइड 11 और बाद के संस्करण पर अनुकूलन को अक्षम करता है)

इसके अलावा, प्लगइन स्थापित करते समय, एंड्रॉइड स्टूडियो एक त्रुटि की रिपोर्ट करता है कि इसे लॉन्च करने के लिए एक मुख्य गतिविधि नहीं मिल रही है। यह अपेक्षित है, क्योंकि प्लगइन में कोई गतिविधि नहीं है (एक इरादे को हल करने के लिए उपयोग किए गए खाली इरादे को छोड़कर)। त्रुटि को समाप्त करने के लिए, बिल्ड कॉन्फ़िगरेशन में लॉन्च विकल्प को कुछ भी नहीं में बदलें।

प्लगइन एंड्रॉइड स्टूडियो कॉन्फ़िगरेशन चित्रा 1. प्लगइन एंड्रॉइड स्टूडियो कॉन्फ़िगरेशन

प्लगइन एपीआई को लागू करना

प्लगइन का मुख्य प्रवेश बिंदु com.android.car.ui.plugin.PluginVersionProviderImpl वर्ग है। सभी प्लगइन्स में इस सटीक नाम और पैकेज नाम के साथ एक वर्ग शामिल होना चाहिए। इस वर्ग में एक डिफ़ॉल्ट कंस्ट्रक्टर होना चाहिए और PluginVersionProviderOEMV1 इंटरफ़ेस को लागू करना चाहिए।

CarUi प्लगइन्स को उन ऐप्स के साथ काम करना चाहिए जो प्लगइन से पुराने या नए हैं। इसे सुविधाजनक बनाने के लिए, सभी प्लगइन एपीआई को उनके वर्गनाम के अंत में V# के साथ संस्करणित किया जाता है। यदि नई सुविधाओं के साथ car-ui-lib का नया संस्करण जारी किया जाता है, तो वे घटक के V2 संस्करण का हिस्सा होते हैं। car-ui-lib नई सुविधाओं को पुराने प्लगइन घटक के दायरे में काम करने की पूरी कोशिश करता है। उदाहरण के लिए, टूलबार में एक नए प्रकार के बटन को MenuItems में कनवर्ट करके।

हालांकि, car-ui-lib के पुराने संस्करण वाला एक पुराना ऐप नए एपीआई के खिलाफ लिखे गए नए प्लगइन के अनुकूल नहीं हो सकता है। इस समस्या को हल करने के लिए, हम प्लगइन्स को ऐप्स द्वारा समर्थित OEM API के संस्करण के आधार पर स्वयं के विभिन्न कार्यान्वयनों को वापस करने की अनुमति देते हैं।

PluginVersionProviderOEMV1 में एक विधि है:

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

यह विधि एक ऐसी वस्तु लौटाती है जो प्लगइन द्वारा समर्थित PluginFactoryOEMV# के उच्चतम संस्करण को लागू करती है, जबकि अभी भी maxVersion से कम या बराबर है। यदि किसी प्लगइन में पुराने प्लगइन फैक्ट्री का कार्यान्वयन नहीं है, तो यह null वापस आ सकता है, इस मामले में PluginFactory घटकों के सांख्यिकीय रूप से लागू कार्यान्वयन का उपयोग किया जाता है।

प्लगइन फैक्ट्री वह इंटरफ़ेस है जो अन्य सभी PluginFactory घटकों को बनाता है। यह यह भी परिभाषित करता है कि उनके इंटरफेस के किस संस्करण का उपयोग किया जाना चाहिए। यदि प्लगइन इनमें से किसी भी घटक को लागू करने की कोशिश नहीं करता है, तो यह उनके निर्माण कार्य में null वापस आ सकता है (टूलबार के अपवाद के साथ, जिसमें एक अलग customizesBaseLayout() फ़ंक्शन है)।

प्लगइनFactory सीमित करता है कि pluginFactory घटकों के कौन से संस्करण एक साथ उपयोग किए जा सकते हैं। उदाहरण के लिए, कोई प्लगइन फैक्ट्री कभी नहीं होगी जो Toolbar का संस्करण 100 और pluginFactory का संस्करण 1 भी बना RecyclerView , क्योंकि इस बात की बहुत कम गारंटी होगी कि घटकों के विभिन्न संस्करण एक साथ काम करेंगे। टूलबार संस्करण 100 का उपयोग करने के लिए, डेवलपर्स से pluginFactory के एक संस्करण का कार्यान्वयन प्रदान करने की अपेक्षा की जाती है जो टूलबार संस्करण 100 बनाता है, जो तब अन्य घटकों के संस्करणों पर विकल्पों को सीमित करता है जिन्हें बनाया जा सकता है। अन्य घटकों के संस्करण समान नहीं हो सकते हैं, उदाहरण के लिए एक प्लगइन ToolbarControllerOEMV100 pluginFactoryOEMV100 एक RecyclerViewOEMV70 बना सकता है।

उपकरण पट्टी

आधार लेआउट

टूलबार और "बेस लेआउट" बहुत निकट से संबंधित हैं, इसलिए टूलबार बनाने वाले फ़ंक्शन को installBaseLayoutAround कहा जाता है। बेस लेआउट एक अवधारणा है जो टूलबार को ऐप की सामग्री के चारों ओर कहीं भी स्थित करने की अनुमति देता है, ऐप के ऊपर/नीचे टूलबार की अनुमति देता है, लंबवत रूप से पक्षों के साथ, या यहां तक ​​​​कि पूरे ऐप को घेरने वाला एक गोलाकार टूलबार भी। यह टूलबार/आधार लेआउट के चारों ओर लपेटने के लिए एक दृश्य को installBaseLayoutAround पास करके पूरा किया जाता है।

प्लगइन को प्रदान किए गए दृश्य को लेना चाहिए, इसे अपने माता-पिता से अलग करना चाहिए, माता-पिता की एक ही अनुक्रमणिका में प्लगइन के स्वयं के लेआउट को बढ़ाना चाहिए और उसी लेआउट LayoutParams के साथ जो कि अभी-अभी अलग किया गया था, और फिर उस लेआउट के अंदर कहीं भी दृश्य को दोबारा जोड़ें बस फुलाया। ऐप द्वारा अनुरोध किए जाने पर फुलाए गए लेआउट में टूलबार होगा।

ऐप बिना टूलबार के बेस लेआउट का अनुरोध कर सकता है। यदि ऐसा होता है, installBaseLayoutAround को अशक्त लौटना चाहिए। अधिकांश प्लगइन्स के लिए, बस इतना ही होना चाहिए, लेकिन अगर प्लगइन लेखक आवेदन करना चाहता है, जैसे कि ऐप के किनारे के आसपास की सजावट, जो अभी भी एक बेस लेआउट के साथ किया जा सकता है। ये सजावट गैर-आयताकार स्क्रीन वाले उपकरणों के लिए विशेष रूप से उपयोगी हैं, क्योंकि वे ऐप को एक आयताकार स्थान में धकेल सकते हैं और गैर-आयताकार स्थान में स्वच्छ संक्रमण जोड़ सकते हैं।

installBaseLayoutAround को Consumer<InsetsOEMV1> भी पास किया गया है। इस उपभोक्ता का उपयोग ऐप से संवाद करने के लिए किया जा सकता है कि प्लगइन आंशिक रूप से ऐप की सामग्री को कवर कर रहा है (टूलबार के साथ या अन्यथा)। तब ऐप इस स्पेस में ड्राइंग रखना जानता होगा, लेकिन किसी भी महत्वपूर्ण उपयोगकर्ता-इंटरैक्टेबल घटकों को इससे बाहर रखें। टूलबार को अर्ध-पारदर्शी बनाने के लिए इस आशय का उपयोग हमारे संदर्भ डिज़ाइन में किया जाता है, और इसके नीचे सूचियाँ स्क्रॉल होती हैं। यदि यह सुविधा लागू नहीं की गई थी, तो सूची में पहला आइटम टूलबार के नीचे अटक जाएगा और क्लिक करने योग्य नहीं होगा। यदि इस आशय की आवश्यकता नहीं है, तो प्लगइन उपभोक्ता को अनदेखा कर सकता है।

टूलबार के नीचे सामग्री स्क्रॉल करना चित्र 2. टूलबार के नीचे सामग्री स्क्रॉल करना

ऐप के दृष्टिकोण से, जब प्लगइन नए इनसेट भेजता है, तो यह उन्हें किसी भी गतिविधियों/टुकड़ों के माध्यम से प्राप्त करेगा जो InsetsChangedListener को लागू करता है। यहां एक कार्यान्वयन का एक उदाहरण दिया गया है जो इनसेट को ऐप में एक पुनर्चक्रण पर पैडिंग के रूप में लागू करता है:

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

अंत में, प्लगइन को एक fullscreen संकेत दिया जाता है, जिसका उपयोग यह इंगित करने के लिए किया जाता है कि क्या जिस दृश्य को लपेटा जाना चाहिए वह संपूर्ण ऐप या केवल एक छोटा सा खंड लेता है। इसका उपयोग किनारे पर कुछ सजावट लगाने से बचने के लिए किया जा सकता है जो केवल तभी समझ में आता है जब वे पूरी स्क्रीन के किनारे पर दिखाई देते हैं। एक नमूना ऐप जो गैर-पूर्णस्क्रीन आधार लेआउट का उपयोग करता है वह है सेटिंग्स, जिसमें दोहरे फलक लेआउट के प्रत्येक फलक का अपना टूलबार होता है।

चूंकि यह अपेक्षा की जाती है कि installBaseLayoutAround के लिए टूलबार सक्षम होने पर शून्य वापस आ जाए, प्लगइन के लिए यह इंगित करने के लिए कि यह आधार लेआउट को customizesBaseLayout नहीं करना चाहता false , इसे toolbarEnabled से false वापस करना होगा।

रोटरी नियंत्रणों को पूरी तरह से समर्थन देने के लिए बेस लेआउट में FocusParkingView और FocusArea एरिया होना चाहिए। इन दृश्यों को उन उपकरणों पर छोड़ा जा सकता है जो रोटरी का समर्थन नहीं करते हैं। FocusParkingView/FocusAreas स्थिर कारयूआई लाइब्रेरी में कार्यान्वित किए जाते हैं, इसलिए संदर्भों से विचार बनाने के लिए कारखानों को प्रदान करने के लिए एक setRotaryFactories का उपयोग किया जाता है।

फ़ोकस दृश्य बनाने के लिए उपयोग किए जाने वाले संदर्भ स्रोत संदर्भ होने चाहिए, न कि प्लगइन के संदर्भ में। FocusParkingView व्यू यथोचित रूप से पेड़ में पहले दृश्य के सबसे करीब होना चाहिए, क्योंकि यह वही है जो तब केंद्रित होता है जब उपयोगकर्ता को कोई फोकस दिखाई नहीं देना चाहिए। FocusArea को टूलबार को बेस लेआउट में लपेटना चाहिए ताकि यह इंगित किया जा सके कि यह एक रोटरी न्यूड ज़ोन है। यदि FocusArea प्रदान नहीं किया गया है, तो उपयोगकर्ता रोटरी कंट्रोलर के साथ टूलबार में किसी भी बटन पर नेविगेट करने में असमर्थ है।

टूलबार नियंत्रक

लौटाया गया वास्तविक ToolbarController बेस लेआउट की तुलना में लागू करने के लिए अधिक सरल होना चाहिए। इसका काम अपने बसने वालों को दी गई जानकारी को ले जाना और इसे बेस लेआउट में प्रदर्शित करना है। अधिकांश विधियों के बारे में जानकारी के लिए जावाडोक देखें। कुछ अधिक जटिल विधियों की चर्चा नीचे की गई है।

getImeSearchInterface का उपयोग IME (कीबोर्ड) विंडो में खोज परिणाम दिखाने के लिए किया जाता है। यह कीबोर्ड के साथ-साथ खोज परिणामों को प्रदर्शित/एनिमेट करने के लिए उपयोगी हो सकता है, उदाहरण के लिए यदि कीबोर्ड केवल स्क्रीन का आधा हिस्सा लेता है। अधिकांश कार्यक्षमता स्थिर CarUi लाइब्रेरी में कार्यान्वित की जाती है, प्लगइन में खोज इंटरफ़ेस केवल onPrivateIMECommand TextView प्राप्त करने के लिए स्थिर लाइब्रेरी के तरीके प्रदान करता है। इसका समर्थन करने के लिए, प्लगइन को एक TextView उपवर्ग का उपयोग करना चाहिए जो onPrivateIMECommand को ओवरराइड करता है और प्रदान किए गए श्रोता को कॉल को इसके खोज बार के TextView के रूप में पास करता है।

setMenuItems बस स्क्रीन पर मेनू आइटम प्रदर्शित करता है, लेकिन इसे अक्सर आश्चर्यजनक रूप से कहा जाएगा। चूंकि MenuItems के लिए प्लगइन API अपरिवर्तनीय हैं, जब भी कोई MenuItem बदला जाता है, तो एक नया setMenuItems कॉल होगा। यह कुछ तुच्छ के लिए हो सकता है क्योंकि उपयोगकर्ता ने एक स्विच मेनूइटम पर क्लिक किया था, और उस क्लिक ने स्विच को टॉगल करने का कारण बना दिया। प्रदर्शन और एनीमेशन दोनों कारणों से, इसलिए पुरानी और नई मेनू आइटम सूची के बीच अंतर की गणना करने के लिए प्रोत्साहित किया जाता है, और केवल उन दृश्यों को अपडेट करें जो वास्तव में बदल गए हैं। MenuItems एक key फ़ील्ड प्रदान करता है जो इसके साथ मदद कर सकता है, क्योंकि एक ही MenuItem के लिए setMenuItems के लिए विभिन्न कॉलों में कुंजी समान होनी चाहिए।

ऐप स्टाइल व्यू

AppStyledView एक दृश्य के लिए एक कंटेनर है जिसे बिल्कुल भी अनुकूलित नहीं किया गया है। इसका उपयोग उस दृश्य के चारों ओर एक सीमा प्रदान करने के लिए किया जा सकता है जो इसे बाकी ऐप से अलग बनाता है, और उपयोगकर्ता को संकेत देता है कि यह एक अलग तरह का इंटरफ़ेस है। AppStyledView द्वारा लपेटा गया दृश्य setContent में दिया गया है। AppStyledView में ऐप के अनुरोध के अनुसार बैक या क्लोज बटन भी हो सकता है।

AppStyledView तुरंत अपने दृश्यों को दृश्य पदानुक्रम में सम्मिलित नहीं करता है जैसे installBaseLayoutAround करता है, यह इसके बजाय स्थिर पुस्तकालय को getView के माध्यम से अपना दृश्य देता है, जो तब सम्मिलन करता है। AppStyledView की स्थिति और आकार को AppStyledView को लागू getDialogWindowLayoutParam भी नियंत्रित किया जा सकता है।

संदर्भों

संदर्भों का उपयोग करते समय प्लगइन को सावधान रहना चाहिए, क्योंकि प्लगइन और "स्रोत" संदर्भ दोनों हैं। प्लगइन संदर्भ getPluginFactory के तर्क के रूप में दिया गया है, और यह एकमात्र संदर्भ है जिसमें प्लगइन के संसाधन होने की गारंटी है। इसका मतलब है कि यह एकमात्र संदर्भ है जिसका उपयोग प्लगइन में लेआउट को बढ़ाने के लिए किया जा सकता है।

हालाँकि, प्लगइन संदर्भ में उस पर सही कॉन्फ़िगरेशन सेट नहीं हो सकता है। सही कॉन्फ़िगरेशन प्राप्त करने के लिए, हम घटक बनाने वाली विधियों में स्रोत संदर्भ प्रदान करते हैं। स्रोत संदर्भ आमतौर पर एक गतिविधि है, लेकिन कुछ मामलों में यह एक सेवा या अन्य Android घटक भी हो सकता है। प्लगइन संदर्भ से संसाधनों के साथ स्रोत संदर्भ से कॉन्फ़िगरेशन का उपयोग करने के लिए, createConfigurationContext का उपयोग करके एक नया संदर्भ बनाया जाना चाहिए। यदि सही कॉन्फ़िगरेशन का उपयोग नहीं किया जाता है, तो एंड्रॉइड सख्त मोड उल्लंघन होगा, और फुलाए गए विचारों में सही आयाम नहीं हो सकते हैं।

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

मोड में बदलाव

कुछ प्लगइन्स अपने घटकों के लिए कई मोड का समर्थन कर सकते हैं, जैसे कि स्पोर्ट मोड या इको मोड जो नेत्रहीन रूप से अलग दिखते हैं। CarUi में ऐसी कार्यक्षमता के लिए कोई अंतर्निहित समर्थन नहीं है, लेकिन प्लगइन को पूरी तरह से आंतरिक रूप से लागू करने से कोई रोक नहीं सकता है। प्लग-इन उन सभी स्थितियों की निगरानी कर सकता है जो यह पता लगाना चाहती हैं कि मोड को कब स्विच करना है, जैसे कि प्रसारण सुनना। प्लगइन मोड बदलने के लिए कॉन्फ़िगरेशन परिवर्तन को ट्रिगर नहीं कर सकता है, लेकिन किसी भी तरह से कॉन्फ़िगरेशन परिवर्तनों पर भरोसा करने की अनुशंसा नहीं की जाती है, क्योंकि प्रत्येक घटक की उपस्थिति को मैन्युअल रूप से अपडेट करना उपयोगकर्ता के लिए आसान होता है और उन बदलावों की भी अनुमति देता है जो कॉन्फ़िगरेशन परिवर्तनों के साथ संभव नहीं हैं।

जेटपैक लिखें

जेटपैक कंपोज़ का उपयोग करके प्लगइन्स को लागू किया जा सकता है, लेकिन यह एक अल्फा-स्तरीय विशेषता है और इसे स्थिर नहीं माना जाना चाहिए।

प्रस्तुत करने के लिए कंपोज़-सक्षम सतह बनाने के लिए प्लगइन्स ComposeView का उपयोग कर सकते हैं। यह ComposeView वह होगा जो घटकों में getView विधि से ऐप से लौटाया जाता है।

ComposeView का उपयोग करने के साथ एक प्रमुख समस्या यह है कि यह पदानुक्रम में विभिन्न ComposeViews में साझा किए गए वैश्विक चर को संग्रहीत करने के लिए लेआउट में रूट व्यू पर टैग सेट करता है। चूंकि प्लगइन के संसाधन आईडी को ऐप से अलग से नाम नहीं दिया गया है, यह तब विरोध का कारण बन सकता है जब ऐप और प्लगइन दोनों एक ही दृश्य पर टैग सेट करते हैं। एक कस्टम ComposeViewWithLifecycle जो इन वैश्विक चरों को ComposeView में नीचे ले जाता है, नीचे दिया गया है। फिर, इसे स्थिर नहीं माना जाना चाहिए।

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