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

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

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

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

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

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

اگر حالت شما در UI، یا در توابع ترکیب‌پذیر یا کلاس‌های دارنده حالت ساده که در محدوده Composition قرار دارند، بالا می‌رود، می‌توانید از rememberSaveable برای حفظ حالت در سراسر فعالیت و بازآفرینی فرآیند استفاده کنید.

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

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

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

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

شکل 1 . حباب پیام چت با ضربه زدن بزرگ می شود و جمع می شود.

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

rememberSaveable حالت عنصر UI را در یک Bundle از طریق مکانیسم حالت نمونه ذخیره شده ذخیره می کند.

این می تواند انواع اولیه را به صورت خودکار در بسته ذخیره کند. اگر حالت شما در یک نوع ابتدایی نیست، مانند کلاس داده، می‌توانید از مکانیسم‌های ذخیره‌سازی مختلفی استفاده کنید، مانند استفاده از حاشیه‌نویسی Parcelize ، استفاده از Compose API مانند listSaver و mapSaver ، یا پیاده‌سازی یک کلاس محافظ سفارشی که Compose Runtime Saver را گسترش می‌دهد. کلاس برای کسب اطلاعات بیشتر در مورد این روش‌ها، راه‌های ذخیره اسناد حالت را ببینید.

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

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

بهترین تمرین

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

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

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

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

بازیابی حالت را تأیید کنید

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

منطق کسب و کار

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

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

با این حال، یک نمونه ViewModel از مرگ فرآیند آغاز شده توسط سیستم جان سالم به در نمی برد. برای اینکه حالت رابط کاربری در این حالت باقی بماند، از ماژول Saved State برای ViewModel استفاده کنید که حاوی SavedStateHandle API است.

بهترین تمرین

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

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

API های SavedStateHandle

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

State نوشتن

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

API saveable از انواع اولیه خارج از جعبه پشتیبانی می‌کند و یک پارامتر stateSaver برای استفاده از ذخیره‌کننده‌های سفارشی، درست مانند rememberSaveable() دریافت می‌کند.

در قطعه زیر، 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() برای ذخیره حالت عنصر UI و مصرف آن به عنوان یک جریان از 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 های پوشش داده شده در این بخش و زمان استفاده از هر یک برای ذخیره وضعیت UI را نشان می دهد:

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

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

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

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

{% کلمه به کلمه %} {% آخر کلمه %}