توصيات لبنية Android

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

في ما يلي أفضل الممارسات في مجموعات حسب الموضوع. لكل منها أولوية تعكس مدى قوة الفريق الذي يوصي به. في ما يلي قائمة بالأولويات:

  • يُنصح بشدة بتنفيذ هذا الإجراء: يجب تطبيق هذه الممارسة إلا إذا كانت تتعارض بشكل أساسي مع أسلوبك.
  • إجراء مقترَح: من المحتمل أن تحسِّن هذه الممارسة تطبيقك.
  • اختياري: يمكن أن يؤدي هذا الإجراء إلى تحسين تطبيقك في ظروف معيّنة.

بنية متعددة الطبقات

تُفضِّل البنية الأساسية المتعدّدة الطبقات التي ننصح بها ميزة فصل الاستفسارات. وهو يوجّه واجهة المستخدم من نماذج البيانات ويتوافق مع مبدأ المصدر الوحيد للحقيقة، ويتّبع مبادئ تدفق البيانات أحادي الاتجاه. فيما يلي بعض أفضل الممارسات للبنية متعددة الطبقات:

مقترَح الوصف
استخدام طبقة بيانات محدّدة بوضوح.
موصى به بشدة
تعرض طبقة البيانات بيانات التطبيق لبقية التطبيق وتحتوي على الغالبية العظمى من منطق النشاط التجاري لتطبيقك.
  • يجب إنشاء مستودعات حتى إذا كانت تحتوي فقط على مصدر بيانات واحد.
  • في التطبيقات الصغيرة، يمكنك اختيار وضع أنواع طبقات البيانات في حزمة أو وحدة data.
استخدام طبقة واجهة مستخدم محدّدة بوضوح.
موصى به بشدة
تعرض طبقة واجهة المستخدم بيانات التطبيق على الشاشة، وهي بمثابة النقطة الأساسية لتفاعل المستخدم.
  • في التطبيقات الصغيرة، يمكنك اختيار وضع أنواع طبقات البيانات في حزمة أو وحدة ui.
مزيد من أفضل الممارسات حول طبقة واجهة المستخدم
من المفترض أن تعرض طبقة البيانات بيانات التطبيق باستخدام مستودع.
موصى به بشدة

يجب ألا تتفاعل المكونات في طبقة واجهة المستخدم، مثل العناصر أو الأنشطة أو ViewModels، مباشرةً مع مصدر البيانات. أمثلة على مصادر البيانات:

  • قواعد البيانات، وDataStore، وSharedPreferences، وواجهات برمجة تطبيقات Firebase.
  • مقدمو خدمات الموقع الجغرافي بنظام تحديد المواقع العالمي (GPS).
  • مزوِّدو بيانات البلوتوث.
  • موفِّر حالة اتصال الشبكة.
استخدِم الكوروتينات والتدفقات.
موصى به بشدة
استخدام الكوروتينات والتدفقات للتواصل بين الطبقات.

يمكنك الاطّلاع على المزيد من أفضل الممارسات حول الكوروتين هنا.

استخدام طبقة نطاق
يُنصح به في التطبيقات الكبيرة
استخدِم طبقة النطاق وحالات الاستخدام إذا كنت بحاجة إلى إعادة استخدام منطق العمل الذي يتفاعل مع طبقة البيانات على مستوى نماذج ViewModels متعددة، أو كنت تريد تبسيط تعقيد منطق العمل لنموذج معيّن.

طبقة واجهة المستخدم

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

مقترَح الوصف
اتّبِع تدفق البيانات أحادي الاتجاه (UDF).
موصى به بشدة
اتّبِع مبادئ تدفق البيانات غير الاتجاهي (UDF)، حيث تعرض نماذج العرض حالة واجهة المستخدم باستخدام نمط المراقب وتتلقّى الإجراءات من واجهة المستخدم من خلال استدعاءات الطريقة.
استخدِم نماذج AAC ViewModels إذا كانت مزاياها تنطبق على تطبيقك.
موصى به بشدة
يمكنك استخدام AAC ViewModels للتعامل مع منطق العمل، واسترجاع بيانات التطبيق لعرض حالة واجهة المستخدم لواجهة المستخدم (Compose أو طرق عرض Android).

يمكنك الاطّلاع على المزيد من المعلومات حول أفضل ممارسات ViewModel هنا.

يمكنك الاطّلاع على مزايا ViewModels هنا.

استخدام جمع حالة واجهة المستخدم الواعية بدورة الحياة.
موصى به بشدة
يمكنك جمع حالة واجهة المستخدم من واجهة المستخدم باستخدام أداة إنشاء الكوروتين المناسبة لمراحل النشاط: repeatOnLifecycle في نظام العرض وcollectAsStateWithLifecycle في Jetpack Compose.

يمكنك الاطّلاع على مزيد من المعلومات عن repeatOnLifecycle.

يمكنك الاطّلاع على مزيد من المعلومات عن collectAsStateWithLifecycle.

لا ترسل الأحداث من ViewModel إلى واجهة المستخدم.
موصى به بشدة
معالجة الحدث على الفور في نموذج العرض وإجراء تحديث للحالة نتيجة معالجة الحدث. تعرَّف على مزيد من المعلومات عن أحداث واجهة المستخدم هنا.
استخدام تطبيق أحادي النشاط
مقترَح
استخدم أجزاء التنقل أو إنشاء التنقل للتنقل بين الشاشات ورابط لموضع معيّن في تطبيقك إذا كان تطبيقك يحتوي على أكثر من شاشة واحدة.
استخدِم Jetpack Compose.
مقترَح
يمكنك استخدام Jetpack Compose لإنشاء تطبيقات جديدة للهواتف والأجهزة اللوحية والأجهزة القابلة للطي وWear OS.

يوضِّح المقتطف التالي كيفية جمع حالة واجهة المستخدم وفقًا لمراحل نشاط الحياة:

المشاهدات

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

إنشاء

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

نموذج العرض

تكون ViewModels مسؤولة عن توفير حالة واجهة المستخدم والوصول إلى طبقة البيانات. في ما يلي بعض أفضل الممارسات المتعلّقة بنماذج العرض:

مقترَح الوصف
يجب أن تكون ViewModels محايدة لمراحل نشاط Android.
موصى به بشدة
يجب ألا تحتوي ViewModels على مرجع إلى أي نوع متعلق بدورة الحياة. لا تتجاوز Activity, Fragment, Context أو Resources كتبعية. إذا كان هناك شيء يحتاج إلى Context في ViewModel، فيجب عليك تقييم ما إذا كان ذلك في الطبقة الصحيحة أم لا.
استخدِم الكوروتينات والتدفقات.
موصى به بشدة

يتفاعل ViewModel مع البيانات أو طبقات النطاق باستخدام:

  • تدفقات Kotlin لاستلام بيانات التطبيق،
  • suspend لتنفيذ إجراءات باستخدام viewModelScope.
يمكنك استخدام ViewModels على مستوى الشاشة.
موصى به بشدة

لا تستخدِم ViewModels في أجزاء واجهة مستخدم قابلة لإعادة الاستخدام. يجب استخدام ViewModels في:

  • والمواد المركبة على مستوى الشاشة
  • أنشطة/أجزاء في المشاهدات
  • الوجهات أو الرسوم البيانية عند استخدام ميزة التنقّل باستخدام Jetpack
يمكنك استخدام فئات الاحتفاظ بالحالة العادية في مكوّنات واجهة المستخدم القابلة لإعادة الاستخدام.
موصى به بشدة
استخدِم فئات الاحتفاظ بالبيانات العادية للتعامل مع التعقيد في مكوّنات واجهة المستخدم القابلة لإعادة الاستخدام. من خلال القيام بذلك، يمكن رفع الدولة والتحكم فيها خارجيًا.
لا تستخدِم AndroidViewModel.
مقترَح
يجب استخدام الصف ViewModel، وليس AndroidViewModel. لا يجب استخدام الفئة Application في ViewModel. بدلاً من ذلك، انقل التبعية إلى واجهة المستخدم أو طبقة البيانات.
تعرض حالة واجهة المستخدم.
مقترَح
من المفترض أن تعرض ViewModels البيانات لواجهة المستخدم من خلال موقع واحد يُعرف باسم uiState. إذا كانت واجهة المستخدم تعرض بيانات متعدّدة غير مرتبطة ببعضها، يمكن للجهاز الافتراضي أن يعرض خصائص متعدّدة لحالة واجهة المستخدِم.
  • يجب أن تجعل uiState StateFlow.
  • عليك إنشاء uiState باستخدام عامل التشغيل stateIn مع سياسة WhileSubscribed(5000) (مثال) إذا كانت البيانات واردة على شكل مصدر بيانات من طبقات أخرى من التسلسل الهرمي.
  • بالنسبة إلى الحالات الأبسط التي لا تتضمّن أي مصادر بيانات من طبقة البيانات، من المقبول استخدام MutableStateFlow ظاهرة على أنها StateFlow غير قابلة للتغيير (مثال).
  • يمكنك اختيار استخدام ${Screen}UiState كفئة بيانات يمكن أن تحتوي على بيانات وأخطاء وإشارات تحميل. ويمكن أن تكون هذه الفئة أيضًا فئة مغلقة إذا كانت الولايات المختلفة حصرية.

يوضّح المقتطف التالي كيفية إظهار حالة واجهة المستخدم من ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

مراحل النشاط

في ما يلي بعض أفضل الممارسات للتعامل مع مراحل نشاط Android:

مقترَح الوصف
يجب عدم تجاوز طرق مراحل النشاط في الأنشطة أو الأجزاء.
موصى به بشدة
يجب عدم تجاوز طُرق مراحل النشاط، مثل onResume في الأنشطة أو الأجزاء. يمكنك استخدام LifecycleObserver بدلاً من ذلك. إذا كان التطبيق يحتاج إلى تنفيذ عمل عند بلوغ مراحل نشاط Lifecycle.State، استخدِم repeatOnLifecycle API.

يوضح المقتطف التالي كيفية إجراء العمليات في ظل دورة حياة معينة:

المشاهدات

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

إنشاء

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

التعامل مع التبعيات

هناك العديد من أفضل الممارسات التي يجب ملاحظتها عند إدارة التبعيات بين المكونات:

مقترَح الوصف
استخدِم إدراج التبعية.
موصى به بشدة
استخدِم أفضل ممارسات حقن الاعتماد، وخاصةً حقن الدالة الإنشائية عند الإمكان.
بدِّل إلى مكوّن عند الضرورة.
موصى به بشدة
النطاق على حاوية التبعية عندما يحتوي النوع على بيانات قابلة للتغيير يجب مشاركتها أو يكون النوع مكلفًا للتهيئة ويتم استخدامه على نطاق واسع في التطبيق.
استخدِم Hilt.
مقترَح
استخدِم النقر أو إدخال التبعية يدويًا في التطبيقات البسيطة. استخدِم Hilt إذا كان مشروعك معقدًا بدرجة كافية. على سبيل المثال، إذا كان لديك:
  • شاشات متعددة باستخدام ViewModels: الدمج
  • استخدام WorkManager — الدمج
  • الاستخدام المتقدم لميزة التنقّل، مثل ViewModels المحدّدة في الرسم البياني للتنقُّل.

الاختبار

في ما يلي بعض أفضل الممارسات لإجراء الاختبار:

مقترَح الوصف
معرفة ما يجب اختباره.
موصى به بشدة

ما لم يكن المشروع بسيطًا مثل تطبيق أهلاً بالعالم، يجب عليك اختباره، على الأقل باستخدام:

  • نماذج عرض الوحدة لاختبار الوحدة، بما في ذلك المسارات.
  • كيانات طبقة بيانات اختبار الوحدة. أي المستودعات ومصادر البيانات.
  • اختبارات التنقّل في واجهة المستخدم التي تفيد كاختبارات انحدار في CI
أفضّل المنتجات المزيّفة على النماذج التجريبية.
موصى به بشدة
يمكنك الاطّلاع على مزيد من المعلومات في مقالة استخدام مزدوج الاختبار في مستندات Android.
اختبِر StateFlows.
موصى به بشدة
عند اختبار StateFlow:

لمزيد من المعلومات، يمكنك الاطّلاع على دليل الميزات التي يجب اختبارها في وحدة التحكم في الوصول إلى الكمبيوتر (DAC) لنظام التشغيل Android.

عارضات

ينبغي لك مراعاة أفضل الممارسات هذه عند تطوير النماذج في تطبيقاتك:

مقترَح الوصف
إنشاء نموذج لكل طبقة في التطبيقات المُعقدة
مقترَح

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

  • يمكن لمصدر البيانات البعيد ربط النموذج الذي يتلقّاه عبر الشبكة بفئة أبسط بالبيانات التي يحتاجها التطبيق فقط.
  • يمكن للمستودعات تعيين نماذج DAO إلى فئات بيانات أبسط باستخدام المعلومات التي تحتاجها طبقة واجهة المستخدم فقط.
  • يمكن أن يتضمّن ViewModel نماذج طبقة البيانات في UiState فئات.

اصطلاحات التسمية

عند تسمية قاعدة التعليمات البرمجية، يجب أن تكون على دراية بأفضل الممارسات التالية:

مقترَح الوصف
طرق التسمية.
اختيارية
يجب أن تكون الطرق عبارة فعل. مثلاً: makePayment()
خصائص التسمية.
اختيارية
يجب أن تكون الخصائص عبارة اسمية. مثلاً: inProgressTopicSelection
تسمية ساحات البيانات.
اختيارية
عندما تعرض إحدى الصفوف بث تدفق أو بيانات مباشرة أو أي بث آخر، يكون اصطلاح التسمية get{model}Stream(). على سبيل المثال، getAuthorStream(): Flow<Author> إذا كانت الدالة تعرض قائمة من النماذج، يجب أن يكون اسم النموذج بصيغة الجمع: getAuthorsStream(): Flow<List<Author>>
عمليات تنفيذ واجهات التسمية.
اختيارية
يجب أن تكون أسماء عمليات تنفيذ الواجهات ذات مغزى. يجب استخدام Default كبادئة في حال تعذّر العثور على اسم أفضل. على سبيل المثال، بالنسبة إلى واجهة NewsRepository، قد يكون لديك OfflineFirstNewsRepository أو InMemoryNewsRepository. إذا لم تتمكن من العثور على اسم جيد، استخدِم DefaultNewsRepository. يجب أن تبدأ عمليات التنفيذ الزائفة بـ Fake، كما في FakeAuthorsRepository.