रनटाइम संसाधन ओवरले (आरआरओ) का उपयोग करने के बजाय 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)
// }
}