نموذج العرض ViewModel

الإعلانات

نموذج العرض ViewModel نظرة عامه 

 

 

نموذج العرض

تم تصميم فئة ViewModel لتخزين وإدارة البيانات، المتعلقة بواجهة المستخدم، بطريقة مدركة لدورة الحياة.

تتيح فئة ViewModel للبيانات، الصمود أثناء تغييرات التكوين مثل تغير إتجاه الشاشة.

ملاحظة: لإستيراد ViewModel إلى مشروع الأندرويد، راجع إرشادات إعلان الإعتمادات ملاحظات إصدار دورة الحياه.

 

إطار عمل الأندرويد، يقوم بإدارة دورات حياة وحدات تحكم واجهة المستخدم، مثل الأنشطة والشظايا.

قد يقرر إطار العمل، تدمير أو إعادة إنشاء، وحدة تحكم واجهة المستخدم، إستجابةً لبعض إجراءات المستخدم، أو أحداث الجهاز، الخارجة كلياً عن نطاق سيطرتك.

في حالة قيام النظام، بتدمير أو إعادة إنشاء وحدة تحكم واجهة المستخدم، فإن أية بيانات مرتبطة بواجهة المستخدم، والتي تخزنها بها، سوف تُفقد.

مثال، قد يحتوي تطبيقك على قائمة بالمستخدمين في إحدى أنشطته. عند إعادة إنشاء النشاط، بسبب حدوث تغير في التكوين..

يجب على النشاط الجديد، إعادة جلب قائمة المستخدمين. بالنسبة للبيانات البسيطة، يمكن للنشاط إستخدام دالة ()onSaveInstanceState ..

وإستعادة بياناته من الحزمة في ()onCreate ، ولكن هذا الأسلوب مناسب فقط، للكميات الصغيرة من البيانات..

التي يمكن، إجراء عمل تسلسل لها، ثم إلغاء تسلسلها، وليس للكميات الكبيرة من البيانات مثل قائمة المستخدمين أو الصور النقطية.

مشكلة أخرى هي، أن وحدات تحكم واجهة المستخدم، تحتاج في كثير من الأحيان، إلى إجراء إستدعاءات غير متزامنة، قد تستغرق بعض الوقت لإرجاعها.

تحتاج وحدة تحكم واجهة المستخدم، إلى إدارة هذه الإستدعاءات، والتأكد من أن النظام يقوم بتنظيفها، بعد تدميرها، لتجنب حدوث تسرب محتمل للذاكرة.

تتطلب هذه الإدارة الكثير من الصيانة، وفي حالة إعادة إنشاء الكائن، بسبب حدوث تغير في التكوين..

فإن هذا يعد إهداراً للموارد، لأن الكائن قد يضطر إلى إعادة إصدار الإستدعاءات، التي أجراها بالفعل.

وحدات تحكم واجهة المستخدم، مثل الأنشطة والشظايا، تهدف في المقام الأول إلى عرض بيانات واجهة المستخدم..

أو الإستجابة لإجراءات المستخدم، أو التعامل مع إتصالات نظام التشغيل، مثل طلبات الأذونات.

إن مطالبة وحدات تحكم واجهة المستخدم، أيضاً بأن تكون مسؤولة عن تحميل البيانات، من قاعدة بيانات، أو من الشبكة، يضيف تضخماً إلى الفئه.

إلقاء المسؤولية الزائدة على وحدات تحكم واجهة المستخدم، يمكن أن يؤدي إلى محاولة فئة واحدة، التعامل مع كل أعمال التطبيق بنفسها..

بدلاً من تفويض فئات أخرى بالعمل. كما أن إلقاء المسؤولية الزائدة، على وحدات تحكم واجهة المستخدم بهذه الطريقة، يجعل الإختبار أكثر صعوبة.

من الأسهل والأكثر فعالية، فصل ملكية بيانات العرض، من منطق وحدة تحكم واجهة المستخدم.

 

 

 

 

 

نموذج العرض

 

تنفيذ ViewModel


توفر مكونات البنيه، فئة مساعد ViewModel لوحدة تحكم واجهة المستخدم، المسؤولة عن تجهيز البيانات لواجهة المستخدم.

يتم الإحتفاظ بكائنات ViewModel تلقائياً، أثناء تغييرات التكوين، بحيث تكون البيانات التي يحتفظ بها، متاحة على الفور للنشاط التالي أو لمثيل الشظيه.

مثال، إذا أردت عرض قائمة بالمستخدمين في تطبيقك، فتأكد من تعيين المسؤولية للحصول والحفاظ على..

قائمة المستخدمين في قائمة ViewModel ،بدلاً من النشاط أو الشظيه، كما هو موضح في نموذج الكود التالي: 

KOTLIN

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

JAVA

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

 

يمكنك بعد ذلك الوصول إلى القائمة من النشاط على النحو التالي:

KOTLIN

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

JAVA

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

 

إذا تم إعادة إنشاء النشاط، فإنه يتلقى نفس مثيل MyViewModel الذي تم إنشاؤه بواسطة النشاط الأول.

عند إنتهاء النشاط المالك، يقوم إطار العمل بإستدعاء دالة ()onCleared الخاصه بـ ViewModel حتى يتمكن من تنظيف الموارد.

تحذير: يجب ألا يشير viewModel مطلقاً إلى طريقة عرض، دورة حياة، أو أي فئة قد تحتوي على إشارة إلى سياق النشاط.

 

تم تصميم كائنات ViewModel لتعيش فترة أطول في مثيلات محددة من views أو LifecycleOwners.

ويعني هذا التصميم أيضاً، أنه يمكنك وضع إختبارات لتغطية ViewModel بسهولة أكبر طالما أنه لا يعرف عن كائنات “view” و  Lifecycle.

يمكن أن تحتوي كائنات ViewModel على LifecycleObservers، مثل كائنات LiveData.

ومع ذلك، يجب ألا تراقب كائنات ViewModel أبداً التغييرات، التي تطرأ على المراقبه المدركة لدورة الحياة، مثل كائنات LiveData.

إذا كانت ViewModel بحاجة إلى سياق التطبيق، على سبيل المثال، لإيجاد خدمة نظام، فيمكنه توسيع فئة AndroidViewModel ..

والحصول على مُنشئ، يتلقى التطبيق في المُنشئ، طالما فئة التطبيق تقوم بتوسيع السياق.

 

 

 

 

نموذج العرض

 

دورة حياة ViewModel


يتم تحديد نطاق كائنات ViewModel إلى دورة الحياة Lifecycle التي تم تمريرها إلى ViewModelProvider عند الحصول على ViewModel.

يظل ViewModel في الذاكرة، حتى يتم تحديد نطاق دورة الحياة Lifecycle ليختفي بشكلٍ دائم:

في حالة النشاط، عندما ينتهي. بينما في حالة الشظيه، عندما يتم فصلها.

يوضح الشكل 1 حالات دورة الحياة المختلفة، لنشاطٍ ما، أثناء خضوعه لتغيير الإتجاه، ثم إنتهاؤه.

يعرض الرسم التوضيحي أيضاً، مدى حياة ViewModel ، إلى جانب دورة حياة النشاط المقترنة.

يوضح هذا المخطط الخاص، حالات نشاطٍ ما. تنطبق الحالات الأساسية نفسها على دورة حياة الشظية.

عادة تقوم بطلب ViewModel ، في المرة الأولى التي يستدعي فيها النظام دالة ()onCreate لكائن النشاط.

قد يستدعي النظام ()onCreate عدة مرات طوال فترة النشاط، مثل: عندما يتم تدوير شاشة الجهاز.

تتواجد ViewModel منذ طلبك ViewModel لأول مرة، حتى يتم الإنتهاء من النشاط وتدميره.

 

 

 

 

 

 

 

نموذج العرض

 

مشاركة البيانات بين الشظايا


من الشائع جداً، أن تحتاج شظيتان أو أكثر في نشاطٍ ما، إلى التواصل مع بعضها البعض. تخيل حالة شائعة من الشظايا، ذات التفاصيل الرئيسية..

حيث يكون لديك شظيه، يقوم فيها المستخدم بتحديد عنصر من قائمة، وشظية أخرى تعرض محتويات العنصر المحدد.

هذه الحالة لا تكون بسيطة أبداً، حيث تحتاج كلتا الشظايا، إلى تعريف بعضٍ من وصف الواجهة، ويجب أن يربط النشاط المالك بين الشظيتين.

بالإضافة إلى ذلك، يجب على كلتا الشظايا، التعامل مع السيناريو بينما لم يتم إنشاء الشظية الأخرى أو رؤيتها.

يمكن التصدي لمرحلة العناء الشائعة هذه، بإستخدام كائنات ViewModel. يمكن أن تشترك هذه الشظايا في ViewModel بإستخدام نطاق نشاطها..

للتعامل مع هذا الإتصال، كما هو موضح في الكود التالي:

KOTLIN

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
        model.selected.observe(this, Observer<Item> { item ->
            // Update the UI
        })
    }
}

JAVA

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

 

لاحظ أن كلا الشظيتين تقوم بإسترجاع النشاط الذي يحتوي عليها، بهذه الطريقه، عندما تحصل كلا الشظيتين على ViewModelProvider..

تتلقى كلتا الشظيتين مثيل SharedViewModel نفسه، الذي يتم تحديد نطاقه إلى هذا النشاط.

يقدم هذا النهج الفوائد التالية:

  • لا يحتاج النشاط إلى القيام بأي شيء، أو معرفة أي شيء عن هذا الإتصال.

 

  • لا تحتاج الشظايا إلى المعرفة بشأن بعضها البعض إلى جانب إتفاقية SharedViewModel. إذا اختفت إحدى الشظايا، فستظل الأخرى تعمل كالمعتاد.

 

  • كل شظيه لها دورة حياة خاصه بها، ولا تتأثر بدورة حياة الشظيه الأخرى. إذا إستبدلت شظيه شظية أخرى، تستمر واجهة المستخدم في العمل دون أي مشاكل.

 

 

 

 

 

 

إستبدال المحملات بإستخدام ViewModel


يتم إستخدام فئات المحمل مثل CursorLoader بشكلٍ متكرر للحفاظ على البيانات في واجهة مستخدم التطبيق متزامنة مع قاعدة البيانات.

يمكنك إستخدام ViewModel، مع بعض الفئات الأخرى، لإستبدال المحمل. يؤدي إستخدام ViewModel إلى..

فصل وحدة تحكم واجهة المستخدم عن عملية تحميل البيانات، مما يعني أن لديك مراجع قوية أقل بين الفئات.

في إحدى الأساليب الشائعة، لإستخدام المحملات، قد يستخدم أحد التطبيقات CursorLoader لمراقبة محتويات قاعدة البيانات.

عندما تتغير قيمة ما، في قاعدة البيانات، يقوم المحمل تلقائياً بإعادة تحميل البيانات وتحديث واجهة المستخدم:

الشكل 2. تحميل البيانات بإستخدام المحملات

 

يعمل ViewModel مع الروم و LiveData لإستبدال المحمل. يضمن ViewModel أن البيانات تنجو من التغييرات التي تحدث في تكوين الجهاز.

يقوم الروم بإبلاغ LiveData الخاصة بك، عندما تتغير قاعدة البيانات، ويقوم LiveData بدوره بتحديث واجهة المستخدم الخاصة بك، مع البيانات المنقحة.

 

الشكل 3. تحميل البيانات بإستخدام ViewModel

 

 

 

 

مصادر إضافية


توضح مقالة المدونة هذه كيفية إستخدام ViewModel مع LiveData لإستبدال AsyncTaskLoader.

عندما تزداد بياناتك تعقيداً، قد تختار أن يكون لديك فئه منفصله لتحميل البيانات فقط.

الغرض من ViewModel هو حصر بيانات وحدة تحكم واجهة المستخدم، للسماح للبيانات بالبقاء على قيد الحياة بعد تغييرات التكوين.

للحصول على معلومات حول كيفية تحميل البيانات وإستمرارها وإدارتها أثناء تغييرات التكوين، راجع حفظ حالات واجهة المستخدم.

يقترح دليل بنية تطبيقات الأندرويد إنشاء فئة مستودع للتعامل مع هذه الوظائف.

يستخدم ViewModel في كود إختبار دورة حياة الأندرويد وتطبيق Sunflower التجريبي. راجع أيضاً النموذج الأساسي لمحتويات البنيه.

 


للإطلاع على المقال باللغة الإنجليزية أضغط هنا.

الإعلانات