در کامپوز، برای بروزرسانی (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 در کامپوز هست.

چه زمانی از چه روشی استفاده کنیم؟
- remember: برای Stateهای موقت در طول recomposeها (مثلا نمایش پیغام، بازبودن منو پاپ آپ و…)
- rememberSaveable: برای Stateهای ساده UI که باید در تغییرات پیکربندی ذخیره بمانند.
- ViewModel: برای Stateهای پیچیده (بعدا راجعش حرف می زنیم.)
State Hoisting و مزایای آن
برای انتقال State به کامپوننت های پایینتر، بهتره که مقدارش رو و نه خود state رو به کامپوننت منتقل کنیم. حالا اگر در کامپوننت پایینتر قراره تغییری هم برای state اتفاق بیوفته با کالبک به کامپوننت بالایی اطلاع می دیم تا state رو بروز کنه.
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:
- زنده ماندن در تغییرات پیکربندی
- نگهداری منطق و دادههای 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 قابل تستتر، قابل استفاده مجدد و جداسازی شده از منطق باشد.
دیدگاهتان را بنویسید