بازخوان‌های وضعیت Compose با RememberObserver و RetainObserver

در Jetpack Compose، یک شیء می‌تواند RememberObserver را پیاده‌سازی کند تا وقتی با remember استفاده می‌شود، کاربران بتوانند با دریافت کردن تماس‌های برگشتی از شروع و پایان به‌یاد آورده شدن آن در سلسله‌مراتب ترکیب مطلع شوند. به‌همین ترتیب، می‌توانید از RetainObserver برای دریافت اطلاعات درباره وضعیت شیء استفاده‌شده با retain استفاده کنید.

برای اشیایی که از این اطلاعات چرخه حیات از سلسله‌مراتب ترکیب استفاده می‌کنند، چند روال مطلوب برای تأیید اینکه اشیای شما در پلاتفرم عملکرد خوبی دارند و دربرابر سوءاستفاده محافظت می‌کنند توصیه می‌کنیم. به‌طور خاص، از onRemembered (یا onRetained) پاسخ‌برگ‌ها برای راه‌اندازی کار به‌جای سازنده استفاده کنید، وقتی اشیا دیگر به‌خاطر آورده یا حفظ نمی‌شوند، همه کارها را لغو کنید، و از نشت پیاده‌سازی‌های RememberObserver و RetainObserver برای جلوگیری از تماس‌های تصادفی اجتناب کنید. بخش بعدی این توصیه‌ها را با جزئیات بیشتری توضیح می‌دهد.

مقداردهی اولیه و پاک‌سازی با RememberObserver و RetainObserver

راهنمای «تفکر در حالت نوشتن» مدل ذهنی پشت ترکیب را شرح می‌دهد. هنگام کار با RememberObserver و RetainObserver، مهم است که دو رفتار ترکیب را درنظر داشته باشید:

  • ترکیب مجدد خوش‌بینانه است و ممکن است لغو شود
  • همه توابع ترکیب‌پذیر نباید اثر جانبی داشته باشند

اجرای اثرات جانبی مقداردهی اولیه درطول onRemembered یا onRetained، نه ساخت

وقتی اشیا به‌خاطر سپرده یا حفظ می‌شوند، لامبدای محاسبه به‌عنوان بخشی از ترکیب اجرا می‌شود. به همان دلایلی که نباید درطول ترکیب، عوارض جانبی انجام دهید یا روال هم‌زمان راه‌اندازی کنید، نباید در لامبدای محاسباتی که به 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 شیء است، نباید شیء به‌یادمانده را حفظ کنید. به‌جای آن، ترجیح دهید شیء را در سایت مبدأ حفظ کنید به‌جای اینکه آن را به‌خاطر بسپارید.