نوشتن فراخوانی‌های وضعیت با RememberObserver و RetainObserver

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

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

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

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

  • ترکیب مجدد خوشبینانه است و ممکن است لغو شود
  • تمام توابع قابل ترکیب نباید عوارض جانبی داشته باشند

عوارض جانبی مقداردهی اولیه را در طول onRemembered یا onRetained اجرا کنید، نه در طول ساخت و ساز

وقتی اشیاء به خاطر سپرده می‌شوند یا حفظ می‌شوند، محاسبه لامبدا به عنوان بخشی از ترکیب اجرا می‌شود. به همان دلایلی که شما در طول ترکیب، یک اثر جانبی را اجرا نمی‌کنید یا یک کوروتین را راه‌اندازی نمی‌کنید، نباید در محاسبه لامبدا که به remember ، retain و انواع آنها ارسال می‌شود، اثرات جانبی را نیز اجرا کنید. این شامل بخشی از سازنده برای اشیاء به خاطر سپرده شده یا حفظ شده نیز می‌شود.

در عوض، هنگام پیاده‌سازی RememberObserver یا RetainObserver ، تأیید کنید که تمام افکت‌ها و کارهای راه‌اندازی‌شده در فراخوانی onRemembered ارسال می‌شوند. این روش زمان‌بندی مشابهی با APIهای 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 رخ می‌دهد و همچنین می‌تواند هنگام استفاده از بارگذاری مجدد سریع با ابزار پیش‌نمایش ترکیب‌بندی اندروید استودیو رخ دهد.)

وقتی یک شیء به خاطر سپرده شده رها می‌شود، فقط فراخوانی onAbandoned را دریافت می‌کند (و هیچ فراخوانی onRemembered را دریافت نمی‌کند). برای پیاده‌سازی متد abandon، هر چیزی که بین زمان مقداردهی اولیه شیء و زمانی که شیء فراخوانی 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 خصوصی نگه دارید

هنگام نوشتن APIهای عمومی، هنگام توسعه‌ی 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 در یک محدوده متفاوت که طول عمر متفاوتی از 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) }
}

چیزی را که از قبل به خاطر دارید، نگه ندارید

مشابه یادآوری مجدد یک شیء، باید از نگه‌داری شیء‌ای که به خاطر سپرده شده است، برای افزایش طول عمر آن خودداری کنید. این نتیجه‌ی توصیه‌ی موجود در State lifetimes است: retain نباید با اشیایی استفاده شود که طول عمرشان با طول عمر پیشنهادی retain مطابقت ندارد. از آنجایی که اشیاء remembered طول عمر کوتاه‌تری نسبت به اشیاء retained دارند، نباید یک شیء به خاطر سپرده شده را نگه دارید. در عوض، ترجیح می‌دهید شیء را در محل مبدا به جای به خاطر سپردن آن، نگه دارید.