وحدات الميزات والوحدات الأساسية
ارسم حدود الوحدات
وحدات الميزات والوحدات الأساسية درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Android Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Android Academy 4 دروس في المجموع.
نوعان من الوحدات
تنظم معظم تطبيقات Android المقسمة إلى وحدات الشفرة في نوعين رئيسيين من الوحدات: وحدات feature ووحدات core، بالإضافة إلى وحدة :app رفيعة في الأعلى.
- تحتوي وحدات Feature على جزء من التطبيق يتعامل معه المستخدم، مثل شاشة أو مسار.
- تحتوي وحدات Core على البنية التحتية المشتركة التي تستخدمها ميزات متعددة.
في هذا الدرس ستتعلمون كيفية رسم هذه الحدود بشكل جيد.
تشريح وحدة Feature
تحتوي وحدة feature مثل :feature:profile على كل ما تحتاج إليه ميزة واحدة: شاشات Compose الخاصة بها، وViewModel الخاص بها، وحالة واجهة المستخدم. وهي رأسية؛ إذ تملك الجزء الكامل الممتد من واجهة المستخدم إلى نموذج العرض الخاص بها.
تعتمد على وحدات core للأجزاء المشتركة، لكن لا ينبغي لها الاعتماد على وحدات feature أخرى.
// feature/profile/ProfileScreen.kt
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (state) {
is ProfileUiState.Loading -> CircularProgressIndicator()
is ProfileUiState.Success -> ProfileContent((state as ProfileUiState.Success).user)
is ProfileUiState.Error -> ErrorMessage()
}
}نموذج العرض الخاص بالميزة
تمتلك كل ميزة ViewModel الخاص بها. وتجلب البيانات من خلال مستودع موجود في وحدة core، وتكشف حالة واجهة المستخدم. ولا تعرف وحدة feature شيئًا عن كيفية جلب البيانات، بل تعرف عقد المستودع فقط.
// feature/profile/ProfileViewModel.kt
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository // from :core:data
) : ViewModel() {
val uiState: StateFlow<ProfileUiState> =
userRepository.observeUser()
.map { ProfileUiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ProfileUiState.Loading)
}تشريح وحدة Core
تكون وحدة core أفقية؛ إذ توفر قدرة واحدة تستخدمها الميزات المختلفة. وتشمل وحدات core الشائعة:
:core:model— فئات بيانات عادية مشتركة في كل مكان:core:network— عملاء Retrofit/Ktor:core:database— إعداد Room:core:data— مستودعات تجمع بين الشبكة وقاعدة البيانات:core:designsystem— السمة والعناصر القابلة لإعادة الاستخدام
// core/model/User.kt
data class User(
val id: String,
val name: String,
val avatarUrl: String
)وحدة نظام التصميم
تُعد :core:designsystem إحدى أكثر الوحدات إعادةً للاستخدام. فهي تحتوي على MaterialTheme ومخططات الألوان والطباعة والعناصر القابلة لإعادة الاستخدام مثل الأزرار والبطاقات. وتطبق كل ميزة المظهر نفسه دون نسخ الشفرة.
// core/designsystem/AppTheme.kt
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}وحدة البيانات تملك المستودعات
تكشف وحدة :core:data واجهات المستودعات التي تعتمد عليها الميزات، مع إخفاء التنفيذ. وعادةً ما تعتمد على :core:network و:core:database، وتجمعهما في مصدر واحد للحقيقة.
// core/data/UserRepository.kt
interface UserRepository {
fun observeUser(): Flow<User>
suspend fun refresh()
}
// core/data/OfflineFirstUserRepository.kt
internal class OfflineFirstUserRepository @Inject constructor(
private val api: UserApi, // :core:network
private val dao: UserDao // :core:database
) : UserRepository {
override fun observeUser(): Flow<User> = dao.observe().map { it.toUser() }
override suspend fun refresh() { dao.upsert(api.fetch().toEntity()) }
}الحفاظ على خفة model وdesignsystem
ينبغي أن تعتمد الوحدات الأساسية ذات المستوى الأدنى على أقل قدر ممكن من الأشياء. ومن الأفضل أن لا يحتوي :core:model على أي تبعيات Android على الإطلاق — بل على فئات بيانات Kotlin عادية فقط. وهذا يجعله قابلًا للاستخدام في كل مكان وسريع البناء.
إذا بدأ :core:model يعتمد على Retrofit أو Room، فستجرّ كل وحدة تستخدم فئات البيانات الخاصة بك تلك المكتبات الثقيلة.
// core/model/build.gradle.kts
plugins {
id("myapp.jvm.library") // pure Kotlin, no Android
}
// No Retrofit, no Room, no Compose here — just data classesوحدة :app النحيفة
تُعد وحدة :app المجمِّع. وينبغي أن تحتوي على قدر ضئيل جدًا من المنطق: فئة Application، وMainActivity الوحيدة، وNavHost ذي المستوى الأعلى، وربط حقن التبعيات. أما جميع الشاشات الفعلية فتوجد في وحدات الميزات.
تعني وحدة التطبيق النحيفة أن معظم التغييرات تحدث في الميزات، ولذلك نادرًا ما يُعاد بناء وحدة التطبيق.
// app/MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme { // from :core:designsystem
AppNavHost() // routes into :feature:* screens
}
}
}
}رسم حدود جيدة
كيف تقرر ما الذي سيصبح وحدة؟ إليك بعض القواعد العملية:
- الميزة = شاشة أو تدفق يمكن للمستخدم تسميته (Home أو Profile أو Checkout).
- وحدة core = قدرة يُعاد استخدامها في ميزتين أو أكثر.
- إذا احتاجت ميزتان إلى الشيفرة نفسها، فانقلها إلى الأسفل داخل وحدة core.
- إذا كانت الوحدة تنفذ مهام كثيرة غير مترابطة، فقسّمها.
اختياري: تقسيم api وimpl
تقسّم التطبيقات الكبيرة أحيانًا الميزة إلى :feature:profile:api عامة (للواجهات ومسارات التنقل) و:feature:profile:impl خاصة (للشاشات وview-models). وتعتمد الميزات الأخرى على api الصغيرة فقط، ولا تعتمد مطلقًا على التنفيذ.
هذا أسلوب متقدم؛ وبالنسبة إلى معظم التطبيقات، تكفي وحدة واحدة لكل ميزة. يكفي أن تعرف أن هذا النمط موجود لقواعد الشيفرة الكبيرة جدًا.
// Other features see only the contract, not the screens
// feature/home depends on :feature:profile:api
interface ProfileEntry {
val route: String
fun NavGraphBuilder.register(navController: NavController)
}تجميع الرسم البياني
إليك كيفية ربط الطبقات في مثالنا. لاحظ أن التبعيات تشير دائمًا إلى الأسفل فقط: app -> feature -> data -> network/database -> model.
// :app -> :feature:home, :feature:profile
// :feature:home -> :core:data, :core:designsystem
// :feature:profile -> :core:data, :core:designsystem
// :core:data -> :core:network, :core:database, :core:model
// :core:network -> :core:model
// :core:database -> :core:model
// :core:model -> (nothing)تحقق سريع
تحتاج كل من وحدتي :feature:home و:feature:profile إلى جلب بيانات المستخدم وتخزينها مؤقتًا. أين ينبغي أن توجد شيفرة المستودع هذه؟
مراجعة: وحدات الميزات وCore
لقد تعلمت تقسيم الشيفرة إلى طبقتين:
- وحدات الميزات هي شرائح عمودية (الشاشة + ViewModel + حالة واجهة المستخدم).
- وحدات Core هي قدرات أفقية (model وnetwork وdatabase وdata وdesignsystem).
- تبقى وحدة
:appنحيفة وتقتصر على تجميع الميزات. - تُنقل الشيفرة المشتركة إلى الأسفل داخل core، وتبقى
:core:modelخفيفة وخالية من Android.
بعد ذلك، ستدير التبعيات بين هذه الوحدات وتحافظ على نظافة الرسم البياني.
الأسئلة الشائعة
هل درس «وحدات الميزات والوحدات الأساسية» مجاني؟
نعم — نص درس «وحدات الميزات والوحدات الأساسية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.
ماذا ستتعلم في «وحدات الميزات والوحدات الأساسية»؟
ارسم حدود الوحدات تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟
لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «وحدات الميزات والوحدات الأساسية»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟
نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا نُجزّئ التطبيق إلى وحدات
- وحدات الميزات والوحدات الأساسية
- إدارة تبعيات الوحدات
- التنقّل بين الوحدات