في Jetpack Compose، يمكن أن ينفّذ أحد العناصر الواجهة RememberObserver لتلقّي عمليات رد الاتصال عند استخدامه مع remember لمعرفة وقت بدء تذكّره وتوقّفه في التسلسل الهرمي للتكوين. وبالمثل، يمكنك استخدام
RetainObserver لتلقّي معلومات حول حالة عنصر مستخدَم
مع retain.
بالنسبة إلى العناصر التي تستخدم معلومات دورة الحياة هذه من التسلسل الهرمي للتركيب، ننصحك باتّباع بعض أفضل الممارسات للتأكّد من أنّ عناصرك تتوافق مع النظام الأساسي وتتجنّب إساءة الاستخدام. على وجه التحديد، استخدِم عمليات الاسترجاع onRemembered (أو onRetained) لتشغيل العمل بدلاً من الدالة الإنشائية، وألغِ جميع العمليات عندما يتوقف تذكُّر الكائنات أو الاحتفاظ بها، وتجنَّب تسريب عمليات تنفيذ RememberObserver وRetainObserver لتجنُّب عمليات الاستدعاء غير المقصودة. يوضّح القسم التالي هذه الاقتراحات بمزيد من التفصيل.
عمليات الإعداد والإزالة باستخدام RememberObserver وRetainObserver
يوضّح دليل التفكير في Compose النموذج الذهني الذي يستند إليه
التكوين. عند العمل مع RememberObserver وRetainObserver، من المهم مراعاة سلوكَين للتركيب:
- إعادة الإنشاء هي عملية متفائلة وقد يتم إلغاؤها
- يجب ألا يكون لأي دالة قابلة للإنشاء آثار جانبية.
تنفيذ التأثيرات الجانبية لعملية التهيئة أثناء onRemembered أو onRetained، وليس أثناء الإنشاء
عندما يتم تذكُّر الكائنات أو الاحتفاظ بها، يتم تنفيذ دالة lambda الخاصة بالحساب كجزء من التركيب. للأسباب نفسها التي تمنعك من تنفيذ تأثير جانبي أو تشغيل روتين فرعي أثناء عملية الإنشاء، يجب أيضًا عدم تنفيذ تأثيرات جانبية في تعبير lambda الخاص بالحساب الذي يتم تمريره إلى remember وretain وأشكالهما المختلفة.
ويشمل ذلك كجزء من الدالة الإنشائية للعناصر التي تم تذكّرها أو الاحتفاظ بها.
بدلاً من ذلك، عند تنفيذ RememberObserver أو RetainObserver، تأكَّد من إرسال جميع التأثيرات والمهام التي تم تشغيلها في دالة معاودة الاتصال onRemembered.
ويوفّر ذلك التوقيت نفسه الذي توفّره واجهات برمجة التطبيقات SideEffect. ويضمن أيضًا عدم تنفيذ هذه التأثيرات إلا عند تطبيق التركيب، ما يمنع حدوث مهام غير مكتملة وتسريب للذاكرة في حال تم إلغاء إعادة التركيب أو تأجيلها.
class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) init { // Not recommended: This will cause work to begin during composition instead of // with other effects. Move this into onRemembered(). coroutineScope.launch { loadData() } } override fun onRemembered() { // Recommended: Move any cancellable or effect-driven work into the onRemembered // callback. If implementing RetainObserver, this should go in onRetained. coroutineScope.launch { loadData() } } private suspend fun loadData() { /* ... */ } // ... }
التفكيك عند فقدان الجهاز أو إيقافه نهائيًا أو التخلّي عنه
ولتجنُّب تسريب الموارد أو ترك المهام التي تعمل في الخلفية بدون إكمالها، يجب أيضًا التخلص من العناصر التي تم تذكُّرها. بالنسبة إلى العناصر التي تنفّذ RememberObserver، يعني ذلك أنّ أي شيء يتم تهيئته في onRemembered يجب أن يتضمّن استدعاء إصدار مطابقًا في onForgotten.
بما أنّه يمكن إلغاء التجميع، يجب أن تعمل العناصر التي تنفّذ RememberObserver
على ترتيب نفسها أيضًا إذا تم تجاهلها في عمليات التجميع. يتم إيقاف عنصر عندما يتم عرضه بواسطة remember في تركيبة يتم إلغاؤها أو تفشل. (يحدث ذلك في أغلب الأحيان عند استخدام PausableComposition،
ويمكن أن يحدث أيضًا عند استخدام ميزة "إعادة التحميل السريع" مع أدوات معاينة العناصر القابلة للإنشاء في "استوديو Android").
عند التخلّي عن عنصر تم تذكّره، يتلقّى العنصر فقط طلبًا إلى onAbandoned (وليس طلبًا إلى onRemembered). لتنفيذ طريقة التخلّي، تخلَّص من أي شيء تم إنشاؤه بين وقت تهيئة العنصر والوقت الذي كان من المفترض أن يتلقّى فيه العنصر معاودة الاتصال onRemembered.
class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) // ... override fun onForgotten() { // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired // should cancel work launched from onRetained. job.cancel() } override fun onAbandoned() { // If any work was launched by the constructor as part of remembering the object, // you must cancel that work in this callback. For work done as part of the construction // during retain, this code should will appear in onUnused. job.cancel() } }
الحفاظ على خصوصية عمليات تنفيذ RememberObserver وRetainObserver
عند كتابة واجهات برمجة تطبيقات عامة، يجب توخّي الحذر عند توسيع RememberObserver وRetainObserver في إنشاء فئات يتم عرضها بشكل علني. قد لا يتذكّر المستخدم العنصر عندما تتوقّع منه ذلك، أو قد يتذكّره بطريقة مختلفة عمّا كنت تقصده. لهذا السبب، ننصح بعدم عرض الدوال الإنشائية أو دوال المصنع للعناصر التي تنفّذ RememberObserver أو RetainObserver. يُرجى العِلم أنّ هذا يعتمد على نوع وقت التشغيل لفئة ما، وليس على النوع المعلَن. فتذكُّر كائن ينفّذ RememberObserver أو RetainObserver ولكن تم تحويله إلى Any سيؤدي إلى تلقّي الكائن عمليات رد الاتصال.
لا ننصح بما يلي:
abstract class MyManager
// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }
// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()
قيمة مُقترَحة:
abstract class MyManager class MyComposeManager : MyManager() { // Callers that construct this object must manually call initialize and teardown fun initialize() { /*...*/ } fun teardown() { /*...*/ } } @Composable fun rememberMyManager(): MyManager { // Protect the RememberObserver implementation by never exposing it outside the library return remember { object : RememberObserver { val manager = MyComposeManager() override fun onRemembered() = manager.initialize() override fun onForgotten() = manager.teardown() override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ } } }.manager }
اعتبارات عند تذكُّر العناصر
بالإضافة إلى النصائح السابقة بشأن RememberObserver وRetainObserver، ننصحك أيضًا بالانتباه إلى عدم إعادة تذكُّر العناصر عن طريق الخطأ، وتجنُّب ذلك، سواء كان ذلك لأسباب تتعلّق بالأداء أو الدقة. تتضمّن الأقسام التالية تفاصيل أكثر حول سيناريوهات محدّدة لإعادة التذكير وسبب ضرورة تجنُّبها.
تذكُّر العناصر مرة واحدة فقط
قد يكون تذكُّر حدث ما بشكل خاطئ أمرًا خطيرًا. في أفضل الحالات، قد تستهلك مواردك في تذكُّر قيمة سبق تذكُّرها. ولكن إذا كان أحد العناصر ينفّذ RememberObserver وتم تذكّره مرتين بشكل غير متوقع، سيتلقّى عددًا أكبر من عمليات معاودة الاتصال مما يتوقّع. قد يؤدي ذلك إلى حدوث مشاكل، لأنّ منطق onRemembered وonForgotten سيتم تنفيذه مرتين، ومعظم عمليات تنفيذ RememberObserver لا تتيح هذه الحالة. إذا تم إجراء مكالمة ثانية لتذكُّر الحالة في نطاق مختلف له مدة بقاء مختلفة عن remember الأصلية، ستتخلص العديد من عمليات تنفيذ RememberObserver.onForgotten من العنصر قبل الانتهاء من استخدامه.
val first: RememberObserver = rememberFoo()
// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }
لا تنطبق هذه النصيحة على العناصر التي يتم تذكّرها بشكل غير مباشر (أي العناصر التي يتم تذكّرها وتستهلك عنصرًا آخر يتم تذكّره). من الشائع كتابة رمز برمجي يبدو على النحو التالي، وهو أمر مسموح به لأنّه يتم تذكّر عنصر مختلف، وبالتالي لا يؤدي إلى مضاعفة غير متوقّعة لعمليات معاودة الاتصال.
val foo: Foo = rememberFoo() // Acceptable: val bar: Bar = remember { Bar(foo) } // Recommended key usage: val barWithKey: Bar = remember(foo) { Bar(foo) }
افتراض أنّه تمّت مراعاة وسيطات الدالة
يجب ألا تتذكّر الدالة أيًا من مَعلماتها، وذلك لأنّ ذلك قد يؤدي إلى استدعاءات مزدوجة لدالة رد الاتصال في RememberObserver، ولأنّ ذلك غير ضروري. إذا كان يجب تذكُّر مَعلمة إدخال، إما أن تتأكّد من أنّها لا تنفّذ RememberObserver، أو أن تطلب من المتصلين تذكُّر وسيطتهم.
@Composable
fun MyComposable(
parameter: Foo
) {
// Not Recommended: Input should be remembered by the caller.
val rememberedParameter = remember { parameter }
}
لا ينطبق ذلك على العناصر التي يتم تذكّرها بشكل متعدٍّ. عند تذكُّر
عنصر مشتق من وسيطات دالة، ننصحك بتحديده كأحد
مفاتيح remember:
@Composable fun MyComposable( parameter: Foo ) { // Acceptable: val derivedValue = remember { Bar(parameter) } // Also Acceptable: val derivedValueWithKey = remember(parameter) { Bar(parameter) } }
عدم الاحتفاظ بعنصر تم تذكّره من قبل
على غرار إعادة تذكُّر عنصر، عليك تجنُّب الاحتفاظ بعنصر تم تذكُّره لمحاولة إطالة مدة بقائه. هذا نتيجة للنصيحة الواردة في
مدد بقاء الحالة: يجب عدم استخدام retain مع العناصر التي لها مدة بقاء لا تتطابق مع مدة بقاء العروض الترويجية. بما أنّ مدة بقاء عناصر remembered أقصر من مدة بقاء عناصر retained، يجب عدم الاحتفاظ بعنصر تم تذكّره. بدلاً من ذلك، يُفضّل الاحتفاظ بالكائن في الموقع الأصلي
بدلاً من تذكّره.