تخطَّ إلى المحتوى
ARAB CODEمجتمع المطورين العرب

معمارية تطبيقات Android الحديثة: الطبقات وViewModel وتدفق البيانات في اتجاه واحد

درسنُشر ٢٤ أكتوبر ٢٠٢٦ · 0 رد · 1 مشاهدة#android#kotlin#معمارية#viewmodel
قبل ساعتين

مقدمة

تطبيقات Android تواجه تحدياً خاصاً: الشاشة قد تُعاد إنشاؤها عند تدوير الجهاز أو تغيير اللغة، والنظام قد يوقف التطبيق في أي لحظة. إن وضعت المنطق وطلبات الشبكة داخل Activity مباشرة ستفقد البيانات وتكرر الطلبات وتصعب الاختبارات. المعمارية الموصى بها من Google تحل ذلك بفصل المسؤوليات.

أهداف التعلم

  • فهم الطبقات: الواجهة (UI)، والنطاق (اختيارية)، والبيانات (Data).
  • استخدام ViewModel لحفظ حالة الشاشة.
  • تمثيل الحالة بـ StateFlow وتدفق البيانات في اتجاه واحد (UDF).
  • معرفة دور المستودع (Repository).

1. الطبقات

UI (Compose / Views)  ←  حالة الشاشة  ←  ViewModel
                                         ↓ يستدعي
                                   Repository (مصدر الحقيقة)
                                   ↙                ↘
                         مصدر شبكة (Retrofit)   مصدر محلي (Room)
  • الواجهة تعرض الحالة وترسل أحداث المستخدم فقط.
  • ViewModel يحتفظ بحالة الشاشة وينجو من إعادة إنشائها.
  • المستودع يقرر من أين تأتي البيانات (شبكة أم تخزين محلي) ويخفي ذلك عن الأعلى.

2. حالة الشاشة ككائن واحد

data class TopicsUiState(
    val isLoading: Boolean = false,
    val topics: List<Topic> = emptyList(),
    val errorMessage: String? = null,
)

كائن واحد يصف الشاشة بالكامل يمنع الحالات المتناقضة (مثلاً: تحميل وخطأ في الوقت نفسه دون قصد).

3. ViewModel مع StateFlow

class TopicsViewModel(
    private val repository: TopicsRepository,
) : ViewModel() {

    private val _uiState = MutableStateFlow(TopicsUiState(isLoading = true))
    val uiState: StateFlow<TopicsUiState> = _uiState.asStateFlow()

    init { refresh() }

    fun refresh() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, errorMessage = null) }
            try {
                val topics = repository.latestTopics()
                _uiState.update { it.copy(isLoading = false, topics = topics) }
            } catch (e: IOException) {
                _uiState.update { it.copy(isLoading = false, errorMessage = "تعذر الاتصال. تحقق من الشبكة.") }
            }
        }
    }
}
  • viewModelScope يلغي العمليات تلقائياً عند انتهاء الـ ViewModel، فلا تسريب.
  • الواجهة تقرأ uiState فقط ولا تستطيع تعديله (الاتجاه الواحد).

4. الواجهة بـ Jetpack Compose

@Composable
fun TopicsScreen(viewModel: TopicsViewModel = viewModel()) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    when {
        state.isLoading -> CircularProgressIndicator()
        state.errorMessage != null -> ErrorView(state.errorMessage!!, onRetry = viewModel::refresh)
        else -> LazyColumn {
            items(state.topics, key = { it.id }) { TopicRow(it) }
        }
    }
}

collectAsStateWithLifecycle يوقف الجمع عندما يكون التطبيق في الخلفية فيوفر البطارية.

5. المستودع ومصدر الحقيقة

class TopicsRepository(
    private val api: ArabCodeApi,
    private val dao: TopicDao,
) {
    suspend fun latestTopics(): List<Topic> {
        val remote = api.latestTopics()
        dao.upsertAll(remote)          // قاعدة البيانات المحلية مصدر الحقيقة
        return dao.latest(limit = 50)
    }
}

جعل التخزين المحلي مصدر الحقيقة يسمح بالعرض دون اتصال وتحديث الشاشة عند وصول بيانات جديدة.

تمرين عملي

  1. أنشئ مشروع Compose وأضف TopicsViewModel مع مستودع وهمي يعيد قائمة ثابتة بعد تأخير delay(1000).
  2. دوّر الجهاز أثناء التحميل وتأكد أن الطلب لا يتكرر.
  3. اكتب اختباراً للـ ViewModel باستخدام مستودع مزيف يرمي IOException وتأكد من ظهور رسالة الخطأ.

أخطاء شائعة

  • تمرير Context أو Activity إلى الـ ViewModel (تسريب ذاكرة).
  • كشف MutableStateFlow للواجهة بدل StateFlow للقراءة فقط.
  • إطلاق طلبات الشبكة من الواجهة مباشرة عند كل إعادة رسم.
  • التقاط كل الاستثناءات (Exception) فتخفي أخطاء برمجية حقيقية.

الخلاصة

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

مصادر مقترحة

  • Android Developers: «Guide to app architecture».
  • دورة «Android Basics with Compose» من Google.
  • توثيق Kotlin Coroutines وFlow.

الرد على الموضوع

سجّل الدخول للمشاركة في النقاش.

نقاشات مشابهة