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

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

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

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

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

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

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

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

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

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

  1. داده‌های برنامه را مصرف کند و آن را به داده‌هایی تبدیل کند که واسط کاربر بتواند به‌راحتی ارائه کند.
  2. داده‌های قابل‌پرداخت واسط کاربر را مصرف می‌کند و آن را به عناصر واسط کاربر برای ارائه به کاربر تبدیل می‌کند.
  3. Consume user input events from those assembled UI elements and reflect their effects in the UI data as needed.
  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 به‌روز شود، آن پرچم با لایه داده به‌عنوان منبع وضعیت نشانک‌گذاری‌شده مقاله رقابت می‌کند. کلاس‌های داده تغییرناپذیر برای جلوگیری از این نوع ناسازگاری بسیار مفید هستند.

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

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

functionality + UiState.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

انواع منطق

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

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

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

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

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

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

به عبارت دیگر، UDF موارد زیر را امکان‌پذیر می‌کند:

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

نمایش دادن حالت واسط کاربر

After you define your UI state and determine how you will manage the production of that state, the next step is to present the produced state to the UI.

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

سایر ملاحظات

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

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

    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 diffing: هرچه فیلدهای بیشتری در شیء UiState وجود داشته باشد، احتمال اینکه جریان درنتیجه به‌روزرسانی یکی از فیلدهایش منتشر شود بیشتر است. چون عناصر میانای کاربر مکانیزم تفاوتی ندارند تا متوجه شوند که آیا انتشار متوالی متفاوت است یا یکسان، هر انتشار باعث به‌روزرسانی عنصر میانای کاربر می‌شود. این یعنی ممکن است کاهش بااستفاده از روش‌های میانای برنامه‌سازی کاربردی Flow مانند distinctUntilChanged() ضروری باشد.

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

مصرف کردن حالت واسط کاربر

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

هنگام مصرف نگهدارنده‌های داده‌های دیده‌شدنی در میانای کاربر، حتماً چرخه حیات میانای کاربر را درنظر بگیرید. وقتی composable به کاربر نمایش داده نمی‌شود، رابط کاربری را وادار به مشاهده وضعیت رابط کاربری نکنید. برای کسب اطلاعات بیشتر درباره این موضوع، این پست وبلاگ را ببینید. هنگام استفاده از جریان‌ها، بهتر است نگرانی‌های مربوط به چرخه حیات را با محدوده کوروتین مناسب و 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 کتابخانه Paging را نشان می‌دهد:

@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")
        }
    }
}

پویانمایی

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

برای اطلاعات بیشتر درباره گذارهای ناوبری، ناوبری ۳ و گذارهای عنصر مشترک در Compose را ببینید.

منابع بیشتر

محتوا را مشاهده می‌کند

نمونه‌ها

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