لایه میانای کاربر

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

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

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

مطالعه موردی پایه

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

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

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

معماری لایه واسط کاربر

اصطلاح UI به عناصر UI مانند محتویات و توابع ترکیب‌پذیر که داده‌ها را نمایش می‌دهند اشاره دارد. برای ساختن «میاناهای کاربر Android»، Jetpack Compose جعبه‌ابزار توصیه‌شده است. ازآنجایی‌که نقش لایه داده نگهداری، مدیریت، و ارائه دسترسی به داده‌های برنامه است، لایه میانای کاربری باید مراحل زیر را انجام دهد:

  1. داده‌های برنامه را مصرف می‌کند و آن‌ها را به داده‌هایی تبدیل می‌کند که واسط کاربر به‌راحتی می‌تواند ارائه کند.
  2. داده‌های قابل‌پردازش با میانای کاربری را مصرف می‌کند و آن‌ها را به عناصر میانای کاربری برای ارائه به کاربر تبدیل می‌کند.
  3. رویدادهای ورودی کاربر را از آن عناصر واسط کاربر مونتاژشده مصرف کنید و درصورت نیاز، تأثیرات آن‌ها را در داده‌های واسط کاربر منعکس کنید.
  4. مراحل ۱ تا ۳ را تا زمانی که لازم است تکرار کنید.

بقیه این راهنما نحوه پیاده‌سازی لایه واسط کاربر را که این مراحل را انجام می‌دهد نشان می‌دهد. به‌طور خاص، این راهنما وظایف و مفاهیم زیر را پوشش می‌دهد:

  • نحوه تعریف وضعیت میانای کاربر
  • جریان داده یک‌طرفه (UDF) به‌عنوان روشی برای تولید و مدیریت وضعیت رابط کاربری
  • نحوه آشکار کردن وضعیت میانای کاربر با انواع داده‌های قابل‌مشاهده براساس اصول UDF
  • نحوه پیاده‌سازی میانای کاربری که وضعیت میانای کاربر قابل‌مشاهده را مصرف می‌کند

بنیادی‌ترین آن‌ها تعریف وضعیت میانای کاربر است.

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

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

به‌عبارت دیگر، اگر میانای کاربری چیزی است که کاربر می‌بیند، وضعیت میانای کاربری چیزی است که برنامه می‌گوید کاربر باید ببیند. مانند دو روی یک سکه، «میانای کاربر» نمایش تصویری وضعیت «میانای کاربر» است. هر تغییری در وضعیت میانای کاربر بلافاصله در میانای کاربر منعکس می‌شود.

«میانای کاربر» نتیجه پیوند دادن عناصر «میانای کاربر» روی صفحه‌نمایش با وضعیت «میانای کاربر» است.
شکل ۳. «میانای کاربر» نتیجه پیوند عناصر «میانای کاربر» روی صفحه‌نمایش با وضعیت «میانای کاربر» است.

مطالعه موردی را درنظر بگیرید: برای برآورده کردن الزامات برنامه «اخبار»، اطلاعات لازم برای ارائه کامل واسط کاربر می‌تواند در NewsUiState کلاس داده‌ای که به‌صورت زیر تعریف شده است کپسوله شود:

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    // ...
)

برای اطلاعات بیشتر درباره وضعیت واسط کاربر، وضعیت و Jetpack Compose را ببینید.

تغییرناپذیری

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

برای مثال، مطالعه موردی قبلی را درنظر بگیرید. اگر پرچم bookmarked در شیء NewsItemUiState از وضعیت واسط کاربر در کلاس Activity به‌روز شود، آن پرچم با لایه داده به‌عنوان منبع وضعیت نشانک‌گذاری مقاله رقابت می‌کند. کلاس‌های داده تغییرناپذیر برای جلوگیری از این نوع ناسازگاری بسیار مفید هستند.

قواعد نام‌گذاری در این راهنما

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

کارکرد + UiState.

برای مثال، وضعیت صفحه‌ای که اخبار را نمایش می‌دهد ممکن است NewsUiState نامیده شود، و وضعیت یک خبر در فهرست اخبار ممکن است NewsItemUiState باشد.

مدیریت وضعیت با «جریان داده یک‌طرفه»

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

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

این بخش درباره «جریان داده یک‌طرفه» (UDF)، الگوی معماری‌ای که به اجرای این تفکیک سالم مسئولیت کمک می‌کند، بحث می‌کند.

نگهدارنده‌های حالت

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

در حالت دوم، پیاده‌سازی معمول نمونه‌ای از ViewModel است، اگرچه بسته به الزامات برنامه، یک کلاس ساده ممکن است کافی باشد. برنامه «اخبار» از مطالعه موردی، برای مثال، از کلاس NewsViewModel به‌عنوان نگه‌دارنده وضعیت برای تولید وضعیت میانای کاربر برای صفحه‌نمایش نشان‌داده‌شده در آن بخش استفاده می‌کند.

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

جریان داده برنامه از لایه داده به ViewModel. وضعیت میانای کاربر
    از ViewModel به عناصر میانای کاربر جاری می‌شود و رویدادها از عناصر میانای کاربر
    به ViewModel برمی‌گردد.
شکل ۴. نمودار نحوه عملکرد UDF در معماری برنامه.

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

  • ‫ViewModel وضعیت را نگه می‌دارد و آن را برای مصرف واسط کاربر آشکار می‌کند. وضعیت واسط کاربر داده‌های برنامه است که توسط ViewModel تبدیل شده است.
  • «میانای کاربر» رویدادهای کاربر را به «نمای مدل» اطلاع می‌دهد.
  • ‫ViewModel کنش‌های کاربر را مدیریت می‌کند و وضعیت را به‌روز می‌کند.
  • وضعیت به‌روزرسانی‌شده برای پردازش به میانای کاربری برگردانده می‌شود.
  • مورد بالا برای هر رویدادی که باعث تغییر وضعیت می‌شود تکرار می‌شود.

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

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

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

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

بخش‌های زیر نگاهی دقیق‌تر به رویدادهایی دارند که باعث تغییر وضعیت می‌شوند و نحوه پردازش آن‌ها بااستفاده از UDF را توضیح می‌دهند.

انواع منطق

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

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

منطق واسط کاربر را در واسط کاربر نگه دارید، نه در «نمای مدل»، به‌ویژه زمانی که شامل انواع واسط کاربر مثل Context باشد. اگر پیچیدگی میانای کاربری افزایش یافت و می‌خواهید منطق میانای کاربری را به کلاس دیگری واگذار کنید تا قابلیت آزمایش و تفکیک نگرانی‌ها را بهبود دهید، می‌توانید کلاس ساده‌ای به‌عنوان نگهدارنده وضعیت ایجاد کنید. کلاس‌های ساده‌ای که در «واسط کاربر» ایجاد می‌شوند می‌توانند وابستگی‌های «کیت توسعه نرم‌افزار Android» را داشته باشند زیرا چرخه حیات «واسط کاربر» را دنبال می‌کنند؛ اشیاء «مدل نمایشی» طول عمر بیشتری دارند.

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

چرا از UDF استفاده کنیم؟

«مدل UDF» چرخه تولید حالت را همان‌طور که در «شکل ۴» نشان داده شده است مدل‌سازی می‌کند. همچنین مکان شروع تغییرات وضعیت، مکان تبدیل آن‌ها، و مکان مصرف نهایی آن‌ها را از هم جدا می‌کند. این جداسازی به میانای کاربری اجازه می‌دهد دقیقاً کاری را که نامش نشان می‌دهد انجام دهد: با مشاهده تغییرات وضعیت، اطلاعات را نمایش دهد و با انتقال این تغییرات به «نمای مدل»، هدف کاربر را منتقل کند.

به‌عبارت دیگر، «تابع تعریف‌شده توسط کاربر» امکانات زیر را فراهم می‌کند:

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

نمایش وضعیت واسط کاربر

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

وقتی از UDF برای مدیریت تولید وضعیت استفاده می‌کنید، می‌توانید وضعیت تولیدشده را یک جاری‌سازی درنظر بگیرید—به‌عبارت دیگر، نسخه‌های متعددی از وضعیت درطول زمان تولید می‌شود. وضعیت واسط کاربر را در نگهدارنده داده‌های قابل‌مشاهده‌ای مثل StateFlow آشکار کنید. این کار به واسط کاربر امکان می‌دهد بدون نیاز به واکشی دستی داده‌ها مستقیماً از ViewModel، به هر تغییری که در وضعیت ایجاد می‌شود واکنش نشان دهد. این کار همچنین این مزیت را دارد که همیشه جدیدترین نسخه وضعیت میانای کاربری در حافظه نهان ذخیره می‌شود که برای بازیابی سریع وضعیت پس‌از تغییرات پیکربندی مفید است.

class NewsViewModel(
    // ...
) : ViewModel() {

    val uiState: NewsUiState = /* ... */
}

برای آشنایی با جریان‌های Kotlin، به جریان‌های Kotlin در Android مراجعه کنید. برای آشنایی با نحوه استفاده از StateFlow به‌عنوان نگهدارنده داده‌های قابل‌مشاهده، به codelab حالت پیشرفته و عوارض جانبی در Jetpack Compose مراجعه کنید.

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

روش معمول ایجاد جاری‌سازی UiState این است که mutableStateOf دارایی را با private set نمایان کنید، وضعیت را در داخل ViewModel تغییرپذیر نگه دارید اما برای «میانای کاربری» فقط خواندنی باشد.

class NewsViewModel(
    // ...
) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    // ...
}

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

class NewsViewModel(
    private val repository: NewsRepository,
    // ...
) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                uiState = uiState.copy(newsItems = newsItems)
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                val messages = getMessagesFromThrowable(ioe)
                uiState = uiState.copy(userMessages = messages)
            }
        }
    }
}

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

سایر ملاحظات

علاوه‌بر راهنمایی‌های قبلی، هنگام آشکار کردن وضعیت «میانای کاربری»، موارد زیر را درنظر بگیرید:

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

    data class NewsUiState(
        val isSignedIn: Boolean = false,
        val isPremium: Boolean = false,
        val newsItems: List<NewsItemUiState> = listOf()
    )
    
    val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium

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

  • وضعیت‌های رابط کاربری: یک جاری‌سازی یا چند جاری‌سازی؟ اصل راهنمای کلیدی برای انتخاب بین نمایش وضعیت واسط کاربر در یک جاری‌سازی یا در چندین جاری‌سازی، رابطه بین موارد منتشرشده است. بزرگ‌ترین مزایای نمایش تک‌جریانی عبارت‌اند از راحتی و ثبات داده‌ها: مصرف‌کنندگان وضعیت همیشه جدیدترین اطلاعات را در هر زمان دردسترس دارند. بااین‌حال، مواردی وجود دارد که جاری‌سازی‌های جداگانه وضعیت از ViewModel ممکن است مناسب باشد:

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

    • تفاوت UiState: هرچه فیلدهای بیشتری در یک شیء UiState وجود داشته باشد، احتمال اینکه جاری‌سازی در نتیجه به‌روزرسانی یکی از فیلدهای آن منتشر شود بیشتر است. ازآنجایی‌که عناصر واسط کاربر سازوکار مقایسه برای درک اینکه آیا انتشارهای متوالی متفاوت هستند یا یکسان ندارند، هر انتشار باعث به‌روزرسانی عنصر واسط کاربر می‌شود. این یعنی ممکن است استفاده از روش‌های میانای برنامه‌سازی کاربردی Flow مثل distinctUntilChanged() برای کاهش ضروری باشد.

برای اطلاعات بیشتر درباره پرداز و وضعیت واسط کاربر، چرخه حیات عناصر ترکیب‌شدنی را ببینید.

مصرف وضعیت واسط کاربر

برای مصرف کردن جاری‌سازی UiState شیء در واسط کاربر، از عامل پایانه برای نوع داده قابل‌مشاهده‌ای که استفاده می‌کنید استفاده کنید. برای مثال، برای جاری‌سازی‌های Kotlin از روش collect() یا انواع آن استفاده کنید.

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

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

نمایش عملیات درحال انجام

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

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    // ...
)

مقدار این پرچم نشان‌دهنده وجود یا عدم وجود نوار پیشرفت در واسط کاربر است.

@Composable
fun LatestNewsScreen(
    modifier: Modifier = Modifier,
    viewModel: NewsViewModel = viewModel()
) {
    Box(modifier.fillMaxSize()) {

        if (viewModel.uiState.isFetchingArticles) {
            CircularProgressIndicator(Modifier.align(Alignment.Center))
        }

        // Add other UI elements. For example, the list.
    }
}

نمایش خطاها در صفحه

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

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

data class Message(val id: Long, val message: String)

data class NewsUiState(
    val userMessages: List<Message> = listOf(),
    // ...
)

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

رشته‌بندی و هم‌زمان‌گرایی

مطمئن شوید همه کارهای انجام‌شده در ViewModel main-safe باشد—فراخوانی از رشته اصلی ایمن باشد. لایه‌های داده و دامنه مسئول انتقال کار به ردیفی دیگر هستند.

اگر یک ViewModel عملیات طولانی‌مدت انجام می‌دهد، پس مسئولیت انتقال آن منطق به یک رشته پس‌زمینه نیز برعهده آن است. روال‌های هم‌زمان Kotlin روشی عالی برای مدیریت عملیات هم‌زمان است و «اجزای معماری Jetpack» از آن‌ها پشتیبانی داخلی می‌کند. برای کسب اطلاعات بیشتر درباره استفاده از روال‌های مشترک در برنامه‌های Android، روال‌های مشترک Kotlin در Android را ببینید.

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

برای اطلاعات بیشتر درباره پیمایش میانای کاربر، پیمایش ۳ را ببینید.

صفحه‌بندی

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

مثال زیر میانای برنامه‌سازی کاربردی Compose کتابخانه «صفحه‌بندی» را نشان می‌دهد:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

پویانمایی

برای ارائه انتقال‌های ناوبری سطح بالا روان، بهتر است قبل‌از شروع پویانمایی، منتظر بمانید تا داده‌های صفحه دوم بار شود.

برای اطلاعات بیشتر درباره گذارهای ناوبری، Navigation 3 و گذارهای عنصر مشترک در «نوشتن» را ببینید.

منابع بیشتر

محتوا را می‌بیند

نمونه‌ها

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