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

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

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

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

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

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

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

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

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

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

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

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

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

اساسی ترین آنها تعریف حالت UI است.

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

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

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

UI نتیجه اتصال عناصر UI روی صفحه با حالت UI است.
شکل 3. UI نتیجه اتصال عناصر UI روی صفحه با حالت 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,
    ...
)

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

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

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

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

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

قابلیت + UiState .

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

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

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

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

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

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

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

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

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

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

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

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

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

شکل 5. رابط کاربری یک مورد مقاله در برنامه مطالعه موردی.

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

یک رویداد UI زمانی رخ می دهد که کاربر یک artcile را نشانک می کند. ViewModel لایه داده را از تغییر وضعیت مطلع می کند. لایه داده تغییر داده ها را ادامه می دهد و داده های برنامه را به روز می کند. داده های برنامه جدید با مقاله نشانک شده به ViewModel منتقل می شود، که سپس حالت رابط کاربری جدید را تولید می کند و آن را برای نمایش به عناصر UI ارسال می کند.
شکل 6. نمودار چرخه رویدادها و داده ها در UDF را نشان می دهد.

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

انواع منطق

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

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

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

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

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

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

به عبارت دیگر، UDF به موارد زیر اجازه می دهد:

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

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

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

بازدیدها

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

    val uiState: StateFlow<NewsUiState> = …
}

نوشتن

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

    val uiState: NewsUiState = …
}

برای معرفی LiveData به عنوان یک دارنده داده قابل مشاهده، به این آزمایشگاه کد مراجعه کنید. برای آشنایی مشابه با جریان‌های Kotlin، به جریان‌های Kotlin در Android مراجعه کنید.

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

یک راه متداول برای ایجاد یک جریان UiState ، نمایش یک جریان قابل تغییر پشتیبان به عنوان یک جریان غیرقابل تغییر از ViewModel است - برای مثال، نمایش یک MutableStateFlow<UiState> به عنوان StateFlow<UiState> .

بازدیدها

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

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

نوشتن

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

    var uiState by mutableStateOf(NewsUiState())
        private set

    ...
}

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

بازدیدها

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

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

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

نوشتن

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

ملاحظات اضافی

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

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

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

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

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

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

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

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

بازدیدها

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

نوشتن

@Composable
fun LatestNewsScreen(
    viewModel: NewsViewModel = viewModel()
) {
    // Show UI elements based on the viewModel.uiState
}

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

یک راه ساده برای نمایش حالت های بارگیری در کلاس UiState با یک فیلد بولی است:

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

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

بازدیدها

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

نوشتن

@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.
    }
}

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

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

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

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

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

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

رشته و همزمانی

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

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

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

صفحه بندی

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

انیمیشن ها

به منظور ارائه انتقال ناوبری در سطح بالا روان و روان، ممکن است بخواهید قبل از شروع انیمیشن منتظر بمانید تا صفحه دوم داده ها را بارگیری کند. چارچوب نمای Android قلاب هایی را برای به تاخیر انداختن انتقال بین مقصدهای قطعه با APIهای postponeEnterTransition() و startPostponedEnterTransition() فراهم می کند. این API ها راهی برای اطمینان از اینکه عناصر UI در صفحه دوم (معمولاً تصویری که از شبکه واکشی می شود) آماده نمایش قبل از اینکه رابط کاربری انتقال به آن صفحه را متحرک کند، فراهم می کند. برای جزئیات بیشتر و جزئیات پیاده سازی، نمونه Android Motion را ببینید.

نمونه ها

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

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