وضعیت رابط کاربری را در Compose ذخیره کنید

بسته به اینکه وضعیت شما به کجا منتقل شده و منطق مورد نیاز چیست، می‌توانید از APIهای مختلفی برای ذخیره و بازیابی وضعیت رابط کاربری خود استفاده کنید. هر برنامه از ترکیبی از APIها برای دستیابی به بهترین نتیجه استفاده می‌کند.

هر برنامه اندروید می‌تواند به دلیل فعالیت یا بازآفرینی فرآیند ، وضعیت رابط کاربری خود را از دست بدهد. این از دست دادن وضعیت می‌تواند به دلیل رویدادهای زیر رخ دهد:

حفظ وضعیت پس از این رویدادها برای یک تجربه کاربری مثبت ضروری است. انتخاب اینکه کدام وضعیت حفظ شود، به جریان‌های کاربری منحصر به فرد برنامه شما بستگی دارد. به عنوان یک روش بهتر، حداقل باید ورودی کاربر و وضعیت مربوط به ناوبری را حفظ کنید. نمونه‌هایی از این موارد شامل موقعیت اسکرول یک لیست، شناسه موردی که کاربر جزئیات بیشتری در مورد آن می‌خواهد، انتخاب در حال انجام تنظیمات کاربر یا ورودی در فیلدهای متنی است.

این صفحه خلاصه‌ای از APIهای موجود برای ذخیره حالت رابط کاربری، بسته به محل ذخیره حالت شما و منطقی که به آن نیاز دارد، ارائه می‌دهد.

منطق رابط کاربری

اگر وضعیت شما در رابط کاربری، چه در توابع قابل ترکیب و چه در کلاس‌های نگهدارنده وضعیت ساده که به ترکیب محدود شده‌اند، منتقل شده است، می‌توانید rememberSaveable برای حفظ وضعیت در طول فعالیت و بازتولید فرآیند استفاده کنید.

در قطعه کد زیر، rememberSaveable برای ذخیره حالت یک عنصر رابط کاربری از نوع بولی استفاده شده است:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

شکل ۱. حباب پیام چت با ضربه زدن باز و بسته می‌شود.

showDetails یک متغیر بولی است که مشخص می‌کند آیا حباب چت جمع شده یا باز شده است.

rememberSaveable وضعیت عنصر رابط کاربری را از طریق مکانیسم وضعیت نمونه ذخیره شده در یک Bundle ذخیره می‌کند.

این ابزار قادر است انواع اولیه را به طور خودکار در bundle ذخیره کند. اگر state شما در نوعی غیر اولیه، مانند یک کلاس داده، نگهداری می‌شود، می‌توانید از مکانیسم‌های ذخیره‌سازی مختلفی مانند استفاده از حاشیه‌نویسی Parcelize ، استفاده از APIهای Compose مانند listSaver و mapSaver یا پیاده‌سازی یک کلاس saver سفارشی که کلاس زمان اجرای Compose Saver را گسترش می‌دهد، استفاده کنید. برای کسب اطلاعات بیشتر در مورد این روش‌ها، به مستندات «روش‌های ذخیره state» مراجعه کنید.

در قطعه کد زیر، API مربوط به rememberLazyListState Compose، LazyListState که شامل وضعیت اسکرول یک LazyColumn یا LazyRow است، با استفاده از rememberSaveable ذخیره می‌کند. این API از LazyListState.Saver استفاده می‌کند که یک ذخیره‌کننده سفارشی است که قادر به ذخیره و بازیابی وضعیت اسکرول است. پس از اجرای مجدد یک فعالیت یا فرآیند (به عنوان مثال، پس از تغییر پیکربندی مانند تغییر جهت دستگاه)، وضعیت اسکرول حفظ می‌شود.

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

بهترین شیوه

rememberSaveable از یک Bundle برای ذخیره وضعیت رابط کاربری استفاده می‌کند که توسط سایر APIهایی که در آن می‌نویسند، مانند فراخوانی‌های onSaveInstanceState() در activity شما، به اشتراک گذاشته می‌شود. با این حال، اندازه این Bundle محدود است و ذخیره اشیاء بزرگ می‌تواند منجر به خطاهای TransactionTooLarge در زمان اجرا شود. این امر می‌تواند به ویژه در برنامه‌های Activity واحد که در آنها Bundle یکسانی در سراسر برنامه استفاده می‌شود، مشکل‌ساز باشد.

برای جلوگیری از این نوع خرابی، نباید اشیاء پیچیده بزرگ یا لیستی از اشیاء را در بسته نرم‌افزاری ذخیره کنید .

در عوض، حداقل وضعیت مورد نیاز، مانند شناسه‌ها یا کلیدها را ذخیره کنید و از آنها برای واگذاری بازیابی وضعیت پیچیده‌تر رابط کاربری به سازوکارهای دیگر، مانند ذخیره‌سازی پایدار ، استفاده کنید.

این انتخاب‌های طراحی به موارد استفاده خاص برنامه شما و نحوه انتظار کاربران از آن بستگی دارد.

تأیید بازیابی وضعیت

شما می‌توانید تأیید کنید که وضعیت ذخیره شده با rememberSaveable در عناصر Compose شما، هنگام ایجاد مجدد فعالیت یا فرآیند، به درستی بازیابی می‌شود. APIهای خاصی برای دستیابی به این هدف وجود دارد، مانند StateRestorationTester . برای کسب اطلاعات بیشتر، مستندات Testing را بررسی کنید.

منطق کسب و کار

اگر وضعیت عنصر رابط کاربری شما به دلیل الزام منطق کسب‌وکار به ViewModel منتقل شده است، می‌توانید از APIهای ViewModel استفاده کنید.

یکی از مزایای اصلی استفاده از ViewModel در برنامه اندروید شما این است که تغییرات پیکربندی را به صورت رایگان مدیریت می‌کند. هنگامی که تغییری در پیکربندی رخ می‌دهد و activity از بین می‌رود و دوباره ایجاد می‌شود، وضعیت UI که به ViewModel منتقل شده است در حافظه نگه داشته می‌شود. پس از بازسازی، نمونه ViewModel قدیمی به نمونه activity جدید متصل می‌شود.

با این حال، یک نمونه ViewModel پس از مرگ فرآیند آغاز شده توسط سیستم، دوام نمی‌آورد. برای اینکه وضعیت رابط کاربری (UI state) در این حالت باقی بماند، از ماژول Saved State برای ViewModel استفاده کنید که شامل API SavedStateHandle است.

بهترین شیوه

SavedStateHandle همچنین از مکانیزم Bundle برای ذخیره حالت رابط کاربری استفاده می‌کند، بنابراین شما فقط باید از آن برای ذخیره حالت عنصر رابط کاربری ساده استفاده کنید.

وضعیت رابط کاربری صفحه نمایش ، که با اعمال قوانین تجاری و دسترسی به لایه‌های برنامه شما غیر از رابط کاربری تولید می‌شود، به دلیل پیچیدگی و اندازه بالقوه آن، نباید در SavedStateHandle ذخیره شود. می‌توانید از مکانیسم‌های مختلفی برای ذخیره داده‌های پیچیده یا بزرگ، مانند ذخیره‌سازی پایدار محلی ، استفاده کنید. پس از بازآفرینی فرآیند، صفحه نمایش با وضعیت گذرای بازیابی شده که در SavedStateHandle ذخیره شده بود (در صورت وجود) بازسازی می‌شود و وضعیت رابط کاربری صفحه نمایش دوباره از لایه داده تولید می‌شود.

APIهای SavedStateHandle

SavedStateHandle APIهای مختلفی برای ذخیره وضعیت عناصر رابط کاربری دارد، که مهم‌ترین آنها عبارتند از:

State نوشتن saveable()
StateFlow getStateFlow()

State نوشتن

از API saveable SavedStateHandle برای خواندن و نوشتن حالت عنصر رابط کاربری به عنوان MutableState استفاده کنید، بنابراین با حداقل تنظیم کد، از فعالیت و بازآفرینی فرآیند جان سالم به در می‌برد.

API saveable از انواع اولیه (primitive types) به صورت پیش‌فرض پشتیبانی می‌کند و درست مانند rememberSaveable() یک پارامتر stateSaver برای استفاده از saverهای سفارشی دریافت می‌کند.

در قطعه کد زیر، message ورودی کاربر را که در یک TextField تایپ شده است، ذخیره می‌کند:

class ConversationViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

برای اطلاعات بیشتر در مورد استفاده از API saveable به مستندات SavedStateHandle مراجعه کنید.

StateFlow

از getStateFlow() برای ذخیره حالت عنصر رابط کاربری و استفاده از آن به عنوان یک جریان از SavedStateHandle استفاده کنید. StateFlow فقط خواندنی است و API از شما می‌خواهد که یک کلید مشخص کنید تا بتوانید جریان را برای انتشار یک مقدار جدید جایگزین کنید. با کلیدی که پیکربندی کرده‌اید، می‌توانید StateFlow بازیابی کرده و آخرین مقدار را جمع‌آوری کنید.

در قطعه کد زیر، savedFilterType یک متغیر StateFlow است که نوع فیلتر اعمال شده بر روی لیستی از کانال‌های چت در یک برنامه چت را ذخیره می‌کند:

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

هر بار که کاربر نوع فیلتر جدیدی را انتخاب می‌کند، setFiltering فراخوانی می‌شود. این تابع مقدار جدیدی را در SavedStateHandle ذخیره می‌کند که با کلید _CHANNEL_FILTER_SAVED_STATE_KEY_ ذخیره می‌شود. savedFilterType جریانی است که آخرین مقدار ذخیره شده در کلید را منتشر می‌کند. filteredChannels برای انجام فیلتر کردن کانال در جریان مشترک شده است.

برای اطلاعات بیشتر در مورد API مربوط به getStateFlow() به مستندات SavedStateHandle مراجعه کنید.

خلاصه

جدول زیر خلاصه‌ای از APIهای پوشش داده شده در این بخش و زمان استفاده از هر کدام برای ذخیره وضعیت رابط کاربری را نشان می‌دهد:

رویداد منطق رابط کاربری منطق کسب و کار در یک ViewModel
تغییرات پیکربندی rememberSaveable خودکار
مرگ فرآیند آغاز شده توسط سیستم rememberSaveable SavedStateHandle

API مورد استفاده به محل نگهداری state و منطقی که به آن نیاز دارد بستگی دارد. برای state که در منطق UI استفاده می‌شود، rememberSaveable استفاده کنید. برای state که در منطق business استفاده می‌شود، اگر آن را در ViewModel نگهداری می‌کنید، آن را با استفاده از SavedStateHandle ذخیره کنید.

شما باید از APIهای بسته ( rememberSaveable و SavedStateHandle ) برای ذخیره مقادیر کمی از وضعیت رابط کاربری استفاده کنید. این داده‌ها حداقل داده‌های لازم برای بازیابی رابط کاربری به حالت قبلی خود، همراه با سایر مکانیسم‌های ذخیره‌سازی هستند. به عنوان مثال، اگر شناسه پروفایلی را که کاربر به آن نگاه می‌کرده در بسته ذخیره کنید، می‌توانید داده‌های سنگین، مانند جزئیات پروفایل، را از لایه داده دریافت کنید.

برای اطلاعات بیشتر در مورد روش‌های مختلف ذخیره وضعیت رابط کاربری، به مستندات عمومی ذخیره وضعیت رابط کاربری و صفحه لایه داده در راهنمای معماری مراجعه کنید.

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}