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

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

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

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

التصميم المتعدّد الطبقات

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

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

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

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

مزيد من أفضل الممارسات المتعلّقة بوظائف التشغيل المتعدّد

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

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

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

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

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

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

استخدِم مجموعة حالات واجهة المستخدم المراعية لرحلة المستخدِم. جمع حالة واجهة المستخدم من واجهة المستخدم باستخدام أداة إنشاء وظائف التشغيل المتعدّد المتوافقة مع دورة الحياة: repeatOnLifecycle في نظام View وcollectAsStateWithLifecycle في Jetpack Compose

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

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

لا تُرسِل الأحداث من ViewModel إلى واجهة المستخدِم. معالجة الحدث فورًا في 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()
}

ViewModel

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

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

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

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

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

  • العناصر القابلة للتجميع على مستوى الشاشة
  • الأنشطة/المقاطع في "المشاهدات"،
  • الوجهات أو الرسوم البيانية عند استخدام Jetpack Navigation
استخدِم فئات حاملي الحالة العادية في مكونات واجهة المستخدم القابلة لإعادة الاستخدام. استخدِم فئات حاملي الحالة البسيطة للتعامل مع التعقيد في مكونات واجهة المستخدم القابلة لإعادة الاستخدام. من خلال إجراء ذلك، يمكن رفع الحالة والتحكّم فيها خارجيًا.
لا تستخدِم AndroidViewModel. استخدِم فئة ViewModel، وليس AndroidViewModel. يجب عدم استخدام فئة Application في ViewModel. بدلاً من ذلك، انقل التبعية إلى واجهة المستخدم أو طبقة البيانات.
اعرض حالة واجهة المستخدم. يجب أن تعرض نماذج العرض البيانات لواجهة المستخدم من خلال سمة واحدة تُسمى 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.

يوضّح المقتطف التالي كيفية تنفيذ العمليات استنادًا إلى حالة Lifecycle معيّنة:

المشاهدات

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 أو الحقن اليدوي للتبعيات في التطبيقات البسيطة. استخدِم دالة Hilt إذا كان مشروعك معقّدًا بدرجة كافية. على سبيل المثال، إذا كان لديك:
  • شاشات متعددة تحتوي على ViewModels — الدمج
  • استخدام WorkManager: الدمج
  • الاستخدام المتقدّم لميزة التنقّل، مثل ViewModels التي تستهدف الرسم البياني للتنقّل

الاختبار

في ما يلي بعض أفضل الممارسات المتعلّقة بعمليات الاختبار:

مقترَح الوصف
تحديد ما يجب اختباره

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

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

لمزيد من المعلومات، يمكنك الاطّلاع على الدليل حول ما يجب اختباره في أداة Android DAC.

نماذج

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

مقترَح الوصف
أنشئ نموذجًا لكل طبقة في التطبيقات المعقدة.

في التطبيقات المعقدة، أنشئ نماذج جديدة في طبقات أو مكوّنات مختلفة عندما يكون ذلك منطقيًا. راجِع الأمثلة التالية:

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

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

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

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