دارندگان ایالت و ایالت UI

راهنمای لایه رابط کاربری، جریان داده یک‌طرفه (UDF) را به عنوان وسیله‌ای برای تولید و مدیریت وضعیت رابط کاربری برای لایه رابط کاربری مورد بحث قرار می‌دهد.

داده‌ها به صورت یک‌طرفه از لایه داده به رابط کاربری جریان می‌یابند.
شکل ۱. جریان داده یک‌طرفه.

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

در پایان این سند، شما باید درک درستی از نحوه مدیریت وضعیت برنامه در لایه رابط کاربری داشته باشید؛ که همان خط تولید وضعیت رابط کاربری است. شما باید بتوانید موارد زیر را درک کرده و بدانید:

  • انواع حالت‌های رابط کاربری موجود در لایه رابط کاربری را درک کنید.
  • انواع منطقی که روی آن حالت‌های رابط کاربری در لایه رابط کاربری عمل می‌کنند را درک کنید.
  • بدانید که چگونه پیاده‌سازی مناسب یک نگهدارنده‌ی حالت، مانند ViewModel یا یک کلاس، را انتخاب کنید.

عناصر خط تولید وضعیت UI

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

حالت رابط کاربری

وضعیت رابط کاربری (UI state ) ویژگی‌ای است که رابط کاربری (UI) را توصیف می‌کند. دو نوع وضعیت رابط کاربری وجود دارد:

  • وضعیت رابط کاربری صفحه نمایش ، چیزی است که باید روی صفحه نمایش داده شود. برای مثال، یک کلاس NewsUiState می‌تواند شامل مقالات خبری و سایر اطلاعات مورد نیاز برای رندر رابط کاربری باشد. این وضعیت معمولاً به لایه‌های دیگر سلسله مراتب متصل است زیرا حاوی داده‌های برنامه است.
  • حالت عنصر رابط کاربری به ویژگی‌های ذاتی عناصر رابط کاربری اشاره دارد که بر نحوه رندر شدن آنها تأثیر می‌گذارند. یک عنصر رابط کاربری ممکن است نمایش داده شود یا پنهان شود و ممکن است فونت، اندازه فونت یا رنگ فونت خاصی داشته باشد. در Jetpack Compose، حالت نسبت به composable خارجی است و شما حتی می‌توانید آن را از مجاورت composable به داخل تابع فراخوانی کننده composable یا یک نگهدارنده حالت منتقل کنید. نمونه‌ای از این مورد ScaffoldState برای Scaffold composable است.

منطق

وضعیت رابط کاربری یک ویژگی ایستا نیست، زیرا داده‌های برنامه و رویدادهای کاربر باعث تغییر وضعیت رابط کاربری در طول زمان می‌شوند. منطق، جزئیات تغییر را تعیین می‌کند، از جمله اینکه چه بخش‌هایی از وضعیت رابط کاربری تغییر کرده‌اند، چرا تغییر کرده‌اند و چه زمانی باید تغییر کنند.

منطق، حالت رابط کاربری را تولید می‌کند
شکل ۲. منطق به عنوان تولیدکننده‌ی حالت رابط کاربری.

منطق در یک برنامه می‌تواند منطق تجاری یا منطق رابط کاربری باشد:

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

چرخه حیات اندروید و انواع حالت‌ها و منطق رابط کاربری

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

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

موارد فوق را می‌توان با جدول زیر خلاصه کرد:

چرخه عمر رابط کاربری مستقل وابسته به چرخه عمر رابط کاربری
منطق کسب و کار منطق رابط کاربری
وضعیت رابط کاربری صفحه نمایش

خط تولید وضعیت UI

خط تولید وضعیت رابط کاربری (UI state pipeline) به مراحل انجام شده برای تولید وضعیت رابط کاربری اشاره دارد. این مراحل شامل اعمال انواع منطقی است که قبلاً تعریف شده‌اند و کاملاً به نیازهای رابط کاربری شما وابسته هستند. برخی از رابط‌های کاربری ممکن است از هر دو بخش مستقل از چرخه حیات رابط کاربری (UI Lifecycle) و وابسته به چرخه حیات رابط کاربری (UI Lifecycle) در خط تولید بهره‌مند شوند، یا یکی از آنها را داشته باشند یا هیچ‌کدام را .

یعنی، جایگشت‌های زیر از خط لوله لایه رابط کاربری معتبر هستند:

  • وضعیت رابط کاربری توسط خود رابط کاربری تولید و مدیریت می‌شود. برای مثال، یک شمارنده پایه ساده و قابل استفاده مجدد:

    @Composable
    fun Counter() {
        // The UI state is managed by the UI itself
        var count by remember { mutableStateOf(0) }
        Row {
            Button(onClick = { ++count }) {
                Text(text = "Increment")
            }
            Button(onClick = { --count }) {
                Text(text = "Decrement")
            }
        }
    }
    
  • منطق رابط کاربری → رابط کاربری. برای مثال، نمایش یا پنهان کردن دکمه‌ای که به کاربر اجازه می‌دهد به بالای لیست برود.

    @Composable
    fun ContactsList(contacts: List<Contact>) {
        val listState = rememberLazyListState()
        val isAtTopOfList by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex < 3
            }
        }
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Show or hide the button (UI logic) based on the list scroll position
        AnimatedVisibility(visible = !isAtTopOfList) {
            ScrollToTopButton()
        }
    }
    
  • منطق کاری → رابط کاربری. یک عنصر رابط کاربری که عکس کاربر فعلی را روی صفحه نمایش می‌دهد.

    @Composable
    fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
        // Call on the UserAvatar Composable to display the photo
        UserAvatar(picture = uiState.profilePicture)
    }
    
  • منطق تجاری → منطق رابط کاربری → رابط کاربری. یک عنصر رابط کاربری که برای نمایش اطلاعات صحیح روی صفحه برای یک حالت رابط کاربری مشخص، اسکرول می‌کند.

    @Composable
    fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
        val contacts = uiState.contacts
        val deepLinkedContact = uiState.deepLinkedContact
    
        val listState = rememberLazyListState()
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Perform UI logic that depends on information from business logic
        if (deepLinkedContact != null && contacts.isNotEmpty()) {
            LaunchedEffect(listState, deepLinkedContact, contacts) {
                val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact)
                if (deepLinkedContactIndex >= 0) {
                  // Scroll to deep linked item
                  listState.animateScrollToItem(deepLinkedContactIndex)
                }
            }
        }
    }
    

در مواردی که هر دو نوع منطق در خط تولید وضعیت رابط کاربری (UI) اعمال می‌شوند، منطق تجاری (business logic) باید همیشه قبل از منطق رابط کاربری (UI logic) اعمال شود . تلاش برای اعمال منطق تجاری (business logic) پس از منطق رابط کاربری (UI logic) به این معنی است که منطق تجاری (business logic) به منطق رابط کاربری (UI logic) وابسته است. بخش‌های بعدی با نگاهی عمیق به انواع مختلف منطق و نگهدارنده‌های وضعیت (state holders) آنها، توضیح می‌دهند که چرا این یک مشکل است.

داده‌ها از لایه تولید داده به رابط کاربری جریان می‌یابند.
شکل ۳. کاربرد منطق در لایه رابط کاربری.

دارندگان ایالتی و مسئولیت‌های آنها

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

این امر مزایای زیر را ایجاد می‌کند:

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

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

انواع دارندگان ایالتی

مشابه انواع حالت‌ها و منطق رابط کاربری، دو نوع نگهدارنده حالت در لایه رابط کاربری وجود دارد که بر اساس رابطه‌شان با چرخه حیات رابط کاربری تعریف می‌شوند:

  • دارنده وضعیت منطق کسب و کار.
  • نگهدارنده‌ی حالت منطقی رابط کاربری.

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

منطق کسب و کار و دارنده وضعیت آن

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

ملک جزئیات
وضعیت رابط کاربری (UI State) را تولید می‌کند. دارندگان وضعیت منطق کسب‌وکار مسئول تولید وضعیت رابط کاربری برای رابط‌های کاربری خود هستند. این وضعیت رابط کاربری اغلب نتیجه پردازش رویدادهای کاربر و خواندن داده‌ها از لایه‌های دامنه و داده است.
از طریق فعالیت و تفریح ​​حفظ می‌شود دارندگان وضعیت منطق کسب‌وکار، وضعیت و خطوط لوله پردازش وضعیت خود را در طول بازآفرینی Activity حفظ می‌کنند و به ارائه یک تجربه کاربری یکپارچه کمک می‌کنند. در مواردی که دارنده وضعیت قادر به حفظ نیست و دوباره ایجاد می‌شود (معمولاً پس از مرگ فرآیند )، دارنده وضعیت باید بتواند به راحتی آخرین وضعیت خود را دوباره ایجاد کند تا یک تجربه کاربری سازگار تضمین شود.
دارای عمر طولانی نگهدارنده‌های وضعیت منطق کسب‌وکار اغلب برای مدیریت وضعیت مقاصد ناوبری استفاده می‌شوند. در نتیجه، آنها اغلب وضعیت خود را در طول تغییرات ناوبری حفظ می‌کنند تا زمانی که از گراف ناوبری حذف شوند.
منحصر به رابط کاربری آن است و قابل استفاده مجدد نیست نگهدارنده‌های وضعیت منطق کسب‌وکار معمولاً وضعیت را برای یک تابع برنامه خاص، مثلاً یک TaskEditViewModel یا یک TaskListViewModel ، تولید می‌کنند و بنابراین فقط برای آن تابع برنامه قابل اجرا هستند. یک نگهدارنده وضعیت می‌تواند از این توابع برنامه در فرم‌فکتورهای مختلف پشتیبانی کند. به عنوان مثال، نسخه‌های موبایل، تلویزیون و تبلت برنامه ممکن است از یک نگهدارنده وضعیت منطق کسب‌وکار استفاده مجدد کنند.

برای مثال، مقصد ناوبری نویسنده را در برنامه "Now in Android " در نظر بگیرید:

برنامه Now in Android نشان می‌دهد که چگونه یک مقصد ناوبری که نشان‌دهنده یک عملکرد اصلی برنامه است، باید دارای نگهدارنده وضعیت منطق کسب‌وکار منحصر به فرد خود باشد.
شکل ۴. برنامه Now in Android.

AuthorViewModel به عنوان دارنده وضعیت منطق کسب و کار، وضعیت رابط کاربری را در این مورد تولید می‌کند:

@HiltViewModel
class AuthorViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val authorsRepository: AuthorsRepository,
    newsRepository: NewsRepository
) : ViewModel() {

    val uiState: StateFlow<AuthorScreenUiState> = 

    // Business logic
    fun followAuthor(followed: Boolean) {
      
    }
}

توجه داشته باشید که AuthorViewModel دارای ویژگی‌هایی است که قبلاً شرح داده شده است:

ملک جزئیات
AuthorScreenUiState را تولید می‌کند. AuthorViewModel داده‌ها را از AuthorsRepository و NewsRepository می‌خواند و از آن داده‌ها برای تولید AuthorScreenUiState استفاده می‌کند. همچنین وقتی کاربر می‌خواهد با واگذاری اختیار به AuthorsRepository ، یک Author دنبال کند یا از دنبال کردن آن انصراف دهد، منطق تجاری را اعمال می‌کند.
به لایه داده دسترسی دارد یک نمونه از AuthorsRepository و NewsRepository در سازنده‌اش به آن ارسال می‌شود و به آن اجازه می‌دهد منطق تجاری دنبال کردن یک Author را پیاده‌سازی کند.
Activity تفریحات زنده می‌مانند از آنجایی که با ViewModel پیاده‌سازی شده است، در طول اجرای سریع Activity حفظ خواهد شد. در صورت مرگ فرآیند، می‌توان شیء SavedStateHandle را خواند تا حداقل اطلاعات مورد نیاز برای بازیابی وضعیت رابط کاربری از لایه داده را فراهم کند.
دارای عمری طولانی ViewModel به گراف ناوبری محدود می‌شود، بنابراین تا زمانی که مقصد نویسنده از گراف ناوبری حذف نشود، وضعیت رابط کاربری در uiState StateFlow در حافظه باقی می‌ماند. استفاده از StateFlow همچنین این مزیت را دارد که منطق تجاری که حالت را تولید می‌کند، کند می‌کند، زیرا حالت فقط در صورتی تولید می‌شود که یک جمع‌کننده از حالت رابط کاربری وجود داشته باشد.
منحصر به فرد در رابط کاربری خود است AuthorViewModel فقط برای مقصد ناوبری نویسنده قابل استفاده است و نمی‌توان آن را در هیچ جای دیگری دوباره استفاده کرد. اگر منطق کاری وجود دارد که در مقاصد ناوبری دوباره استفاده می‌شود، آن منطق کاری باید در یک جزء با دامنه لایه داده یا دامنه محصور شود.

ViewModel به عنوان نگهدارنده وضعیت منطق کسب و کار

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

  • عملیات‌هایی که توسط ViewModelها آغاز می‌شوند، از تغییرات پیکربندی جان سالم به در می‌برند.
  • ادغام با ناوبری :
    • ناوبری، ViewModelها را در حالی که صفحه نمایش در پشته پشتی است، ذخیره می‌کند. این مهم است که داده‌های بارگذاری شده قبلی شما هنگام بازگشت به مقصد، فوراً در دسترس باشند. انجام این کار با یک نگهدارنده وضعیت که چرخه عمر صفحه نمایش قابل ترکیب را دنبال می‌کند، دشوارتر است.
    • ViewModel همچنین هنگامی که مقصد از پشته پشتی برداشته می‌شود، پاک می‌شود و تضمین می‌کند که وضعیت شما به طور خودکار پاک می‌شود. این با گوش دادن به دفع قابل ترکیب که می‌تواند به دلایل مختلفی مانند رفتن به صفحه جدید، به دلیل تغییر پیکربندی یا دلایل دیگر اتفاق بیفتد، متفاوت است.
  • ادغام با سایر کتابخانه‌های Jetpack مانند Hilt .

منطق رابط کاربری و نگهدارنده حالت آن

منطق رابط کاربری، منطقی است که روی داده‌هایی که خود رابط کاربری ارائه می‌دهد، عمل می‌کند. این ممکن است روی وضعیت عناصر رابط کاربری یا روی منابع داده رابط کاربری مانند API مجوزها یا Resources باشد. دارندگان وضعیت که از منطق رابط کاربری استفاده می‌کنند، معمولاً دارای ویژگی‌های زیر هستند:

  • وضعیت رابط کاربری (UI state) را تولید و وضعیت عناصر رابط کاربری (UI elements) را مدیریت می‌کند .
  • از بازآفرینی Activity جان سالم به در نمی‌برد : دارندگان وضعیت که در منطق رابط کاربری میزبانی می‌شوند، اغلب به منابع داده از خود رابط کاربری وابسته هستند و تلاش برای حفظ این اطلاعات در طول تغییرات پیکربندی اغلب باعث نشت حافظه می‌شود. اگر دارندگان وضعیت نیاز به داده‌هایی برای حفظ در طول تغییرات پیکربندی داشته باشند، باید آن را به مؤلفه دیگری که برای حفظ بازآفرینی Activity مناسب‌تر است، واگذار کنند. به عنوان مثال، در Jetpack Compose، وضعیت‌های عنصر رابط کاربری Composable که با توابع remembered ایجاد می‌شوند، اغلب به rememberSaveable واگذار می‌شوند تا وضعیت را در طول بازآفرینی Activity حفظ کنند. نمونه‌هایی از چنین توابعی عبارتند از rememberScaffoldState() و rememberLazyListState() .
  • ارجاعاتی به منابع داده در محدوده رابط کاربری دارد : منابع داده مانند APIهای چرخه عمر و منابع را می‌توان با خیال راحت ارجاع داد و خواند، زیرا دارنده وضعیت منطقی رابط کاربری، چرخه عمری مشابه رابط کاربری دارد.
  • قابل استفاده مجدد در چندین رابط کاربری : نمونه‌های مختلف از یک نگهدارنده وضعیت منطقی رابط کاربری ممکن است در بخش‌های مختلف برنامه دوباره استفاده شوند. به عنوان مثال، یک نگهدارنده وضعیت برای مدیریت رویدادهای ورودی کاربر برای یک گروه تراشه می‌تواند در صفحه جستجو برای تراشه‌های فیلتر و همچنین برای فیلد "to" برای گیرندگان یک ایمیل استفاده شود.

نگهدارنده حالت منطقی رابط کاربری معمولاً با یک کلاس ساده پیاده‌سازی می‌شود. دلیل این امر آن است که خود رابط کاربری مسئول ایجاد نگهدارنده حالت منطقی رابط کاربری است و نگهدارنده حالت منطقی رابط کاربری چرخه عمری مشابه خود رابط کاربری دارد. برای مثال، در Jetpack Compose، نگهدارنده حالت بخشی از Composition است و چرخه عمر Composition را دنبال می‌کند.

موارد فوق را می‌توان در مثال زیر در نمونه Now in Android نشان داد:

اکنون در اندروید از یک نگهدارنده حالت کلاس ساده برای مدیریت منطق رابط کاربری استفاده می‌شود.
شکل ۵. برنامه نمونه Now in Android.

نمونه Now in Android بسته به اندازه صفحه نمایش دستگاه، نوار برنامه پایین یا ریل ناوبری را برای ناوبری خود نشان می‌دهد. صفحه نمایش‌های کوچکتر از نوار برنامه پایین و صفحه نمایش‌های بزرگتر از ریل ناوبری استفاده می‌کنند.

از آنجایی که منطق تصمیم‌گیری برای انتخاب عنصر رابط کاربری ناوبری مناسب که در تابع قابل ترکیب NiaApp استفاده می‌شود، به منطق تجاری بستگی ندارد، می‌توان آن را توسط یک نگهدارنده حالت کلاس ساده به نام NiaAppState مدیریت کرد:

@Stable
class NiaAppState(
    val navController: NavHostController,
    val windowSizeClass: WindowSizeClass
) {

    // UI logic
    val shouldShowBottomBar: Boolean
        get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
            windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact

    // UI logic
    val shouldShowNavRail: Boolean
        get() = !shouldShowBottomBar

   // UI State
    val currentDestination: NavDestination?
        @Composable get() = navController
            .currentBackStackEntryAsState().value?.destination

    // UI logic
    fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }

     /* ... */
}

در مثال قبلی، جزئیات زیر در مورد NiaAppState قابل توجه است:

  • از بازآفرینی Activity جان سالم به در نمی‌برد : NiaAppState در کامپوزیشن با ایجاد آن توسط یک تابع Composable به rememberNiaAppState طبق قراردادهای نامگذاری Compose remembered می‌شود. پس از بازآفرینی Activity ، نمونه قبلی از بین می‌رود و یک نمونه جدید با تمام وابستگی‌های ارسالی آن، متناسب با پیکربندی جدید Activity بازآفرینی شده، ایجاد می‌شود. این وابستگی‌ها ممکن است جدید باشند یا از پیکربندی قبلی بازیابی شده باشند. به عنوان مثال، rememberNavController() در سازنده NiaAppState استفاده می‌شود و به rememberSaveable واگذار می‌شود تا حالت را در بازآفرینی Activity حفظ کند.
  • ارجاعاتی به منابع داده با محدوده UI دارد : ارجاعات به navigationController ، Resources و سایر انواع داده با محدوده چرخه حیات مشابه را می‌توان با خیال راحت در NiaAppState نگهداری کرد زیرا آنها محدوده چرخه حیات یکسانی را به اشتراک می‌گذارند.

برای یک دارنده وضعیت، بین ViewModel و کلاس ساده یکی را انتخاب کنید

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

به طور خلاصه، نمودار زیر موقعیت دارندگان وضعیت (state holders) را در خط تولید UI State نشان می‌دهد:

داده‌ها از لایه تولید داده به لایه رابط کاربری جریان می‌یابند.
شکل 6. دارندگان وضعیت در خط تولید وضعیت رابط کاربری. فلش‌ها به معنی جریان داده هستند.

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

دارندگان سهام ایالتی قابل ترکیب هستند

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

  • یک دارنده حالت منطقی رابط کاربری می‌تواند به یک دارنده حالت منطقی رابط کاربری دیگر وابسته باشد.
  • یک نگهدارنده وضعیت سطح صفحه نمایش می‌تواند به یک نگهدارنده وضعیت منطقی رابط کاربری وابسته باشد.

قطعه کد زیر نشان می‌دهد که چگونه DrawerState در Compose به یک نگهدارنده وضعیت داخلی دیگر، SwipeableState ، وابسته است و چگونه نگهدارنده وضعیت منطقی رابط کاربری یک برنامه می‌تواند به DrawerState وابسته باشد:

@Stable
class DrawerState(/* ... */) {
  internal val swipeableState = SwipeableState(/* ... */)
  // ...
}

@Stable
class MyAppState(
  private val drawerState: DrawerState,
  private val navController: NavHostController
) { /* ... */ }

@Composable
fun rememberMyAppState(
  drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
  navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
  MyAppState(drawerState, navController)
}

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

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

class MyScreenViewModel(/* ... */) {
  val uiState: StateFlow<MyScreenUiState> = /* ... */
  fun doSomething() { /* ... */ }
  fun doAnotherThing() { /* ... */ }
  // ...
}

@Stable
class MyScreenState(
  // DO NOT pass a ViewModel instance to a plain state holder class
  // private val viewModel: MyScreenViewModel,

  // Instead, pass only what it needs as a dependency
  private val someState: StateFlow<SomeState>,
  private val doSomething: () -> Unit,

  // Other UI-scoped types
  private val scaffoldState: ScaffoldState
) {
  /* ... */
}

@Composable
fun rememberMyScreenState(
  someState: StateFlow<SomeState>,
  doSomething: () -> Unit,
  scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
  MyScreenState(someState, doSomething, scaffoldState)
}

@Composable
fun MyScreen(
  modifier: Modifier = Modifier,
  viewModel: MyScreenViewModel = viewModel(),
  state: MyScreenState = rememberMyScreenState(
    someState = viewModel.uiState.map { it.toSomeState() },
    doSomething = viewModel::doSomething
  ),
  // ...
) {
  /* ... */
}

نمودار زیر وابستگی‌های بین رابط کاربری و نگهدارنده‌های حالت مختلف قطعه کد قبلی را نشان می‌دهد:

رابط کاربری بسته به هر دو نگهدارنده حالت منطقی رابط کاربری و نگهدارنده حالت سطح صفحه نمایش
شکل ۷. رابط کاربری وابسته به دارندگان وضعیت‌های مختلف. فلش‌ها به معنی وابستگی‌ها هستند.

نمونه‌ها

نمونه‌های گوگل زیر استفاده از نگهدارنده‌های وضعیت (state holders) را در لایه رابط کاربری نشان می‌دهند. برای مشاهده این راهنمایی در عمل، آنها را بررسی کنید:

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