چگونه مشکلات جدی را LiveData برطرف کنیم

ترجمه و تالیف : پوریا شریفی
تاریخ انتشار : 23 اردیبهشت 99
خواندن در 1 دقیقه
دسته بندی ها : اندروید

«وقتی چرخه‌ی زندگی یک component تغییر کرد از مشاهده کردن دوباره‌ی داده‌ها خودداری کنید». Livedata از خانواده JetPack است. بیشتر برای انتقال داده‌ها از viewmodel به فرگمنت‌ها و اکتیویتی‌ها در جدیدترین معماری‌ها مانند mvvm و clean استفاده می‌شود.

Livedata یک کلاس نگهدارنده‌ی داده با قابلیت مشاهده است. برخلاف بقیه‌ی نگهدارنده‌های داده، livedata از چرخه‌ی زندگی componentها مانند اکتیویتی، فرگمنت، سرویس‌ها و... آگاه است. همانطور که livedata از چرخه‌ی زندگی آگاه است، مطمئن می‌شود که داده‌ها را برای کسانی منتشر کند که آن را مشاهده می‌کنند و فعال هستنند، به این معنی که در پیش‌‌زمینه هستنند.

مشکل

Livedata داده‌ها را به component مقصدی که در پیش‌زمینه باشد منتشر می‌کند. اگر در پیش‌زمینه نباشد، باید داده‌ها را نگه دارد و هنگامی که component به پیش‌زمینه برگشت آن را تحویل دهد، مانند حالت OnResume.

حالا می‌دانیم که livedata چگونه کار می‌کند، بیایید در نظر بگیریم که یک livedata با چندین مشاهده‌گر و سه فرگمنت F1, F2 و F3 که در پشته هستند داریم. همه‌ی فرگمنت‌ها یک viewmodel و livedata از نوع T برای نمایش پیغام به اشتراک می‌گذارند.

اکنون، وقتی داده‌ای از نوع T از طریق livedata ارسال کنیم، بخاطر اینکه فرگمنت F3 در پیش‌زمینه است پیغام، اول در F3 نمایش داده می‌شود. خب همه‌ چی خوبه ولی اگر روی دکمه‌ی بازگشت کلیک کنیم چه اتفاقی خواهد افتاد؟ فرگمنت F2 از سرگرفته می‌شود. Livedata دوباره داده‌ها را دریافت می‌کند، بنابراین داده‌ها دوبار نمایش داده می‌شود و همین عملیات در فرگمنت F1 صورت می‌گیرد.

Livedata در زمان واقعی اینگونه کار می‌کند. ممکنه در مواردی بخواهید که بعد از مشاهده‌ی داده دوباره مشاهده نشود، راه‌حل این مشکل در بخش بعدی است.

راه‌ حل

اول از همه نیاز به ساخت یک کلاس با نام Event که داده‌های داده شده از livedata را بسته‌بندی می‌کند است. چگونگی ساخت کلاس در زیر آمده است:

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

در این قسمت از flag، hasbeenhandled برای بررسی اینکه داده‌ها حداقل یک بار مشاهده شوند استفاده می‌شود.

گام دوم

اکنون نیاز به ساخت یک کلاس دیگر با نام EventObserver که از Observer ارث برده است داریم. برای ساده کردن فرایند به کد زیر نگاه کنید:

/**
 * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
 * already been handled.
 *
 * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
 */
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
    override fun onChanged(event: Event<T>?) {
        event?.getContentIfNotHandled()?.let { value ->
            onEventUnhandledContent(value)
        }
    }
}

وقتی داده‌های جدید ارسال می‌شوند، تابع onChanged اجرا می‌شود و hasbeennhandled به false تغییر می‌کند. به محض اینکه اولین مشاهده‌گر داده را مشاهده کند، hasbeenhandled به true تغییر پیدا می‌کند، بنابراین بقیه‌ی مشاهده‌گرها داده‌ها را دریافت نکرده و این چرخه هر بار که داده‌های جدید ارسال شود تکرار می‌شود.

گام سوم

هنگام مشاهده‌ی livedata، باید از EvntObserver به جای Observer عادی استفاده کنیم، بنابراین کنترل کردن ساده می‌شود.

viewModel.observeingdata.observe(this, EventObserver { id ->
        
})

این راه‌حلی است که در اجلاس سران توسعه‌دهندگان اندروید سال 2020 مطرح شد.

منبع

گردآوری و تالیف پوریا شریفی

ابتدا که با برنامه‌نویسی آشنا شدم به سمت php و طراحی وب رفتم، بعد از اون به توسعه‌ی اندروید علاقه‌مند شدم و تقریبا ۲ سال است که مشغول به برنامه‌نویسی اندروید هستم، همچنین عاشق یادگیری چیزهای جدید هستم.