OOZOU
تواصلوا معنا
العودة إلى المدونة

الارتقاء بـ LiveData إلى مستوى أعلى مع MediatorLiveData وTransformations

مقدمة إلى MediatorLiveData وTransformations لبناء أنماط تفاعلية (reactive) باستخدام LiveData.

7 ديسمبر 2018
4 دقائق للقراءة
الارتقاء بـ LiveData إلى مستوى أعلى مع MediatorLiveData وTransformations

MediatorLiveData

بحسب تعريفها في التوثيق، هي صنف فرعي (subclass) من LiveData يستطيع "مراقبة" كائنات LiveData أخرى، ويستدعي الدالة onChanged() عندما تتغير قيمة LiveData المراقَب.

LiveData<String> source = ....; //
final MediatorLiveData<String> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String x) {
                // do something here 
            }
        });
lang-java

في الكود أعلاه، عندما تتغير قيمة source تُستدعى الدالة onChanged الخاصة بالـ Observer.

Transformations

هذا الصنف أشبه بأداة مساعدة مبنية على MediatorLiveData. ويحتوي على دالتين فقط هما map() وswitchMap()

@MainThread
    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
            @NonNull final Function<X, Y> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(func.apply(x));
            }
        });
        return result;
    }

   @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
            @NonNull final Function<X, LiveData<Y>> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }
lang-java

تؤدي الدالتان الوظيفة نفسها: تطبّقان دالة على القيمة المخزنة في كائن LiveData، ثم تمرّران النتيجة إلى ما بعدها. الفرق أن الدالة التي نمررها إلى switchMap() يجب أن تُرجع كائن LiveData.

أنشأت مشروعًا تجريبيًا هنا. يمكنكم الاطلاع عليه ومتابعتي وأنا أشرح كيف يعمل الكود.

أستخدم https://jsonplaceholder.typicode.com/ كواجهة API للاختبار مع الـ endpoint /todos/1. وأنشأت نموذجًا باسم Todo يطابق شكل الاستجابة

import com.google.gson.annotations.SerializedName

// https://jsonplaceholder.typicode.com/todo/1
class Todo(
    @SerializedName("userId") val userId: Int,
    @SerializedName("id") val id: Int,
    @SerializedName("title") val title: String,
    @SerializedName("completed") val completed: Boolean
)
lang-kotlin

وهناك صنفان إضافيان للتعامل مع استجابات الـ API.

import android.arch.lifecycle.LiveData

data class ResponseError(val code: Int? = null, val messages: String? = null)

data class ResponseResult<T>(val data: LiveData<T>, val error: LiveData<ResponseError>)
lang-kotlin

يحتوي ResponseResult على خاصيتين، كلتاهما من نوع LiveData: الأولى للاستجابة، والثانية للخطأ (ResponseError).

وبحسب توصيات Google، أنشأت وحدة repository تتولى جلب البيانات من الشبكة. وفي هذه الوحدة استخدمت حيلة صغيرة مع ResponseResult:

class TodoRepository(private val api: TodoApi) {

    fun getTodos(): ResponseResult<Todo> {
        val data = MutableLiveData<Todo>()
        val error = MutableLiveData<ResponseError>()
        api.getTodos().enqueue(object : Callback<Todo> {
            override fun onResponse(call: Call<Todo>, response: Response<Todo>) {
                if (response.isSuccessful) {
                    data.value = response.body()
                } else {
                    error.value = ResponseError(response.code(), response.errorBody()?.string())
                }
            }

            override fun onFailure(call: Call<Todo>, t: Throwable) {
                error.value = ResponseError()
            }
        })
        return ResponseResult(data, error)
    }
}
lang-kotlin

تُرجع الدالة getTodos() كائنًا من نوع ResponseResult<Todo> له خاصيتان: قيمة data تتحدّث عند اكتمال الاستجابة، و قيمة error تتحدّث عند حدوث خطأ.

والتزامًا بمبادئ SOLID، أبقيت صنف الـ Usecase بسيطًا جدًا.

class GetTodoUsecases(private val repository: TodoRepository) {

    operator fun invoke() = repository.getTodos()
}
lang-kotlin

ربط كائنات LiveData ببعضها (Chaining)

لنلقِ نظرة على صنفَي الـ ViewModel والـ Fragment:

class MainFragment: Fragment(){
    ...
    ...
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel._onActivityCreated.call()
    }
    ...
    ...
    ...
}

class MainViewModel(private val getTodoUsecase: GetTodoUsecases) : ViewModel() {

    val _onActivityCreated = SingleLiveEvent<Any>()

    private val responseResult: LiveData<ResponseResult<Todo>> = Transformations.map(_onActivityCreated) {
        getTodoUsecase()
    }

    val todoResult: LiveData<Todo> = Transformations.switchMap(responseResult) {
        it.data
    }

    val errorResult: LiveData<ResponseError> = Transformations.switchMap(responseResult) {
        it.error
    }
}
lang-kotlin

أطلقتُ كائن LiveData (SingleLiveEvent) داخل onActivityCreated(). وإذا نظرتم إلى السطر 18 فستجدون أن Transformation.map() تُستدعى، أي أن getTodoUsecase() ستُستدعى. وبعد انتهاء الجلب تُسنَد القيمة إلى responseResult. وإذا نظرتم مجددًا إلى السطرين 22 و 26 فستجدون أن Transformation.switchMap() تُستدعى، أي أنه عندما تتحدّث قيمة responseResult سيشير الكائنان todoResult و errorResult إلى الكائنات نفسها التي تمثلها خصائص responseResult.

نصيحة: لن تُستدعى الدالة OnChanged() إذا لم يكن هناك أي مراقب (observer) لكائن MediatorLiveData.

المرجع:

https://stackoverflow.com/questions/47575961/what-is-the-difference-between-map-and-switchmap-methods

Related Articles

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

تصميم البرمجيات عملية ممتعة لكنها معقدة، تتطلب الإبداع والتفكير المنطقي ومهارات حل المشكلات.

اقرأوا المزيد
مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

يتطور المشهد الرقمي بسرعة، وعلى الشركات أن تتكيّف لتواكب الطلب المتزايد على السرعة والكفاءة والمعالجة في الوقت الفعلي.

اقرأوا المزيد
مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

يتطور قطاع التجزئة بوتيرة غير مسبوقة بفضل الذكاء الاصطناعي (AI) والواقع المعزّز (AR) وتقنيات التحوّل الرقمي. فلم تعد المتاجر التقليدية مجرد أماكن لبيع المنتجات، بل أصبحت متاجر ذكية وتفاعلية.

اقرأوا المزيد

Have a Project in Mind?

Let's discuss how we can help bring your ideas to life with our expertise in web and mobile development.

ابدأوا المحادثةhello@oozou.com
OOZOU Logo

بانكوك · سنغافورة · هونغ كونغ

X
Facebook
Instagram
LinkedIn
OOZOU Co., Ltd.
548 One City Centre
Ploenchit Road
Bangkok 10330
Thailand
+66 2544 0000
hello@oozou.com

الخدمات

  • تطوير الويب
  • وكلاء الذكاء الاصطناعي والذكاء الاصطناعي التوليدي
  • تطوير تطبيقات الجوال
  • تحليلات البيانات وهندستها
  • تصميم تجربة المستخدم وواجهته (UX/UI) وتصميم المنتجات
  • التحوّل الرقمي

الشركة

  • من نحن
  • الوظائف
  • المدونة
  • دراسات الحالة
  • عروض حية
  • تعلّمت اليوم
  • تواصل معنا

المزيد

  • القطاعات
  • شبكة الشركاء
© 2026 OOZOU. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد