لایه رابط کاربری

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

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

در یک معماری معمول، عناصر رابط کاربری لایه رابط کاربری به دارندگان وضعیت (state holders) وابسته هستند که به نوبه خود به کلاس‌هایی از لایه داده یا لایه دامنه اختیاری (optional domain layer) وابسته هستند.
شکل ۱. نقش لایه رابط کاربری در معماری برنامه.

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

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

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

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

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

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

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

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

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

اساسی‌ترین این موارد، تعریف وضعیت رابط کاربری (UI state) است.

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

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

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

رابط کاربری (UI) نتیجه‌ی اتصال عناصر رابط کاربری روی صفحه با حالت رابط کاربری است.
شکل ۳. رابط کاربری نتیجه‌ی اتصال عناصر رابط کاربری روی صفحه با حالت رابط کاربری است.

مطالعه موردی را در نظر بگیرید: برای برآورده کردن الزامات برنامه News، اطلاعات مورد نیاز برای رندر کامل رابط کاربری را می‌توان در یک کلاس داده 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,
    ...
)

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

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

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

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

قراردادهای نامگذاری در این راهنما

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

قابلیت + UiState .

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

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

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

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

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

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

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

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

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

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

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

  • ViewModel وضعیت (state) را نگه می‌دارد و در معرض نمایش قرار می‌دهد تا توسط رابط کاربری (UI) مورد استفاده قرار گیرد. وضعیت رابط کاربری، داده‌های برنامه است که توسط ViewModel تبدیل می‌شوند.
  • رابط کاربری، ViewModel را از رویدادهای کاربر مطلع می‌کند.
  • ViewModel اقدامات کاربر را مدیریت کرده و وضعیت را به‌روزرسانی می‌کند.
  • وضعیت به‌روزرسانی‌شده برای رندر شدن به رابط کاربری بازگردانده می‌شود.
  • موارد فوق برای هر رویدادی که باعث جهش حالت شود، تکرار می‌شود.

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

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

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

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

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

انواع منطق

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

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

منطق رابط کاربری را در رابط کاربری نگه دارید، نه در ViewModel، به خصوص وقتی که شامل انواع رابط کاربری مانند Context باشد. اگر رابط کاربری پیچیده‌تر شود و بخواهید منطق رابط کاربری را به کلاس دیگری واگذار کنید تا قابلیت تست و جداسازی دغدغه‌ها را بهبود بخشید، می‌توانید یک کلاس ساده به عنوان نگهدارنده حالت ایجاد کنید . کلاس‌های ساده ایجاد شده در رابط کاربری می‌توانند وابستگی‌های SDK اندروید را بپذیرند زیرا از چرخه عمر رابط کاربری پیروی می‌کنند. اشیاء ViewModel طول عمر بیشتری دارند.

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

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

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

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

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

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

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

هنگام استفاده از UDF برای مدیریت تولید حالت، می‌توانید حالت تولید شده را به عنوان یک جریان در نظر بگیرید - به عبارت دیگر، چندین نسخه از حالت در طول زمان تولید می‌شوند. حالت UI را در یک نگهدارنده داده قابل مشاهده مانند StateFlow نمایش دهید. این به UI اجازه می‌دهد تا بدون نیاز به دریافت دستی داده‌ها مستقیماً از ViewModel، به هرگونه تغییر ایجاد شده در حالت واکنش نشان دهد. این همچنین این مزیت را دارد که همیشه آخرین نسخه حالت UI در حافظه پنهان (cache) قرار دارد که برای بازیابی سریع حالت پس از تغییرات پیکربندی مفید است.

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

    val uiState: NewsUiState = 
}

برای آشنایی با جریان‌های کاتلین، به جریان‌های کاتلین در اندروید مراجعه کنید. برای یادگیری نحوه استفاده از StateFlow به عنوان یک نگهدارنده داده قابل مشاهده، به بخش Advanced State و Side Effects در Jetpack Compose codelab مراجعه کنید.

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

یک روش رایج برای ایجاد جریانی از UiState ، افشای یک ویژگی mutableStateOf با یک private set است که حالت را در داخل ViewModel قابل تغییر اما برای رابط کاربری فقط خواندنی نگه می‌دارد.

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

    var uiState by mutableStateOf(NewsUiState())
        private set

    ...
}

سپس ViewModel می‌تواند متدهایی را که به صورت داخلی state را تغییر می‌دهند، نمایش دهد و به‌روزرسانی‌هایی را برای مصرف UI منتشر کند. برای مثال، موردی را در نظر بگیرید که نیاز به انجام یک عمل ناهمزمان دارید. می‌توانید با استفاده از viewModelScope یک coroutine راه‌اندازی کنید و سپس state قابل تغییر را پس از اتمام به‌روزرسانی کنید.

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 API مانند distinctUntilChanged() ضروری باشد.

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

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

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

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

@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(),
    ...
)

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

نخ‌کشی و همزمانی

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

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

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

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

صفحه بندی

کتابخانه Paging در رابط کاربری با نوعی به نام 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 مراجعه کنید.

منابع اضافی

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

نمونه‌ها

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

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