وضعیت در برنامه به هر ارزشی گفته میشود که میتواند در طول زمان تغییر کند. این تعریف بسیار گسترده است و همه چیز را از پایگاه داده Room گرفته تا متغیری در یک کلاس دربرمیگیرد.
همه برنامههای Android وضعیت را به کاربر نشان میدهند. چند نمونه از وضعیت در برنامههای Android:
- «میانوعدهخوری» که وقتی اتصال شبکه برقرار نمیشود نشان داده میشود.
- پست وبلاگ و نظرات مرتبط.
- پویانماییهای موجی روی دکمهها که وقتی کاربر روی آنها کلیک میکند پخش میشوند.
- برچسبهایی که کاربر میتواند روی تصویر بکشد.
Jetpack Compose به شما کمک میکند درباره مکان و نحوه ذخیره و استفاده از حالت در برنامه Android شفافسازی کنید. این راهنما بر ارتباط بین حالت و عناصر ترکیبپذیر و بر میاناهای برنامهسازی کاربردی که Jetpack Compose برای کار با حالت ارائه میدهد تمرکز دارد.
وضعیت و ترکیب
Compose بیانیه است و به همین دلیل تنها راه بهروز کردن آن فراخوانی همان عنصر ترکیبی با آرگومانهای جدید است. این آرگومانها نشاندهنده وضعیت واسط کاربر هستند. هر زمان که وضعیتی بهروزرسانی میشود، ترکیب مجدد انجام میشود. درنتیجه، مواردی مثل TextField مانند آنچه در نماهای مبتنی بر XML امری انجام میشود، بهطور خودکار بهروزرسانی نمیشوند. برای اینکه یک عنصر ترکیبی مطابق با وضعیت جدید بهروز شود، باید وضعیت جدید بهطور صریح به آن اعلام شود.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
اگر این را اجرا کنید و سعی کنید نوشتاری وارد کنید، خواهید دید که هیچ اتفاقی نمیافتد. بهدلیل اینکه TextField خودش را بهروز نمیکند، بلکه وقتی پارامتر value
تغییر میکند بهروز میشود. این امر بهدلیل نحوه عملکرد ترکیب و ترکیب مجدد در
«نوشتن» است.
ترکیب اولیه: ایجاد «ترکیب» با اجرای عناصر ترکیبی برای اولینبار.
ترکیب مجدد: اجرای مجدد عناصر ترکیبی برای بهروزرسانی «ترکیب» هنگام تغییر دادهها.
برای کسب اطلاعات بیشتر درباره ترکیب اولیه و ترکیب مجدد، به تفکر در «نوشتن» مراجعه کنید.
وضعیت در عناصر ترکیبپذیر
توابع ترکیبپذیر میتوانند از remember API برای ذخیره کردن
شیء در حافظه استفاده کنند. مقدار محاسبهشده توسط remember در «ترکیب»
درطول ترکیب اولیه ذخیره میشود و مقدار ذخیرهشده درطول
ترکیب مجدد برگردانده میشود. از remember میتوان برای ذخیره کردن هم اشیای تغییرپذیر و هم اشیای تغییرناپذیر استفاده کرد.
mutableStateOf یک مشاهدهگر ایجاد میکند
MutableState<T>،
که نوع مشاهدهگر یکپارچه با زمان اجرای Compose است.
interface MutableState<T> : State<T> {
override var value: T
}
هرگونه تغییر در زمانبندی value بازترکیب هریک از توابع ترکیبی که value را میخوانند.
سه روش برای تعریف کردن شیء MutableState در یک عنصر ترکیبی وجود دارد:
val mutableState = remember { mutableStateOf(default) }var value by remember { mutableStateOf(default) }val (value, setValue) = remember { mutableStateOf(default) }
این بیانیهها معادل هستند و بهعنوان شکر نحوی برای استفادههای مختلف از حالت ارائه میشوند. باید موردی را انتخاب کنید که کد خواناترین را در عنصر ترکیبی که مینویسید تولید کند.
دستورگان نماینده by به وارد کردن موارد زیر نیاز دارد:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
میتوانید از مقدار بهیادآوردهشده بهعنوان پارامتر برای عناصر ترکیبی دیگر یا حتی بهعنوان
منطق در دستورات برای تغییر عناصر ترکیبی نمایشدادهشده استفاده کنید. برای مثال، اگر نمیخواهید وقتی نام خالی است سلام نمایش داده شود، از وضعیت در عبارت
if استفاده کنید:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
درحالیکه remember به شما کمک میکند وضعیت را در ترکیبهای مجدد حفظ کنید، وضعیت در تغییرات پیکربندی حفظ نمیشود. برای این کار، باید از
rememberSaveable استفاده کنید. rememberSaveable بهطور خودکار هر مقداری را که بتوان در Bundle ذخیره کرد ذخیره میکند. برای مقادیر دیگر، میتوانید یک شیء ذخیرهکننده سفارشی ارسال کنید.
انواع دیگر وضعیت پشتیبانیشده
«نگارش» نیازی ندارد که از MutableState<T> برای نگهداری وضعیت استفاده کنید؛
این کتابخانه از انواع قابلمشاهده دیگر پشتیبانی میکند. قبلاز خواندن نوع مشاهدهپذیر دیگری در
Compose، باید آن را به State<T> تبدیل کنید تا
ترکیبشوندهها بتوانند هنگام تغییر وضعیت بهطور خودکار دوباره ترکیب شوند.
Compose با توابعی برای ایجاد State<T> از انواع قابلمشاهده رایج مورداستفاده در برنامههای Android ارائه میشود. قبلاز استفاده از این یکپارچهسازیها، آرتیفکت(های) مناسب را بهصورت زیر اضافه کنید:
Flow:collectAsStateWithLifecycle()collectAsStateWithLifecycle()مقادیر را ازFlowبهشیوهای آگاه از چرخه حیات جمعآوری میکند و به برنامهتان اجازه میدهد منابع برنامه را حفظ کند. این مقدار نشاندهنده جدیدترین مقدار منتشرشده از ComposeStateاست. از این API بهعنوان روش توصیهشده برای جمعآوری جریانها در برنامههای Android استفاده کنید.وابستگی زیر در فایل
build.gradleلازم است (باید نسخه 2.6.0-beta01 یا جدیدتر باشد):
کاتلین
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.11.0")
}
شیک
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.11.0"
}
-
collectAsStateشبیهcollectAsStateWithLifecycleاست، زیرا این مورد نیز مقادیر را ازFlowجمعآوری میکند و آن را به ComposeStateتبدیل میکند.بهجای
collectAsStateWithLifecycleکه فقط برای Android است، ازcollectAsStateبرای کد مستقل از پلاتفرم استفاده کنید.برای
collectAsState، وابستگیهای اضافی لازم نیست، زیرا درcompose-runtimeدردسترس است. -
observeAsState()اینLiveDataرا مشاهده میکند و مقادیر آن را بااستفاده ازStateنشان میدهد.وابستگی زیر در فایل
build.gradleالزامی است:
کاتلین
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.12.1")
}
شیک
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.12.1"
}
-
subscribeAsState()توابع افزونهای هستند که جاریسازیهای واکنشگر RxJava2 (برای نمونه،Single،Observable،Completable) را به ComposeStateتبدیل میکنند.وابستگی زیر در فایل
build.gradleالزامی است:
کاتلین
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.12.1")
}
شیک
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.12.1"
}
-
subscribeAsState()توابع افزونهای هستند که جاریسازیهای واکنشگر RxJava3 (برای نمونهSingle،Observable،Completable) را به ComposeStateتبدیل میکنند.وابستگی زیر در فایل
build.gradleالزامی است:
کاتلین
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.12.1")
}
شیک
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.12.1"
}
با وضعیت دربرابر بدون وضعیت
ترکیبپذیری که از remember برای ذخیره کردن شیء استفاده میکند وضعیت داخلی ایجاد میکند و ترکیبپذیری را وضعیتدار میکند. HelloContent نمونهای از عنصر ترکیبی حالتدار است زیرا وضعیت name خود را بهصورت داخلی نگهداری و اصلاح میکند. این میتواند در شرایطی مفید باشد که تماسگیرنده نیازی به کنترل وضعیت ندارد و میتواند بدون نیاز به مدیریت وضعیت توسط خود، از آن استفاده کند. بااینحال، عناصر ترکیبی با وضعیت داخلی معمولاً کمتر قابل استفاده مجدد هستند و آزمایش آنها سختتر است.
ترکیبشونده بدون حالت ترکیبشوندهای است که هیچ حالتی را نگه نمیدارد. یکی از روشهای رایج برای دستیابی به بیحالت بودن استفاده از بالابردن حالت است.
وقتی عناصر ترکیبی قابلاستفاده مجدد را توسعه میدهید، اغلب میخواهید هم نسخه حالتدار و هم نسخه بدون حالت از همان عنصر ترکیبی را نمایان کنید. نسخه حالتدار برای تماسگیرندگانی که به وضعیت اهمیت نمیدهند راحت است، و نسخه بدون حالت برای تماسگیرندگانی که نیاز به کنترل یا بالابردن وضعیت دارند ضروری است.
بالابردن حالت
الگوی بالابردن حالت در Compose الگویی برای انتقال حالت به فراخواننده تابع ترکیبپذیر است تا تابع ترکیبپذیر بدون حالت شود. الگوی کلی برای بالا بردن حالت در Jetpack Compose این است که متغیر حالت را با دو پارامتر جایگزین کنید:
value: T: مقدار فعلی برای نمایشonValueChange: (T) -> Unit: رویدادی که درخواست میکند مقدار تغییر کند، که در آنTمقدار جدید پیشنهادی است
بااینحال، به onValueChange محدود نمیشوید. اگر رویدادهای دقیقتر برای عنصر ترکیبی مناسب است، باید آنها را بااستفاده از لامبدا تعریف کنید.
حالتی که به این روش بالا میرود دارای چند ویژگی مهم است:
- منبع واحد حقیقت: با انتقال وضعیت بهجای تکرار آن، مطمئن میشویم که فقط یک منبع حقیقت وجود دارد. این کار به جلوگیری از اشکالات کمک میکند.
- کپسولی: فقط عناصر ترکیبی حالتدار میتوانند وضعیت خود را تغییر دهند. کاملاً داخلی است.
- قابلهمرسانی: وضعیت بالابردهشده را میتوان با چندین عنصر ترکیبی همرسانی کرد. اگر میخواستید
nameرا در یک عنصر ترکیبی دیگر بخوانید، برافراشتن به شما امکان میدهد این کار را انجام دهید. - قابلمداخله: تماسگیرندگان با عناصر ترکیبی بدون وضعیت میتوانند تصمیم بگیرند رویدادها را قبلاز تغییر وضعیت نادیده بگیرند یا اصلاح کنند.
- جداشده: وضعیت عناصر ترکیبی بدون وضعیت ممکن است در هر جایی ذخیره شود. برای مثال، اکنون میتوانید
nameرا بهViewModelمنتقل کنید.
در مثال، name و onValueChange را از
HelloContent استخراج میکنید و آنها را در درخت به یک عنصر ترکیبی HelloScreen که
HelloContent را فرا میخواند منتقل میکنید.
@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), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
با بیرون کشیدن وضعیت از HelloContent، استدلال درباره
ترکیبپذیر، استفاده مجدد از آن در موقعیتهای مختلف، و آزمایش کردن آسانتر میشود. HelloContent از نحوه ذخیره وضعیت آن جدا شده است. جداسازی یعنی اگر HelloScreen را اصلاح یا جایگزین کنید، لازم نیست نحوه پیادهسازی HelloContent را تغییر دهید.
الگویی که در آن وضعیت پایین میرود و رویدادها بالا میرود جریان داده یکطرفه نامیده میشود. در این حالت، وضعیت از HelloScreen
به HelloContent کاهش مییابد و رویدادها از HelloContent به HelloScreen افزایش مییابد. با
پیروی از جریان داده یکطرفه، میتوانید عناصر ترکیبی را که وضعیت را در
واسط کاربر نمایش میدهند از بخشهای برنامهتان که وضعیت را ذخیره و تغییر میدهند جدا کنید.
- وضعیت باید به حداقل پایینترین والد مشترک همه ترکیبپذیرهایی که از وضعیت استفاده میکنند (خواندن) ارتقا یابد.
- وضعیت باید به حداقل بالاترین سطحی که ممکن است تغییر کند (نوشتن) ارتقا یابد.
- اگر دو وضعیت در پاسخ به رویدادهای یکسان تغییر میکنند باید با هم بالا آورده شوند.
میتوانید وضعیت را بالاتر از آنچه این قوانین نیاز دارند ارتقا دهید، اما ارتقا دادن وضعیت به سطحی پایینتر از آن دنبال کردن جریان داده یکطرفه را دشوار یا غیرممکن میکند.
برای اطلاعات بیشتر، صفحه مکان برافراشتن پرچم ایالت را ببینید.
بازیابی وضعیت در Compose
rememberSaveable API عملکردی مشابه remember دارد زیرا
وضعیت را در ترکیبهای مجدد و همچنین در فعالیت یا فرایند
بازآفرینی بااستفاده از سازوکار وضعیت نمونه ذخیرهشده حفظ میکند. برای مثال، این اتفاق زمانی روی میدهد که صفحهنمایش چرخانده شود.
روشهای ذخیره وضعیت
همه انواع دادههایی که به Bundle اضافه میشوند بهطور خودکار ذخیره میشوند. اگر میخواهید چیزی را ذخیره کنید که نمیتواند به Bundle اضافه شود، چند گزینه وجود دارد.
Parcelize
سادهترین راهکار این است که @Parcelize
حاشیهنویسی را به شیء اضافه کنید. شیء قابل بستهبندی میشود و میتواند دستهبندی شود. برای
مثال، این کد نوع داده City قابلانتقال را میسازد و آن را در
وضعیت ذخیره میکند.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
اگر بهدلایلی @Parcelize مناسب نیست، میتوانید از mapSaver برای تعریف کردن قانون خودتان برای تبدیل شیء به مجموعهای از مقادیر که سیستم میتواند در Bundle ذخیره کند استفاده کنید.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
برای اینکه نیازی به تعریف کلیدهای نقشه نداشته باشید، میتوانید از listSaver
استفاده کنید و از شاخصهای آن بهعنوان کلید استفاده کنید:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
نگهدارندههای حالت در Compose
بالابردن وضعیت ساده را میتوان در خود توابع ترکیبپذیر مدیریت کرد. بااینحال، اگر مقدار وضعیت برای پیگیری افزایش یابد، یا منطق برای اجرا در توابع ترکیبپذیر بهوجود آید، بهتر است مسئولیتهای منطق و وضعیت را به کلاسهای دیگر واگذار کنید: نگهدارندگان وضعیت.
توجه داشته باشید که در مطالب دیگر، نگهدارندگان حالت را اشیاء حالت بالابردهشده نیز مینامند.
برای کسب اطلاعات بیشتر، به اسناد بالابردن وضعیت در Compose یا بهطور کلی، صفحه نگهدارندگان وضعیت و وضعیت واسط کاربر در راهنمای معماری مراجعه کنید.
وقتی کلیدها تغییر میکند، محاسبات یادآوری را دوباره راهاندازی کنید
از API remember اغلب همراه با MutableState استفاده میشود:
var name by remember { mutableStateOf("") }
در اینجا، استفاده از تابع remember باعث میشود مقدار MutableState در
ترکیبهای مجدد حفظ شود.
بهطورکلی، remember یک پارامتر لامبدا calculation میگیرد. وقتی remember
برای اولینبار اجرا میشود، تابع لامبدای calculation را فرا میخواند و نتیجه آن را ذخیره میکند. درطول
ترکیب مجدد، remember مقداری را که آخرینبار ذخیره شده است برمیگرداند.
علاوهبر ذخیره کردن وضعیت در حافظه نهان، میتوانید از remember برای ذخیره کردن هر شیء یا نتیجه عملیاتی در «ترکیب» که مقداردهی اولیه یا محاسبه آن پرهزینه است استفاده کنید. ممکن است نخواهید این محاسبه را در هر ترکیب مجددی تکرار کنید.
برای مثال، ایجاد این شیء ShaderBrush که عملیاتی پرهزینه است:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember مقدار را تا زمانی که از «قطعه» خارج شود ذخیره میکند. بااینحال، راهی برای نامعتبر کردن مقدار ذخیرهشده در حافظه نهان وجود دارد. میانای برنامهسازی کاربردی remember پارامتر key یا
keys را نیز میگیرد. اگر هریک از این کلیدها تغییر کند، دفعه بعدی که تابع
بازسازی میشود، remember حافظه نهان را نامعتبر میکند و بلوک لامبدا محاسبه را دوباره اجرا میکند. این سازوکار به شما امکان میدهد طول عمر یک
شیء در «ترکیب» را کنترل کنید. محاسبه تا زمانی که ورودیها تغییر کند معتبر میماند، نه تا زمانی که مقدار بهیادمانده از «ترکیب» خارج شود.
مثالهای زیر نحوه عملکرد این سازوکار را نشان میدهد.
در این گلچین، ShaderBrush ایجاد میشود و بهعنوان رنگ پسزمینه
یک عنصر ترکیبی Box استفاده میشود. remember نمونه ShaderBrush را ذخیره میکند
زیرا بازآفرینی آن پرهزینه است، همانطور که قبلاً توضیح داده شد. remember avatarRes را بهعنوان پارامتر key1 میگیرد، که تصویر پسزمینه انتخابشده است. اگر
avatarRes تغییر کند، قلممو با تصویر جدید بازترکیب میشود و دوباره روی
Box اعمال میشود. این اتفاق زمانی رخ میدهد که کاربر تصویر دیگری را از انتخابگر برای
پسزمینه انتخاب کند.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
در تکهکد بعدی، حالت به کلاس نگهدارنده حالت ساده
MyAppState ارتقا داده میشود. این افزونه تابع rememberMyAppState را برای مقداردهی اولیه نمونهای از کلاس بااستفاده از remember آشکار میکند. نمایان کردن چنین توابعی برای ایجاد نمونهای که از ترکیب مجدد جان سالم بهدر میبرد الگویی رایج در Compose است. تابع
rememberMyAppState مقدار windowSizeClass را دریافت میکند که بهعنوان
پارامتر key برای remember عمل میکند. اگر این پارامتر تغییر کند، برنامه باید کلاس نگهدارنده وضعیت ساده را با جدیدترین مقدار بازسازی کند. این اتفاق ممکن است، برای مثال، زمانی رخ دهد که کاربر دستگاه را بچرخاند.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose از پیادهسازی برابر کلاس برای تصمیمگیری درباره تغییر کلید و نامعتبر کردن مقدار ذخیرهشده استفاده میکند.
ذخیره وضعیت با کلیدهای فراتر از ترکیب مجدد
میانای برنامهسازی کاربردی rememberSaveable دادهپوشی در اطراف remember است که میتواند
دادهها را در Bundle ذخیره کند. این API به وضعیت اجازه میدهد نه تنها از بازترکیب، بلکه از بازآفرینی فعالیت و پایان فرایند آغازشده توسط سیستم نیز جان سالم بهدر ببرد.
rememberSaveable input پارامتر را برای همان هدفی که
remember keys را دریافت میکند دریافت میکند. وقتی هریک از ورودیها تغییر کند، حافظه نهان نامعتبر میشود. دفعه بعدی که تابع دوباره ترکیب میشود، rememberSaveable دوباره بلوک لامبدای محاسبه را اجرا میکند.
در مثال زیر، rememberSaveable تا زمانی که userTypedQuery تغییر کند ذخیره میشود:
typedQuery
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
بیشتر بدانید
برای کسب اطلاعات بیشتر درباره حالت و Jetpack Compose، به منابع تکمیلی زیر مراجعه کنید.
نمونهها
Codelabs
ویدیوها
وبلاگها
توصیهشده برای شما
- توجه: نوشتار پیوند وقتی جاوا اسکریپت خاموش است نمایش داده میشود
- معماری میانای کاربر Compose
- ذخیره وضعیت میانای کاربر در Compose
- عوارض جانبی در Compose