,

مدیریت State در Compose

مدیریت State در Compose

در کامپوز، برای بروزرسانی (recomposition) رابط کاربری از State ها استفاده می کنیم.

مدیریت State با remember و mutableStateOf

توابع Composable می‌توانند از remember برای ذخیره یک شیء در حافظه استفاده کنند.

@Composable
fun HelloContent() {
    var name by remember { mutableStateOf("") }
    
    if (name.isNotEmpty()) {
        Text(
            text = "Hello, $name!",
            modifier = Modifier.padding(bottom = 8.dp)
        )
    }
    
    OutlinedTextField(
        value = name,
        onValueChange = { name = it },
        label = { Text("Name") }
    )
}

mutableStateOf یک MutableState قابل مشاهده ایجاد می‌کند که با runtime کامپوز یکپارچه است. هر تغییری در value باعث ایجاد مجدد کامپوننت هایی می‌شود که این مقدار را می‌خوانند.

۲ راه برای اعلام یک MutableState:

// روش اول
val mutableState = remember { mutableStateOf(default) }

// روش دوم
var value by remember { mutableStateOf(default) }

rememberSaveable چیست؟

در حالی که remember به شما کمک می‌کند State را در طول ریکامپوزها حفظ کنید، State در طول تغییرات پیکربندی مثل چرخش صفحه، تغییر حالت دارک/لایت مد دستگاه حین باز بودن برنامه، حفظ نمی‌شود. برای این منظور، باید از rememberSaveable برای مقادیری که نگه داریشان داری اهمیت هست، استفاده کنید.

@Composable
fun RememberSaveableExample() {
    var count by rememberSaveable { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

rememberSaveable معادل SavedInstanceState در کامپوز هست.

چرخه حیات Activity در اندروید

چه زمانی از چه روشی استفاده کنیم؟

  • remember: برای State‌های موقت در طول recomposeها (مثلا نمایش پیغام، بازبودن منو پاپ آپ و…)
  • rememberSaveable: برای State‌های ساده UI که باید در تغییرات پیکربندی ذخیره بمانند.
  • ViewModel: برای State‌های پیچیده (بعدا راجعش حرف می زنیم.)

State Hoisting و مزایای آن

برای انتقال State به کامپوننت های پایین‌تر، بهتره که مقدارش رو و نه خود state رو به کامپوننت منتقل کنیم. حالا اگر در کامپوننت پایین‌تر قراره تغییری هم برای state اتفاق بیوفته با کالبک به کامپوننت بالایی اطلاع می دیم تا state رو بروز کنه.

State Hoisting in Jetpack Compose

State Hoisting: والد دارای State و فرزند بدون State

مثال State Hoisting:

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }
    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp)
        )
        OutlinedTextField(
            value = name, 
            onValueChange = onNameChange, 
            label = { Text("Name") }
        )
    }
}

مزایای State Hoisting:

  • منبع واحد: فقط یک منبع State وجود دارد.
  • کپسوله شدن: فقط Composable‌های Stateful می‌توانند State را تغییر دهند.
  • قابل اشتراک‌گذاری: State را می‌توان با چندین کامپوننت Composable به اشتراک گذاشت.
  • جداسازی شده: State می‌تواند در هر جایی ذخیره شود.

با بالا بردن State از HelloContent، می‌توان آن را در موقعیت‌های مختلف مجدداً استفاده کرد.

الگوی State Hoisting:

  • جایگزین متغیر State با دو پارامتر زیر:
  • value: T: مقدار فعلی برای نمایش
  • onValueChange: (T) -> Unit: رویدادی که تغییر State را درخواست می‌کند.

derivedStateOf

derivedStateOf برای بهینه‌سازی reCompose با جلوگیری از ریکامپوزهای غیرضروری استفاده می‌شود.

@Composable
private fun Counter() {
    val counterState = remember { mutableStateOf(0) }
    val showHurrayState = remember {
        derivedStateOf { counterState.value >= 10 }
    }
    
    Button(
        onClick = { counterState.value++ }
    ) {
        Text(counterState.value.toString())
    }
    
    if (showHurrayState.value) Text("Hurray!")
}

derivedStateOf فقط زمانی باعث بازترکیب (ریکامپوز) می‌شود که مقدار محاسبه شده تغییر کند، نه هر بار که State وابسته تغییر کند.

استفاده از Stateها در ViewModel

ViewModel، داده ها رو جدا از لایه UI نگه داری می کنه و باعث میشه با تغییرات کانفیگی مثل تم روز و شب، چرخش صفحه و … اطلاعات از بین نرن.

هر چند داخل ViewModel هم می توانید از state ها مستقیما استفاده کنید ولی بهتر هست از StateFlow ها استفاده شود، زیرا StateFlow روش توصیه شده برای مدیریت State در ViewModel با کامپوز است، زیرا به طور خاص برای Kotlin Coroutines طراحی شده.

ViewModel Architecture

ویژگی‌های کلیدی ViewModel:

  • زنده ماندن در تغییرات پیکربندی
  • نگهداری منطق و داده‌های UI
  • آگاه از چرخه حیات (lifecycle)
import androidx.lifecycle.ViewModel

class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count
    
    fun increment() {
        _count.value = _count.value + 1
    }
}

دسترسی به ViewModel در Composable:

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val currentCount by viewModel.count.collectAsState()
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Count: $currentCount",
            style = MaterialTheme.typography.headlineMedium
        )
        Button(onClick = { viewModel.increment() }) {
            Text("Increment")
        }
    }
}

مشاهده State از ViewModel:

استفاده از StateFlow

@Composable
fun MyScreen() {
    val textValue by viewModel.uiState
        .collectAsState()
    Text(textValue)
}

مثال‌های کاربردی

مثال ۱: لیست کارها با ViewModel

data class TodoItem(
    val id: Int,
    val text: String,
    val isCompleted: Boolean
)

class TodoViewModel : ViewModel() {
    private val _todos = MutableStateFlow(listOf<TodoItem>())
    val todos: StateFlow<List<TodoItem>> = _todos.asStateFlow()
    
    private val _newTodoText = MutableStateFlow("")
    val newTodoText: StateFlow<String> = _newTodoText.asStateFlow()
    
    fun addTodo() {
        if (_newTodoText.value.isNotBlank()) {
            val newTodo = TodoItem(
                id = System.currentTimeMillis().toInt(),
                text = _newTodoText.value,
                isCompleted = false
            )
            _todos.value = _todos.value + newTodo
            _newTodoText.value = ""
        }
    }
    
    fun toggleTodo(id: Int) {
        _todos.value = _todos.value.map { todo ->
            if (todo.id == id) {
                todo.copy(isCompleted = !todo.isCompleted)
            } else {
                todo
            }
        }
    }
    
    fun deleteTodo(id: Int) {
        _todos.value = _todos.value.filter { it.id != id }
    }
    
    fun updateNewTodoText(text: String) {
        _newTodoText.value = text
    }
}

این ViewModel از StateFlow برای مدیریت State لیست کارها و متن ورودی جدید استفاده می‌کند. StateFlow یک نوع قابل مشاهده است که به طور خاص برای جت پک کامپوز طراحی شده است.

مثال ۲: فرم ورود با ViewModel

قبل از ویو مدل:

@Composable
fun LoginScreen() {
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var isLoading by remember { mutableStateOf(false) }
    
    // UI components using these state variables
}

بعد از ویو مدل:

@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
    val loginForm by viewModel.loginForm.collectAsState()
    
    LoginContent(
        email = loginForm.email,
        onEmailChange = { viewModel.updateEmail(it) },
        password = loginForm.password,
        onPasswordChange = { viewModel.updatePassword(it) },
        isLoading = loginForm.isLoading,
        onLoginClick = { viewModel.login() }
    )
}

ما State را به یک ViewModel منتقل کرده‌ایم و UI را Stateless کرده‌ایم. این باعث می‌شود UI قابل تست‌تر، قابل استفاده مجدد و جداسازی شده از منطق باشد.

Comments

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *