מושגים ויישום ב-Jetpack פיתוח נייטיב
תפקיד ממשק המשתמש הוא להציג את נתוני האפליקציה במסך וגם לשמש כנקודת האינטראקציה העיקרית של המשתמש. בכל פעם שהנתונים משתנים, בגלל אינטראקציה של המשתמש (למשל לחיצה על כפתור) או קלט חיצוני (למשל תגובה של רשת), ממשק המשתמש צריך להתעדכן בהתאם לשינויים האלה. בפועל, ממשק המשתמש הוא ייצוג חזותי של מצב האפליקציה כפי שהוא מאוחזר משכבת הנתונים.
עם זאת, נתוני האפליקציה שמתקבלים משכבת הנתונים הם בדרך כלל בפורמט שונה מהמידע שצריך להציג. לדוגמה, יכול להיות שתצטרכו רק חלק מהנתונים לממשק המשתמש, או שתצטרכו למזג שני מקורות נתונים שונים כדי להציג מידע שרלוונטי למשתמש. לא משנה איזו לוגיקה תיישמו, תצטרכו להעביר לממשק המשתמש את כל המידע שהוא צריך כדי להציג את עצמו באופן מלא. שכבת ממשק המשתמש היא פייפליין שמעביר שינויים בנתוני האפליקציה לפורמט שניתן להצגה בממשק המשתמש, ואז מציג אותם.
חשיפת מצב ממשק המשתמש
אחרי שמגדירים את מצב ממשק המשתמש ומחליטים איך לנהל את הייצור של המצב הזה, השלב הבא הוא להציג את המצב שנוצר בממשק המשתמש. מכיוון שאתם משתמשים ב-UDF כדי לנהל את הייצור של מצב, אפשר להתייחס למצב שנוצר כאל זרם – במילים אחרות, ייווצרו לאורך זמן כמה גרסאות של המצב. לכן, כדאי לחשוף את מצב ממשק המשתמש במאגר נתונים שניתן לצפייה, כמו LiveData או StateFlow. הסיבה לכך היא שהממשק יכול להגיב לכל שינוי שמתבצע במצב בלי למשוך נתונים ישירות מ-ViewModel באופן ידני. בנוסף, היתרון של הסוגים האלה הוא שהגרסה העדכנית של מצב ממשק המשתמש תמיד נשמרת במטמון, וזה שימושי לשחזור מהיר של המצב אחרי שינויים בהגדרות.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
דרך נפוצה ליצירת זרם של UiState היא חשיפה של זרם ניתן לשינוי בתור זרם שלא ניתן לשינוי מ-ViewModel – לדוגמה, חשיפה של MutableStateFlow<UiState> בתור StateFlow<UiState>.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
לאחר מכן, ה-ViewModel יכול לחשוף שיטות שמשנות את המצב באופן פנימי, ולפרסם עדכונים לשימוש בממשק המשתמש. לדוגמה, אם צריך לבצע פעולה אסינכרונית, אפשר להפעיל שגרת המשך (coroutine) באמצעות viewModelScope, ולעדכן את המצב הניתן לשינוי אחרי שהפעולה תסתיים.
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)
}
}
}
}
}
שימוש במצב ממשק המשתמש
כשמשתמשים בממשק המשתמש כדי לצרוך נתונים שניתנים לצפייה, חשוב לקחת בחשבון את מחזור החיים של ממשק המשתמש. זה חשוב כי ממשק המשתמש לא צריך לעקוב אחרי מצב ממשק המשתמש כשהתצוגה לא מוצגת למשתמש. בפוסט הזה בבלוג מפורט מידע נוסף על הנושא.
כשמשתמשים ב-LiveData, LifecycleOwner מטפל באופן מרומז בבעיות שקשורות למחזור החיים. כשמשתמשים בתהליכי עבודה, מומלץ לטפל בזה באמצעות היקף הקורוטינה המתאים ו-repeatOnLifecycle API:
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
}
}
}
}
}
הצגת פעולות בתהליך
דרך פשוטה לייצג מצבי טעינה במחלקה 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 }
}
}
}
}
אנימציות
כדי לספק מעברים חלקים וזורמים בניווט ברמה העליונה, כדאי להמתין עד שהנתונים ייטענו במסך השני לפני שמתחילים את האנימציה.
מסגרת התצוגה של Android מספקת נקודות חיבור (hooks) לעיכוב המעברים בין יעדי מקטעים באמצעות ממשקי ה-API postponeEnterTransition() ו-startPostponedEnterTransition(). ממשקי ה-API האלה מאפשרים לוודא שרכיבי ממשק המשתמש במסך השני (בדרך כלל תמונה שאוחזרה מהרשת) מוכנים להצגה לפני שממשק המשתמש מציג אנימציה של המעבר למסך הזה.
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- ייצור מצבים לממשקי משתמש
- מחזיקי מצב ומצב ממשק המשתמש {:#mad-arch}
- מדריך לארכיטקטורת אפליקציות