توسعه اپلیکیشن اندروید با Jetpack Compose

راهنمای کامل برای ایجاد UI های مدرن و تعاملی

مقدمه

Jetpack Compose انقلابی در توسعه UI اندروید است. علی سلمانیان در این مقاله، راهنمای کاملی از Compose ارائه می‌دهد که به شما کمک می‌کند تا اپلیکیشن‌های مدرن و زیبا بسازید. Compose با استفاده از Kotlin، توسعه UI را ساده‌تر و قدرتمندتر کرده است.

Jetpack Compose چیست؟

Jetpack Compose یک toolkit مدرن برای ساخت UI های اندروید است که:

  • از declarative programming استفاده می‌کند
  • کد کمتر و خوانایی بیشتری دارد
  • State management ساده‌تری ارائه می‌دهد
  • با Material Design 3 سازگار است
  • Performance بهتری دارد

🚀 مزایای اصلی Compose:

  • Declarative UI: UI را بر اساس state توصیف می‌کنید
  • Reusable Components: Component های قابل استفاده مجدد
  • Powerful State Management: مدیریت state پیشرفته
  • Material Design 3: پشتیبانی کامل از Material Design
  • Animation Support: انیمیشن‌های قدرتمند

راه‌اندازی پروژه Compose

1. تنظیمات اولیه

برای شروع با Compose، ابتدا باید پروژه خود را پیکربندی کنید:

// build.gradle (Module: app)
android {
    compileSdk 34
    
    defaultConfig {
        applicationId "com.example.mycomposeapp"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"
    }
    
    buildFeatures {
        compose true
    }
    
    composeOptions {
        kotlinCompilerExtensionVersion '1.5.4'
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
    implementation 'androidx.activity:activity-compose:1.8.2'
    
    // Compose BOM
    implementation platform('androidx.compose:compose-bom:2023.10.01')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    
    // Navigation
    implementation 'androidx.navigation:navigation-compose:2.7.5'
    
    // ViewModel
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
    
    // Testing
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'
}

2. MainActivity با Compose

// MainActivity.kt
package com.example.mycomposeapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.mycomposeapp.ui.theme.MyComposeAppTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyApp()
                }
            }
        }
    }
}

@Composable
fun MyApp() {
    // Your app content here
    Text(
        text = "سلام به Compose!",
        style = MaterialTheme.typography.headlineLarge
    )
}

مفاهیم اصلی Compose

1. Composable Functions

Composable functions قلب Compose هستند. در اینجا مثال‌های کاربردی می‌بینید:

// Basic Composable
@Composable
fun Greeting(name: String) {
    Text(
        text = "سلام $name!",
        style = MaterialTheme.typography.headlineMedium,
        color = MaterialTheme.colorScheme.primary
    )
}

// Composable with State
@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }
    
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "شمارش: $count",
            style = MaterialTheme.typography.headlineLarge
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Row {
            Button(
                onClick = { count-- },
                modifier = Modifier.padding(8.dp)
            ) {
                Text("کاهش")
            }
            
            Button(
                onClick = { count++ },
                modifier = Modifier.padding(8.dp)
            ) {
                Text("افزایش")
            }
        }
    }
}

// Complex Composable with Parameters
@Composable
fun UserCard(
    user: User,
    onEditClick: (User) -> Unit,
    onDeleteClick: (User) -> Unit,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = modifier
            .fillMaxWidth()
            .padding(8.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Column {
                    Text(
                        text = user.name,
                        style = MaterialTheme.typography.headlineSmall
                    )
                    Text(
                        text = user.email,
                        style = MaterialTheme.typography.bodyMedium,
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    )
                }
                
                Row {
                    IconButton(onClick = { onEditClick(user) }) {
                        Icon(
                            imageVector = Icons.Default.Edit,
                            contentDescription = "ویرایش"
                        )
                    }
                    IconButton(onClick = { onDeleteClick(user) }) {
                        Icon(
                            imageVector = Icons.Default.Delete,
                            contentDescription = "حذف"
                        )
                    }
                }
            }
        }
    }
}

2. State Management پیشرفته

مدیریت state در Compose نیاز به درک عمیق دارد:

// State Hoisting
@Composable
fun TodoScreen() {
    var todoText by remember { mutableStateOf("") }
    var todos by remember { mutableStateOf(listOf()) }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        // Input field
        OutlinedTextField(
            value = todoText,
            onValueChange = { todoText = it },
            label = { Text("کار جدید") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Add button
        Button(
            onClick = {
                if (todoText.isNotBlank()) {
                    todos = todos + Todo(
                        id = System.currentTimeMillis(),
                        text = todoText,
                        isCompleted = false
                    )
                    todoText = ""
                }
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("اضافه کردن")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Todo list
        LazyColumn {
            items(todos) { todo ->
                TodoItem(
                    todo = todo,
                    onToggleComplete = { updatedTodo ->
                        todos = todos.map { 
                            if (it.id == updatedTodo.id) updatedTodo else it 
                        }
                    },
                    onDelete = { todoToDelete ->
                        todos = todos.filter { it.id != todoToDelete.id }
                    }
                )
            }
        }
    }
}

@Composable
fun TodoItem(
    todo: Todo,
    onToggleComplete: (Todo) -> Unit,
    onDelete: (Todo) -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Checkbox(
                checked = todo.isCompleted,
                onCheckedChange = { 
                    onToggleComplete(todo.copy(isCompleted = it))
                }
            )
            
            Spacer(modifier = Modifier.width(8.dp))
            
            Text(
                text = todo.text,
                style = MaterialTheme.typography.bodyLarge,
                modifier = Modifier.weight(1f),
                textDecoration = if (todo.isCompleted) 
                    TextDecoration.LineThrough else null
            )
            
            IconButton(onClick = { onDelete(todo) }) {
                Icon(
                    imageVector = Icons.Default.Delete,
                    contentDescription = "حذف"
                )
            }
        }
    }
}

3. Navigation در Compose

Navigation Component برای Compose:

// Navigation Setup
@Composable
fun MyApp() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = { 
                    navController.navigate("profile/$it") 
                }
            )
        }
        
        composable(
            "profile/{userId}",
            arguments = listOf(navArgument("userId") { type = NavType.StringType })
        ) { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId") ?: ""
            ProfileScreen(
                userId = userId,
                onNavigateBack = { navController.popBackStack() }
            )
        }
        
        composable("settings") {
            SettingsScreen(
                onNavigateBack = { navController.popBackStack() }
            )
        }
    }
}

// Home Screen
@Composable
fun HomeScreen(
    onNavigateToProfile: (String) -> Unit
) {
    val users = remember { 
        listOf(
            User("1", "علی سلمانیان", "ali@example.com"),
            User("2", "مریم احمدی", "maryam@example.com"),
            User("3", "حسن رضایی", "hasan@example.com")
        )
    }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "لیست کاربران",
            style = MaterialTheme.typography.headlineLarge,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        LazyColumn {
            items(users) { user ->
                UserCard(
                    user = user,
                    onClick = { onNavigateToProfile(user.id) }
                )
            }
        }
    }
}

// Profile Screen
@Composable
fun ProfileScreen(
    userId: String,
    onNavigateBack: () -> Unit
) {
    var user by remember { mutableStateOf(null) }
    var isLoading by remember { mutableStateOf(true) }
    
    LaunchedEffect(userId) {
        // Simulate API call
        delay(1000)
        user = User(userId, "علی سلمانیان", "ali@example.com")
        isLoading = false
    }
    
    if (isLoading) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator()
        }
    } else {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            TopAppBar(
                title = { Text("پروفایل") },
                navigationIcon = {
                    IconButton(onClick = onNavigateBack) {
                        Icon(
                            imageVector = Icons.Default.ArrowBack,
                            contentDescription = "بازگشت"
                        )
                    }
                }
            )
            
            user?.let { userData ->
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Column(
                        modifier = Modifier.padding(16.dp)
                    ) {
                        Text(
                            text = userData.name,
                            style = MaterialTheme.typography.headlineMedium
                        )
                        Text(
                            text = userData.email,
                            style = MaterialTheme.typography.bodyLarge,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                    }
                }
            }
        }
    }
}

Material Design 3 در Compose

1. Theme و Color System

// Theme.kt
@Composable
fun MyComposeAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

private val LightColorScheme = lightColorScheme(
    primary = Color(0xFF6750A4),
    onPrimary = Color(0xFFFFFFFF),
    primaryContainer = Color(0xFFEADDFF),
    onPrimaryContainer = Color(0xFF21005D),
    secondary = Color(0xFF625B71),
    onSecondary = Color(0xFFFFFFFF),
    secondaryContainer = Color(0xFFE8DEF8),
    onSecondaryContainer = Color(0xFF1D192B),
    tertiary = Color(0xFF7D5260),
    onTertiary = Color(0xFFFFFFFF),
    tertiaryContainer = Color(0xFFFFD8E4),
    onTertiaryContainer = Color(0xFF31111D),
    error = Color(0xFFBA1A1A),
    onError = Color(0xFFFFFFFF),
    errorContainer = Color(0xFFFFDAD6),
    onErrorContainer = Color(0xFF410002),
    background = Color(0xFFFFFBFE),
    onBackground = Color(0xFF1C1B1F),
    surface = Color(0xFFFFFBFE),
    onSurface = Color(0xFF1C1B1F),
    surfaceVariant = Color(0xFFE7E0EC),
    onSurfaceVariant = Color(0xFF49454F),
    outline = Color(0xFF79747E)
)

private val DarkColorScheme = darkColorScheme(
    primary = Color(0xFFD0BCFF),
    onPrimary = Color(0xFF381E72),
    primaryContainer = Color(0xFF4F378B),
    onPrimaryContainer = Color(0xFFEADDFF),
    secondary = Color(0xFFCCC2DC),
    onSecondary = Color(0xFF332D41),
    secondaryContainer = Color(0xFF4A4458),
    onSecondaryContainer = Color(0xFFE8DEF8),
    tertiary = Color(0xFFEFB8C8),
    onTertiary = Color(0xFF492532),
    tertiaryContainer = Color(0xFF633B48),
    onTertiaryContainer = Color(0xFFFFD8E4),
    error = Color(0xFFFFB4AB),
    onError = Color(0xFF690005),
    errorContainer = Color(0xFF93000A),
    onErrorContainer = Color(0xFFFFDAD6),
    background = Color(0xFF1C1B1F),
    onBackground = Color(0xFFE6E1E5),
    surface = Color(0xFF1C1B1F),
    onSurface = Color(0xFFE6E1E5),
    surfaceVariant = Color(0xFF49454F),
    onSurfaceVariant = Color(0xFFCAC4D0),
    outline = Color(0xFF938F99)
)

2. Typography System

// Typography.kt
val Typography = Typography(
    displayLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 57.sp,
        lineHeight = 64.sp,
        letterSpacing = (-0.25).sp
    ),
    displayMedium = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 45.sp,
        lineHeight = 52.sp,
        letterSpacing = 0.sp
    ),
    displaySmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 36.sp,
        lineHeight = 44.sp,
        letterSpacing = 0.sp
    ),
    headlineLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 32.sp,
        lineHeight = 40.sp,
        letterSpacing = 0.sp
    ),
    headlineMedium = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 28.sp,
        lineHeight = 36.sp,
        letterSpacing = 0.sp
    ),
    headlineSmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 24.sp,
        lineHeight = 32.sp,
        letterSpacing = 0.sp
    ),
    titleLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 22.sp,
        lineHeight = 28.sp,
        letterSpacing = 0.sp
    ),
    titleMedium = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    titleSmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 14.sp,
        lineHeight = 20.sp,
        letterSpacing = 0.1.sp
    ),
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    bodyMedium = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 14.sp,
        lineHeight = 20.sp,
        letterSpacing = 0.25.sp
    ),
    bodySmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 12.sp,
        lineHeight = 16.sp,
        letterSpacing = 0.4.sp
    ),
    labelLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 14.sp,
        lineHeight = 20.sp,
        letterSpacing = 0.1.sp
    ),
    labelMedium = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 12.sp,
        lineHeight = 16.sp,
        letterSpacing = 0.5.sp
    ),
    labelSmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 11.sp,
        lineHeight = 16.sp,
        letterSpacing = 0.5.sp
    )
)

انیمیشن‌ها در Compose

1. Basic Animations

// Simple Animation
@Composable
fun AnimatedCounter() {
    var count by remember { mutableIntStateOf(0) }
    val animatedCount by animateIntAsState(
        targetValue = count,
        animationSpec = tween(300)
    )
    
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "شمارش: $animatedCount",
            style = MaterialTheme.typography.headlineLarge
        )
        
        Button(onClick = { count++ }) {
            Text("افزایش")
        }
    }
}

// Visibility Animation
@Composable
fun AnimatedVisibilityExample() {
    var visible by remember { mutableStateOf(false) }
    
    Column {
        Button(onClick = { visible = !visible }) {
            Text(if (visible) "مخفی کردن" else "نمایش")
        }
        
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically() + fadeIn(),
            exit = slideOutVertically() + fadeOut()
        ) {
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Text(
                    text = "این متن با انیمیشن نمایش داده می‌شود",
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}

// Complex Animation
@Composable
fun AnimatedCard() {
    var expanded by remember { mutableStateOf(false) }
    val rotation by animateFloatAsState(
        targetValue = if (expanded) 180f else 0f,
        animationSpec = tween(500)
    )
    
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .clickable { expanded = !expanded }
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "کارت قابل انبساط",
                    style = MaterialTheme.typography.titleMedium
                )
                
                Icon(
                    imageVector = Icons.Default.ExpandMore,
                    contentDescription = null,
                    modifier = Modifier.rotate(rotation)
                )
            }
            
            AnimatedVisibility(
                visible = expanded,
                enter = expandVertically() + fadeIn(),
                exit = shrinkVertically() + fadeOut()
            ) {
                Column {
                    Spacer(modifier = Modifier.height(16.dp))
                    Text(
                        text = "این محتوای اضافی است که با انیمیشن نمایش داده می‌شود.",
                        style = MaterialTheme.typography.bodyMedium
                    )
                }
            }
        }
    }
}

بهترین روش‌های توسعه

1. Performance Optimization

// LazyColumn for Large Lists
@Composable
fun ProductList(products: List) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = products,
            key = { product -> product.id }
        ) { product ->
            ProductCard(product = product)
        }
    }
}

// Memoization with remember
@Composable
fun ExpensiveComputation(data: List) {
    val processedData = remember(data) {
        data.map { item ->
            // Expensive computation
            processData(item)
        }
    }
    
    LazyColumn {
        items(processedData) { item ->
            DataItem(item = item)
        }
    }
}

// State Hoisting
@Composable
fun SearchableList(
    items: List,
    onItemClick: (Item) -> Unit
) {
    var searchQuery by remember { mutableStateOf("") }
    var filteredItems by remember(searchQuery, items) {
        derivedStateOf {
            items.filter { item ->
                item.name.contains(searchQuery, ignoreCase = true)
            }
        }
    }
    
    Column {
        SearchBar(
            query = searchQuery,
            onQueryChange = { searchQuery = it }
        )
        
        LazyColumn {
            items(filteredItems) { item ->
                ItemCard(
                    item = item,
                    onClick = { onItemClick(item) }
                )
            }
        }
    }
}

2. Testing در Compose

// Unit Test
@Test
fun `Counter should increment when button clicked`() {
    composeTestRule.setContent {
        Counter()
    }
    
    composeTestRule
        .onNodeWithText("شمارش: 0")
        .assertIsDisplayed()
    
    composeTestRule
        .onNodeWithText("افزایش")
        .performClick()
    
    composeTestRule
        .onNodeWithText("شمارش: 1")
        .assertIsDisplayed()
}

// UI Test
@Test
fun `UserCard should display user information`() {
    val user = User("1", "علی سلمانیان", "ali@example.com")
    
    composeTestRule.setContent {
        UserCard(
            user = user,
            onEditClick = {},
            onDeleteClick = {}
        )
    }
    
    composeTestRule
        .onNodeWithText("علی سلمانیان")
        .assertIsDisplayed()
    
    composeTestRule
        .onNodeWithText("ali@example.com")
        .assertIsDisplayed()
}

// Integration Test
@Test
fun `Navigation should work correctly`() {
    composeTestRule.setContent {
        MyApp()
    }
    
    // Navigate to profile
    composeTestRule
        .onNodeWithText("علی سلمانیان")
        .performClick()
    
    composeTestRule
        .onNodeWithText("پروفایل")
        .assertIsDisplayed()
}

💡 نکات مهم Compose:

  • همیشه از remember برای state استفاده کنید
  • State را به بالاترین سطح ممکن منتقل کنید
  • از LazyColumn برای لیست‌های بزرگ استفاده کنید
  • Component ها را کوچک و قابل استفاده مجدد نگه دارید
  • از Material Design 3 استفاده کنید

مقالات مرتبط

برای یادگیری بیشتر، مقالات زیر را مطالعه کنید:

نتیجه‌گیری

Jetpack Compose آینده توسعه UI اندروید است. با استفاده از declarative programming و Kotlin، می‌توانید اپلیکیشن‌های زیبا و کارآمد بسازید. علی سلمانیان آماده کمک به شما در پروژه‌های Compose است.

درباره نویسنده

علی سلمانیان - برنامه نویس وب و اندروید با تخصص در Jetpack Compose. متخصص Kotlin، Material Design و Android Development.

📧 alisalmanian1395@gmail.com | 📱 +98 938 822 2808