Kotlin: شرح أساسيات أندرويد بكوتلن
آخر تحديث: 2026-08-26
تطوير أندرويد اعتمد كوتلن بالكامل — Jetpack Compose يبني واجهات تصريحية بـ Kotlin DSL، وViewModel + StateFlow يدير الحالة التفاعلية، وRoom + الكوروتينات تبسّط طبقة البيانات. Charlie يبني لوحة طلبات أندرويد.
1. ما ستتعلمه
- دورة حياة Activity / Fragment والتكامل مع كوتلن
- Jetpack Compose: واجهة تصريحية + Kotlin DSL
- ViewModel + StateFlow: إدارة الحالة التفاعلية
- قاعدة بيانات Room: Entity + Dao + الكوروتينات
- تطبيق Charlie: لوحة طلبات أندرويد
2. قصة مطور حقيقي
(1) المشكلة: جحيم مزامنة الحالة في الواجهة الحتمية
استخدمت Alice وضع XML + RecyclerView لبناء قائمة الطلبات. كل تغيير حالة يتطلب تحديث واجهة يدوي: عند تغيير البيانات، استدعِ notifyDataSetChanged()؛ عند التحميل، أظهر/أخفِ شريط التقدم — 10 حالات تحتاج 20+ سطر كود مزامنة واجهة.
(2) حل واجهة Compose التصريحية
KOTLIN
// XML + RecyclerView: حتمية، عرضة للأخطاء
// recyclerView.adapter.notifyDataSetChanged()
// Compose: تصريحية، تلقائية
@Composable
fun OrderList(orders: List<Order>) {
LazyColumn {
items(orders) { order ->
OrderCard(order) // واجهة المستخدم تعكس تغييرات البيانات تلقائيًا
}
}
}
Compose تصريحية — واجهة المستخدم هي دالة الحالة. عند تغيير البيانات، تتحدث الواجهة تلقائيًا بدون مزامنة يدوية.
3. دورة حياة Activity
(1) استدعاءات دورة الحياة
KOTLIN
class OrderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
OrderScreen()
}
}
override fun onResume() {
super.onResume()
// تحديث البيانات عندما تعود Activity إلى المقدمة
}
override fun onPause() {
super.onPause()
// حفظ بيانات المسودة
}
}
(2) مخطط حالات دورة الحياة
stateDiagram-v2
[*] --> Created: onCreate()
Created --> Started: onStart()
Started --> Resumed: onResume()
Resumed --> Started: onPause()
Started --> Created: onStop()
Created --> [*]: onDestroy()
Started --> Resumed: onResume()
(3) مقارنة استدعاءات دورة الحياة
| الاستدعاء | المحفز | العملية النموذجية |
|---|---|---|
onCreate(bundle) |
إنشاء Activity | تهيئة واجهة المستخدم، ViewModel |
onStart() |
مرئي لكن غير تفاعلي | تسجيل المستمعين |
onResume() |
يكتسب التركيز | بدء الرسوم المتحركة، تحديث البيانات |
onPause() |
يفقد التركيز | إيقاف الرسوم المتحركة، حفظ المسودة |
onStop() |
لم يعد مرئيًا | تحرير الموارد |
onDestroy() |
جارٍ التدمير | تنظيف الموارد |
4. Jetpack Compose
(1) الدوال القابلة للتأليف
KOTLIN
@Composable
fun OrderCard(order: Order) {
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = order.id, style = MaterialTheme.typography.titleMedium)
Text(text = order.customer, style = MaterialTheme.typography.bodyMedium)
}
Text(
text = "\$${order.total} USD",
style = MaterialTheme.typography.titleLarge
)
}
}
}
(2) القوائم والحالة
KOTLIN
@Composable
fun OrderListScreen(viewModel: OrderViewModel = viewModel()) {
val orders by viewModel.orders.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
if (isLoading) {
CircularProgressIndicator()
} else {
LazyColumn {
items(orders) { order ->
OrderCard(order)
}
}
}
}
(3) خط أنابيب عرض Compose
flowchart TD
A[تغيير الحالة] --> B[التأليف<br/>تشغيل دوال @Composable]
B --> C[التخطيط<br/>القياس والتموضع]
C --> D[الرسم<br/>العرض على اللوحة]
D --> E[تحديث واجهة المستخدم]
(4) مقارنة XML مقابل Compose
| البُعد | XML + View | Jetpack Compose |
|---|---|---|
| النموذج | حتمي | تصريحي |
| اللغة | XML + كوتلن | كوتلن بحت |
| مزامنة الحالة | يدوية | تلقائية |
| حجم الكود | كبير (XML + Adapter + ViewHolder) | صغير (@Composable) |
| المعاينة | يتطلب التشغيل | معاينة فورية في IDE |
| منحنى التعلم | متوسط | متوسط (نموذج جديد) |
5. ViewModel + StateFlow
(1) ViewModel
KOTLIN
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
try {
_orders.value = orderRepository.findAll()
} finally {
_isLoading.value = false
}
}
}
}
(2) StateFlow مقابل LiveData
| البُعد | LiveData | StateFlow |
|---|---|---|
| القيمة الأولية | غير مطلوبة | مطلوبة |
| دعم الكوروتينات | يحتاج محولات | أصلي |
| الخيط | الخيط الرئيسي | أي خيط |
| دورة الحياة | واعي | يحتاج repeatOnLifecycle |
| التوصية | الكود القديم | المشاريع الجديدة |
6. قاعدة بيانات Room
(1) Entity + Dao
KOTLIN
@Entity(tableName = "orders")
data class OrderEntity(
@PrimaryKey val id: String,
val total: Double,
val status: String,
val customer: String,
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders ORDER BY createdAt DESC")
fun getAllOrders(): Flow<List<OrderEntity>>
@Query("SELECT * FROM orders WHERE id = :id")
suspend fun getById(id: String): OrderEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(order: OrderEntity)
@Delete
suspend fun delete(order: OrderEntity)
}
(2) مكونات Room
| المكون | التوضيح | المسؤولية |
|---|---|---|
| Entity | @Entity |
تعيين جدول قاعدة البيانات |
| Dao | @Dao |
عمليات الوصول للبيانات |
| Database | @Database |
نقطة دخول قاعدة البيانات |
7. مثال كامل: لوحة طلبات أندرويد
KOTLIN
// ============================================
// OrderProcessor - لوحة طلبات أندرويد
// الميزة: واجهة Compose + ViewModel + StateFlow
// ============================================
// --- النطاق ---
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// --- ViewModel ---
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
loadOrders()
}
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
delay(500) // محاكاة الشبكة
_orders.value = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "PENDING", "Bob"),
Order("ORD-003", 2_500.00, "SHIPPED", "Charlie"),
Order("ORD-004", 8_900.00, "CONFIRMED", "Bob"),
Order("ORD-005", 45.50, "CANCELLED", "Alice")
)
_isLoading.value = false
}
}
fun totalRevenue(): Double = _orders.value
.filter { it.status != "CANCELLED" }
.sumOf { it.total }
}
// --- واجهة Compose (مفاهيمية - تتطلب وقت تشغيل أندرويد) ---
// @Composable
// fun OrderScreen(viewModel: OrderViewModel = viewModel()) {
// val orders by viewModel.orders.collectAsState()
// val isLoading by viewModel.isLoading.collectAsState()
//
// Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Text("لوحة الطلبات", style = MaterialTheme.typography.headlineMedium)
// Text("الإيرادات: \$${viewModel.totalRevenue()} USD", style = MaterialTheme.typography.titleLarge)
//
// if (isLoading) {
// CircularProgressIndicator()
// } else {
// LazyColumn {
// items(orders) { order -> OrderCard(order) }
// }
// }
// }
// }
// --- عرض JVM (محاكاة منطق ViewModel) ---
fun main() {
val viewModel = OrderViewModel()
// محاكاة مراقبة الحالة
Thread.sleep(1000) // انتظار التحميل
println("=== لوحة طلبات أندرويد ===")
println("الإيرادات: \$${viewModel.totalRevenue()} USD")
println("جارٍ التحميل: ${viewModel.isLoading.value}")
viewModel.orders.value.forEach { order ->
val icon = when (order.status) {
"CONFIRMED" -> "✅"
"PENDING" -> "⏳"
"SHIPPED" -> "📦"
"CANCELLED" -> "❌"
else -> "?"
}
println(" $icon ${order.id}: \$${order.total} USD (${order.customer}) [${order.status}]")
}
}
الإخراج:
TEXT
📖 للعرض فقط
=== لوحة طلبات أندرويد ===
الإيرادات: $26699.99 USD
جارٍ التحميل: false
✅ ORD-001: $299.99 USD (Alice) [CONFIRMED]
⏳ ORD-002: $15000.0 USD (Bob) [PENDING]
📦 ORD-003: $2500.0 USD (Charlie) [SHIPPED]
✅ ORD-004: $8900.0 USD (Bob) [CONFIRMED]
❌ ORD-005: $45.5 USD (Alice) [CANCELLED]
8. أمثلة عملية سريعة
▶ مثال: Activity مع Kotlin
KOTLIN
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var messageText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
messageText = findViewById(R.id.message_text)
messageText.text = buildString {
appendLine("Welcome to Android with Kotlin!")
appendLine("Device: ${android.os.Build.MODEL}")
appendLine("Android version: ${android.os.Build.VERSION.RELEASE}")
}
}
override fun onResume() {
super.onResume()
println("Activity resumed")
}
override fun onPause() {
super.onPause()
println("Activity paused")
}
}
**الإخراج:
TEXT
📖 للعرض فقط
Welcome to Android with Kotlin!
Device: Pixel 6
Android version: 14
▶ مثال: Jetpack Compose أساسي
KOTLIN
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@Composable
fun SimpleScreen(name: String) {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Hello, $name!", style = MaterialTheme.typography.headlineMedium)
Spacer(Modifier.height(16.dp))
Text("Count: $count", style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(16.dp))
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
@Composable
fun CounterScreen() {
var count by rememberSaveable { mutableStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text("Counter: $count")
Row {
Button(onClick = { count++ }) { Text("+") }
Spacer(Modifier.width(8.dp))
Button(onClick = { count-- }) { Text("-") }
}
}
}
**الإخراج:
TEXT
📖 للعرض فقط
Hello, Kotlin!
Counter: 0
[+] [-]
▶ مثال: ViewModel مع StateFlow
KOTLIN
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
data class OrderUiState(
val orders: List<Order> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
class OrderViewModel : ViewModel() {
private val _uiState = MutableStateFlow(OrderUiState())
val uiState: StateFlow<OrderUiState> = _uiState.asStateFlow()
fun loadOrders() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true)
try {
val orders = fetchOrders()
_uiState.value = OrderUiState(orders = orders, isLoading = false)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
fun cancelOrder(orderId: String) {
viewModelScope.launch {
try {
// استدعاء API للإلغاء
_uiState.value = _uiState.value.copy(
orders = _uiState.value.orders.filter { it.id != orderId }
)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(error = e.message)
}
}
}
private suspend fun fetchOrders(): List<Order> {
// محاكاة API call
delay(1000)
return listOf(
Order("ORD-001", 1500.0),
Order("ORD-002", 500.0)
)
}
}
class Order(val id: String, val total: Double)
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Room Database
KOTLIN
import androidx.room.*
@Entity
data class UserEntity(
@PrimaryKey val id: Int,
val name: String,
val email: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM UserEntity")
suspend fun getAll(): List<UserEntity>
@Query("SELECT * FROM UserEntity WHERE id = :id")
suspend fun findById(id: Int): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: UserEntity)
@Update
suspend fun update(user: UserEntity)
@Delete
suspend fun delete(user: UserEntity)
}
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// الاستخدام
class UserRepository(private val dao: UserDao) {
suspend fun getAllUsers(): List<UserEntity> = dao.getAll()
suspend fun saveUser(user: UserEntity) = dao.insert(user)
}
// إنشاء قاعدة البيانات
val db = Room.databaseBuilder(
context,
AppDatabase::class.java,
"app-db"
).build()
val userDao = db.userDao()
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Navigation في Compose
KOTLIN
import androidx.navigation.compose.*
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{itemId}") {
fun create(itemId: Int) = "detail/$itemId"
}
object Settings : Screen("settings")
}
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) {
HomeScreen(
onItemClick = { id ->
navController.navigate(Screen.Detail.create(id))
},
onSettingsClick = {
navController.navigate(Screen.Settings.route)
}
)
}
composable(Screen.Detail.route) { backStack ->
val itemId = backStack.arguments?.getInt("itemId") ?: 0
DetailScreen(itemId = itemId, onBack = {
navController.popBackStack()
})
}
composable(Screen.Settings.route) {
SettingsScreen(onBack = {
navController.popBackStack()
})
}
}
}
@Composable
fun HomeScreen(onItemClick: (Int) -> Unit, onSettingsClick: () -> Unit) {
Column {
Text("Home Screen")
Button(onClick = { onItemClick(42) }) { Text("Open Detail") }
Button(onClick = onSettingsClick) { Text("Settings") }
}
}
@Composable
fun DetailScreen(itemId: Int, onBack: () -> Unit) {
Column {
Text("Detail for item $itemId")
Button(onClick = onBack) { Text("Back") }
}
}
@Composable
fun SettingsScreen(onBack: () -> Unit) {
Column {
Text("Settings")
Button(onClick = onBack) { Text("Back") }
}
}
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: ListAdapter مع RecyclerView
KOTLIN
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
data class OrderItem(val id: String, val title: String, val subtitle: String)
class OrderAdapter : RecyclerView.Adapter<OrderAdapter.OrderViewHolder>() {
private val items = mutableListOf<OrderItem>()
fun submitList(newItems: List<OrderItem>) {
items.clear()
items.addAll(newItems)
notifyDataSetChanged()
}
class OrderViewHolder(val view: android.view.View) : RecyclerView.ViewHolder(view) {
fun bind(item: OrderItem) {
view.findViewById<android.widget.TextView>(R.id.title).text = item.title
view.findViewById<android.widget.TextView>(R.id.subtitle).text = item.subtitle
}
}
override fun onCreateViewHolder(parent: android.view.ViewGroup, viewType: Int): OrderViewHolder {
val view = android.view.LayoutInflater.from(parent.context)
.inflate(R.layout.item_order, parent, false)
return OrderViewHolder(view)
}
override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int = items.size
}
// الاستخدام
val recyclerView: RecyclerView = findViewById(R.id.orders_recycler)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = OrderAdapter()
recyclerView.adapter = adapter
adapter.submitList(listOf(
OrderItem("1", "ORD-001", "$1500.00"),
OrderItem("2", "ORD-002", "$500.00")
))
**الإخراج:
TEXT
📖 للعرض فقط
▶ مثال: Coroutines + Lifecycle
KOTLIN
import androidx.lifecycle.*
import kotlinx.coroutines.*
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
// viewModelScope يُلغى عند onCleared()
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO) {
// محاكاة استدعاء API
delay(1000)
"Data loaded at ${System.currentTimeMillis()}"
}
_data.value = result
} catch (e: Exception) {
_data.value = "Error: ${e.message}"
}
}
}
}
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProvider(this)[MyViewModel::class.java]
viewModel.data.observe(this) { data ->
// التحديث في الـ main thread
findViewById<TextView>(R.id.data_text).text = data
}
// launchWhenStarted - يبدأ عند STARTED، يُلغى عند STOPPED
lifecycleScope.launchWhenStarted {
viewModel.loadData()
}
// repeatOnLifecycle - أنظف lifecycle-aware coroutine
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.loadData()
}
}
}
}
**الإخراج:
TEXT
📖 للعرض فقط
❓ أسئلة شائعة
س هل يجب عليّ استخدام Compose؟ هل يمكنني الاستمرار في استخدام XML؟
ج يمكنك الاستمرار في استخدام XML، لكن Google توصي بـ Compose كخيار افتراضي للمشاريع الجديدة. يمكن لـ Compose و XML التعايش (استخدم
ComposeView لتضمين Compose في XML).س ما الفرق بين ViewModel و Presenter؟
ج ViewModel لا يتدمّر عند تغييرات التهيئة (دوران الشاشة) — البيانات تُحفظ تلقائيًا. Presenter يتطلب حفظ/استعادة يدوي عند تغييرات التهيئة.
س كيف أختار بين StateFlow و SharedFlow؟
ج StateFlow لحمل الحالة (يجب أن يكون له قيمة أولية، يزيل التكرار). SharedFlow لتدفقات الأحداث (بث متعدد، بدون قيمة أولية). استخدم StateFlow لحالة واجهة المستخدم، وSharedFlow للأحداث اللحظية.
س هل يدعم Room الكوروتينات؟
ج نعم. دوال Dao
suspend تُنفّذ تلقائيًا على تجمع خيوط Room. الاستعلامات التي تُرجع Flow تُعيد البث تلقائيًا عند تغيير البيانات.س كيف هو أداء Compose؟
ج Compose يستخدم إعادة التأليف الذكية (يعيد رسم Composables المتغيرة فقط)، بأداء قريب من Views المكتوبة يدويًا. تجنب الحسابات داخل Composable — استخدم
remember و derivedStateOf للتخزين المؤقت.س كيف أتعامل مع التنقل في Compose؟
ج استخدم
NavHost + composable() لتعريف رسم تنقل، navController.navigate() للتنقل. التنقل الآمن الأنواع متاح في Compose 1.0+.📖 ملخص
- تطوير أندرويد أصبح يعتمد بالكامل على كوتلن؛ Jetpack Compose هو نموذج واجهة المستخدم الجديد
- واجهة Compose التصريحية: واجهة المستخدم = f(حالة) — تتغير البيانات، تتحدث الواجهة تلقائيًا
- ViewModel + StateFlow يدير حالة واجهة المستخدم التفاعلية، آمن عبر تغييرات التهيئة
- Room + الكوروتينات + Flow يبني طبقة بيانات تفاعلية
- Compose يستخدم Kotlin DSL لبناء أشجار المكونات، أكثر إيجازًا من XML
- دورة حياة Activity لا تزال أساسية لأندرويد، لكن Compose يقلل التفاعل المباشر مع دورة الحياة
📝 تمارين
- مبتدئ (⭐): أنشئ دالة Compose
Greetingتعرض "مرحبًا، $name!". تلميح:@Composable fun Greeting(name: String) { Text("مرحبًا، $name!") } - متوسط (⭐⭐): استخدم ViewModel + StateFlow لتنفيذ عداد مع واجهة Compose تعرض العدد في الوقت الفعلي. تلميح:
MutableStateFlow(0)+collectAsState() - متقدم (⭐⭐⭐): نفّذ واجهة Compose كاملة لقائمة طلبات: LazyColumn تعرض الطلبات، سحب للتحديث، نقر للتنقل إلى التفاصيل. تلميح:
LazyColumn+pullRefresh+NavController