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

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

داده ها به صورت یک طرفه از لایه داده به UI جریان می یابد.
شکل 1 : جریان داده های یک طرفه

همچنین مزایای واگذاری مدیریت UDF به یک طبقه خاص به نام دارنده دولتی را برجسته می کند. شما می توانید یک نگهدارنده حالت را از طریق ViewModel یا یک کلاس ساده پیاده سازی کنید. این سند نگاه دقیق تری به دارندگان حالت و نقشی که در لایه UI ایفا می کنند دارد.

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

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

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

حالت UI و منطقی که آن را تولید می کند لایه UI را تعریف می کند.

وضعیت رابط کاربری

حالت رابط کاربری خصوصیتی است که UI را توصیف می کند. دو نوع حالت رابط کاربری وجود دارد:

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

منطق

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

منطق حالت رابط کاربری را ایجاد می کند
شکل 2 : منطق به عنوان تولید کننده حالت UI

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

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

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

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

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

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

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

خط لوله تولید دولتی UI

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

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

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

    @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")
            }
        }
    }
    
  • منطق UI → UI. برای مثال، نمایش یا پنهان کردن دکمه‌ای که به کاربر اجازه می‌دهد به بالای فهرست بپرد.

    @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()
        }
    }
    
  • منطق کسب و کار → UI. یک عنصر رابط کاربری که عکس کاربر فعلی را روی صفحه نمایش می دهد.

    @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)
    }
    
  • منطق کسب و کار → منطق UI → UI. یک عنصر رابط کاربری که پیمایش می کند تا اطلاعات مناسب را برای یک وضعیت رابط کاربری مشخص روی صفحه نمایش دهد.

    @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 اعمال شود . تلاش برای اعمال منطق تجاری پس از منطق UI به این معنی است که منطق تجاری به منطق UI بستگی دارد. بخش‌های زیر به این موضوع می‌پردازند که چرا این مشکل از طریق نگاهی عمیق به انواع منطق‌های مختلف و دارندگان حالت آنها.

داده ها از لایه تولید کننده داده به UI جریان می یابد
شکل 3 : کاربرد منطق در لایه UI

صاحبان دولت و مسئولیت های آنها

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

این باعث ایجاد مزایای زیر می شود:

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

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

انواع صاحبان دولتی

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

  • دارنده حالت منطق کسب و کار.
  • دارنده حالت منطقی UI.

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

منطق تجاری و دارنده حالت آن

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

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

به عنوان مثال مقصد ناوبری نویسنده را در برنامه «اکنون در Android » در نظر بگیرید:

برنامه Now in Android نشان می دهد که چگونه یک مقصد ناوبری که یک عملکرد اصلی برنامه را نشان می دهد باید دارای حالت منطق تجاری منحصر به فرد خود باشد.
شکل 4 : برنامه Now in Android

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

@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 استفاده می کند. همچنین زمانی که کاربر می‌خواهد Author با تفویض اختیار به AuthorsRepository دنبال کند یا فالوو کند، منطق تجاری را اعمال می‌کند.
به لایه داده دسترسی دارد نمونه‌ای از AuthorsRepository و NewsRepository در سازنده‌اش به آن ارسال می‌شود و به آن اجازه می‌دهد منطق تجاری دنبال کردن یک Author را پیاده‌سازی کند.
Survives Activity تفریحی از آنجایی که با ViewModel پیاده‌سازی می‌شود، در بازآفرینی سریع Activity حفظ می‌شود. در مورد مرگ فرآیند، شی SavedStateHandle را می توان از آن خواند تا حداقل اطلاعات مورد نیاز برای بازگرداندن حالت رابط کاربری از لایه داده را فراهم کند.
دارای حالت عمر طولانی است ViewModel به نمودار ناوبری محدود می شود، بنابراین، مگر اینکه مقصد نویسنده از نمودار ناوبری حذف شود، وضعیت رابط کاربری در uiState StateFlow در حافظه باقی می ماند. استفاده از StateFlow همچنین مزیت استفاده از منطق تجاری را که حالت تنبل را ایجاد می‌کند، اضافه می‌کند، زیرا حالت تنها در صورتی تولید می‌شود که یک جمع‌کننده از حالت UI وجود داشته باشد.
منحصر به فرد برای رابط کاربری آن است AuthorViewModel فقط برای مقصد ناوبری نویسنده قابل استفاده است و نمی توان آن را در جای دیگری دوباره استفاده کرد. اگر منطق تجاری وجود داشته باشد که در مقاصد ناوبری مجدداً مورد استفاده قرار گیرد، آن منطق تجاری باید در یک جزء داده یا لایه دامنه محصور شود.

ViewModel به عنوان یک دارنده حالت منطق تجاری

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

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

منطق UI و دارنده حالت آن

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

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

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

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

اکنون در اندروید از یک نگهدارنده حالت کلاس ساده برای مدیریت منطق UI استفاده می کند
شکل 5 : برنامه نمونه Now in Android

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

از آنجایی که منطق تعیین عنصر UI ناوبری مناسب مورد استفاده در تابع ترکیب پذیر 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 استفاده می شود و برای حفظ حالت در سراسر Activity بازآفرینی، به rememberSaveable واگذار می کند.
  • دارای ارجاعاتی به منابع داده با محدوده رابط کاربری است : ارجاعات به navigationController ، Resources و سایر انواع مشابه با محدوده چرخه حیات را می توان با خیال راحت در NiaAppState نگهداری کرد زیرا محدوده چرخه عمر یکسانی دارند.

بین یک ViewModel و کلاس ساده برای دارنده حالت انتخاب کنید

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

به طور خلاصه، نمودار زیر موقعیت دارندگان دولت را در خط لوله تولید UI State نشان می دهد:

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

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

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

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

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

قطعه کد زیر نشان می‌دهد که چگونه 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)
}

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

اگر دارنده حالت با عمر کوتاه‌تر به اطلاعات خاصی از یک دارنده حالت با دامنه بالاتر نیاز دارد، به جای ارسال نمونه دارنده حالت، فقط اطلاعات مورد نیاز خود را به عنوان پارامتر ارسال کنید. به عنوان مثال، در قطعه کد زیر، کلاس دارنده حالت منطقی UI، به جای اینکه کل نمونه 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
  ),
  // ...
) {
  /* ... */
}

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

UI بستگی به هر دو حالت منطقی UI و نگهدارنده حالت سطح صفحه نمایش دارد
شکل 7 : UI بسته به دارندگان حالت های مختلف. فلش ها به معنای وابستگی ها هستند.

نمونه ها

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

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