במדריך בנושא שכבת ממשק המשתמש מוסבר על זרימת נתונים חד-כיוונית (UDF) כאמצעי ליצירה ולניהול של מצב ממשק המשתמש בשכבת ממשק המשתמש.

בנוסף, מודגשים היתרונות של העברת הניהול של פונקציות UDF למחלקה מיוחדת שנקראת מחזיק מצב. אפשר להטמיע את מחזיק המצב באמצעות ViewModel
או באמצעות מחלקה רגילה. במסמך הזה נבחן את מחזיקי המצב ואת התפקיד שלהם בשכבת ממשק המשתמש.
בסוף המסמך הזה תהיה לכם הבנה לגבי ניהול מצב האפליקציה בשכבת ממשק המשתמש, כלומר צינור הייצור של מצב ממשק המשתמש. חשוב שתבינו ותדעו את הנקודות הבאות:
- הסבר על סוגי מצבי ממשק המשתמש שקיימים בשכבת ממשק המשתמש.
- להבין את סוגי הלוגיקה שפועלים במצבי ממשק המשתמש האלה בשכבת ממשק המשתמש.
- לדעת איך לבחור את ההטמעה המתאימה של מחזיק מצב, כמו
ViewModel
או מחלקה.
רכיבים של צינור העיבוד לייצור מצבים לממשקי משתמש
מצב ממשק המשתמש והלוגיקה שיוצרת אותו מגדירים את שכבת ממשק המשתמש.
מצב ממשק המשתמש
מצב ממשק המשתמש הוא המאפיין שמתאר את ממשק המשתמש. יש שני סוגים של מצב ממשק משתמש:
- מצב ממשק המשתמש במסך הוא מה שצריך להציג במסך. לדוגמה, מחלקה
NewsUiState
יכולה להכיל את מאמרי החדשות ומידע אחר שנדרש כדי להציג את ממשק המשתמש. המצב הזה בדרך כלל קשור לשכבות אחרות בהיררכיה כי הוא מכיל נתונים של אפליקציות. - מצב של רכיב בממשק המשתמש מתייחס למאפיינים שמוטמעים ברכיבים בממשק המשתמש ומשפיעים על אופן העיבוד שלהם. יכול להיות שרכיב בממשק המשתמש יוצג או יוסתר, ויהיה לו גופן, גודל גופן או צבע גופן מסוימים. ב-Android Views, הרכיב View מנהל את המצב הזה בעצמו כי הוא מוגדר כבעל מצב, ומציג שיטות לשינוי המצב או לשליפת נתונים לגביו. דוגמה לכך היא השיטות
get
ו-set
של המחלקהTextView
עבור הטקסט שלה. ב-Jetpack Compose, המצב הוא חיצוני לרכיב הניתן להרכבה, ואפשר אפילו להעביר אותו מחוץ לסביבה הקרובה של הרכיב הניתן להרכבה אל פונקציית הרכיב הניתן להרכבה שקוראת לו או אל מחזיק המצב. דוגמה:ScaffoldState
עבור הרכיבScaffold
.
לוגיקה
מצב ממשק המשתמש הוא לא מאפיין סטטי, כי נתוני האפליקציה ואירועי המשתמש גורמים למצב ממשק המשתמש להשתנות לאורך זמן. הלוגיקה קובעת את הפרטים של השינוי, כולל אילו חלקים במצב ממשק המשתמש השתנו, למה הם השתנו ומתי הם צריכים להשתנות.

הלוגיקה באפליקציה יכולה להיות לוגיקה עסקית או לוגיקת ממשק משתמש:
- לוגיקה עסקית היא הטמעה של דרישות מוצר לגבי נתוני אפליקציות. לדוגמה, הוספת מאמר לסימנייה באפליקציה לקריאת חדשות כשמשתמש מקיש על הלחצן. הלוגיקה לשמירת סימנייה בקובץ או במסד נתונים ממוקמת בדרך כלל בשכבות של הדומיין או הנתונים. בדרך כלל, מחזיק המצב מעביר את הלוגיקה הזו לשכבות האלה על ידי הפעלת השיטות שהן חושפות.
- הלוגיקה של ממשק המשתמש קשורה לאופן הצגת המצב של ממשק המשתמש במסך. לדוגמה, קבלת הרמז הנכון בסרגל החיפוש כשמשתמש בוחר קטגוריה, גלילה לפריט מסוים ברשימה או לוגיקת הניווט למסך מסוים כשמשתמש לוחץ על לחצן.
מחזור החיים של Android וסוגי המצבים והלוגיקה של ממשק המשתמש
לשכבת ממשק המשתמש יש שני חלקים: אחד שתלוי במחזור החיים של ממשק המשתמש, והשני שלא תלוי בו. ההפרדה הזו קובעת את מקורות הנתונים שזמינים לכל חלק, ולכן נדרשים סוגים שונים של מצב ממשק משתמש ולוגיקה.
- מחזור חיים בלתי תלוי של ממשק המשתמש: החלק הזה בשכבת ממשק המשתמש מטפל בשכבות של האפליקציה שמפיקות נתונים (שכבות נתונים או שכבות דומיין) ומוגדר על ידי לוגיקה עסקית. מחזור החיים, שינויים בהגדרות ויצירה מחדש של
Activity
בממשק המשתמש עשויים להשפיע על הפעלת צינור הייצור של מצב ממשק המשתמש, אבל לא ישפיעו על התוקף של הנתונים שנוצרו. - תלוי במחזור החיים של ממשק המשתמש: החלק הזה בשכבת ממשק המשתמש עוסק בלוגיקה של ממשק המשתמש, ומושפע ישירות משינויים במחזור החיים או בהגדרות. השינויים האלה משפיעים ישירות על התוקף של מקורות הנתונים שנקראים בתוך המקור, ולכן המצב שלו יכול להשתנות רק כשמחזור החיים שלו פעיל. דוגמאות לכך כוללות הרשאות זמן ריצה וקבלת משאבים שתלויים בהגדרה, כמו מחרוזות מקומיות.
הטבלה הבאה מסכמת את האפשרויות שצוינו למעלה:
UI Lifecycle independent | תלוי במחזור החיים של ממשק המשתמש |
---|---|
לוגיקה עסקית | הלוגיקה של ממשק המשתמש |
מצב ממשק המשתמש במסך |
צינור העיבוד של מצבי ממשק המשתמש
צינור עיבוד הנתונים של ייצור מצבים לממשקי משתמש מתייחס לשלבים שנדרשים כדי לייצר מצבים לממשקי משתמש. השלבים האלה כוללים את יישום סוגי הלוגיקה שהוגדרו קודם, והם תלויים לחלוטין בצרכים של ממשק המשתמש. יכול להיות שחלק מממשקי המשתמש ייהנו מחלקים בצינור העיבוד שלא תלויים במחזור החיים של ממשק המשתמש וגם מחלקים שתלויים בו, או רק מאחד מהם, או מאף אחד מהם.
כלומר, הפרמוטציות הבאות של צינור עיבוד הנתונים של שכבת ממשק המשתמש הן חוקיות:
מצב ממשק המשתמש נוצר ומנוהל על ידי ממשק המשתמש עצמו. לדוגמה, מונה בסיסי פשוט לשימוש חוזר:
@Composable fun Counter() { // The UI state is managed by the UI itself var count by remember { mutableStateOf(0) } Row { Button(onClick = { ++count }) { Text(text = "Increment") } Button(onClick = { --count }) { Text(text = "Decrement") } } }
לוגיקה של ממשק המשתמש ← ממשק משתמש. לדוגמה, הצגה או הסתרה של לחצן שמאפשר למשתמש לעבור לראש הרשימה.
@Composable fun ContactsList(contacts: List<Contact>) { val listState = rememberLazyListState() val isAtTopOfList by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } } // Create the LazyColumn with the lazyListState ... // Show or hide the button (UI logic) based on the list scroll position AnimatedVisibility(visible = !isAtTopOfList) { ScrollToTopButton() } }
לוגיקה עסקית ← ממשק משתמש. רכיב בממשק המשתמש שבו מוצגת התמונה של המשתמש הנוכחי במסך.
@Composable fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() // Call on the UserAvatar Composable to display the photo UserAvatar(picture = uiState.profilePicture) }
לוגיקה עסקית ← לוגיקה של ממשק המשתמש ← ממשק המשתמש. רכיב בממשק המשתמש שניתן לגלול בו כדי להציג את המידע הנכון במסך עבור מצב מסוים של ממשק המשתמש.
@Composable fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() val contacts = uiState.contacts val deepLinkedContact = uiState.deepLinkedContact val listState = rememberLazyListState() // Create the LazyColumn with the lazyListState ... // Perform UI logic that depends on information from business logic if (deepLinkedContact != null && contacts.isNotEmpty()) { LaunchedEffect(listState, deepLinkedContact, contacts) { val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact) if (deepLinkedContactIndex >= 0) { // Scroll to deep linked item listState.animateScrollToItem(deepLinkedContactIndex) } } } }
אם שני סוגי הלוגיקה מופעלים בצינור ליצירת מצב ממשק המשתמש, הלוגיקה העסקית תמיד צריכה להיות מופעלת לפני הלוגיקה של ממשק המשתמש. ניסיון להחיל לוגיקה עסקית אחרי לוגיקת ממשק משתמש מרמז שהלוגיקה העסקית תלויה בלוגיקת ממשק המשתמש. בקטעים הבאים נסביר למה זו בעיה, ונעמיק בסוגים שונים של לוגיקה ובמחזיקי המצב שלהם.

מאחסני מצב והאחריות שלהם
באחריות של בעל הסטייט לאחסן את הסטייט כדי שהאפליקציה תוכל לקרוא אותו. במקרים שבהם נדרשת לוגיקה, הוא פועל כמתווך ומספק גישה למקורות הנתונים שמארחים את הלוגיקה הנדרשת. כך, מחזיק המצב מעביר את הלוגיקה למקור הנתונים המתאים.
היתרונות של השיטה הזו:
- ממשקי משתמש פשוטים: ממשק המשתמש רק קושר את המצב שלו.
- יכולת תחזוקה: אפשר לחזור על הלוגיקה שמוגדרת ב-State Holder בלי לשנות את ממשק המשתמש עצמו.
- אפשרות בדיקה: אפשר לבדוק את ממשק המשתמש ואת הלוגיקה של ייצור המצבים שלו בנפרד.
- קריאות: מי שקורא את הקוד יכול לראות בבירור את ההבדלים בין קוד ההצגה של ממשק המשתמש לבין קוד הייצור של מצב ממשק המשתמש.
לכל רכיב בממשק המשתמש יש יחס של 1:1 עם מאגר המידע על המצב התואם שלו, בלי קשר לגודל או להיקף שלו. בנוסף, בעל מצב צריך להיות מסוגל לקבל ולעבד כל פעולת משתמש שעשויה לגרום לשינוי במצב ממשק המשתמש, ולבצע את השינוי במצב שנובע מכך.
סוגים של בעלי סטטוס
בדומה לסוגים של מצב ולוגיקה של ממשק המשתמש, יש שני סוגים של מחזיקי מצב בשכבת ממשק המשתמש, שמוגדרים לפי הקשר שלהם למחזור החיים של ממשק המשתמש:
- האובייקט שמחזיק את מצב הלוגיקה העסקית.
- מאחסן המצב של הלוגיקה של ממשק המשתמש.
בקטעים הבאים נבחן מקרוב את הסוגים של מחזיקי מצב, החל ממחזיק המצב של הלוגיקה העסקית.
לוגיקה עסקית ובעל המצב שלה
ה-state holders של הלוגיקה העסקית מעבדים אירועים של משתמשים ומשנים נתונים משכבות הנתונים או הדומיין למצב של ממשק המשתמש במסך. כדי לספק חוויית משתמש אופטימלית כשמתייחסים למחזור החיים של Android ולשינויים בהגדרות האפליקציה, למחזיקי מצב שמשתמשים בלוגיקה עסקית צריכים להיות המאפיינים הבאים:
נכס | פרטים |
---|---|
ייצור מצבים לממשק המשתמש | האחריות על יצירת מצב ממשק המשתמש עבור ממשקי המשתמש שלהם מוטלת על מחזיקי המצב של הלוגיקה העסקית. מצב ממשק המשתמש הזה הוא לרוב תוצאה של עיבוד אירועי משתמשים וקריאת נתונים משכבות הדומיין והנתונים. |
הנתונים נשמרים באמצעות יצירה מחדש של פעילות | מחזיקי מצב של לוגיקה עסקית שומרים על המצב שלהם ועל צינורות העיבוד של המצב במהלך Activity יצירה מחדש, וכך עוזרים לספק חוויית משתמש חלקה. במקרים שבהם אי אפשר לשמור את הנתונים של האובייקט ששומר את המצב והוא נוצר מחדש (בדרך כלל אחרי סיום התהליך), האובייקט ששומר את המצב צריך להיות מסוגל ליצור מחדש בקלות את המצב האחרון שלו כדי להבטיח חוויית משתמש עקבית. |
Possess long lived state | לרוב משתמשים ב-state holders של לוגיקה עסקית כדי לנהל את הסטטוס של יעדי ניווט. כתוצאה מכך, הם לרוב שומרים את המצב שלהם גם אחרי שינויים בניווט, עד שהם מוסרים מתרשים הניווט. |
ייחודית לממשק המשתמש שלה ואי אפשר לעשות בה שימוש חוזר | בדרך כלל, מחזיקי מצב של לוגיקה עסקית יוצרים מצב עבור פונקציה מסוימת של אפליקציה, לדוגמה TaskEditViewModel או TaskListViewModel , ולכן הם רלוונטיים רק לפונקציה הזו של האפליקציה. אותו מאגר נתונים יכול לתמוך בפונקציות האלה של האפליקציה במגוון גורמים שמשפיעים על הצורה. לדוגמה, גרסאות של האפליקציה לנייד, לטלוויזיה ולטאבלט עשויות לעשות שימוש חוזר באותו מחזיק מצב של לוגיקה עסקית. |
לדוגמה, נניח שרוצים להגיע ליעד הניווט של המחבר באפליקציה Now in Android:

ה-AuthorViewModel
משמש כמאגר של מצב הלוגיקה העסקית, ובמקרה הזה הוא יוצר את מצב ממשק המשתמש:
@HiltViewModel
class AuthorViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val authorsRepository: AuthorsRepository,
newsRepository: NewsRepository
) : ViewModel() {
val uiState: StateFlow<AuthorScreenUiState> = …
// Business logic
fun followAuthor(followed: Boolean) {
…
}
}
שימו לב של-AuthorViewModel
יש את המאפיינים שצוינו קודם:
נכס | פרטים |
---|---|
התוצאה היא AuthorScreenUiState |
ה-AuthorViewModel קורא נתונים מ-AuthorsRepository ומ-NewsRepository ומשתמש בנתונים האלה כדי ליצור AuthorScreenUiState . הוא גם מחיל לוגיקה עסקית כשהמשתמש רוצה לעקוב אחרי Author או לבטל את המעקב אחריו, על ידי העברה אל AuthorsRepository . |
יש גישה לשכבת הנתונים | מועבר אליו מופע של AuthorsRepository ושל NewsRepository בקונסטרוקטור שלו, כדי שיוכל להטמיע את הלוגיקה העסקית של מעקב אחרי Author . |
ההגדרה נשמרת גם אחרי יצירה מחדש של Activity |
מכיוון שהיא מיושמת באמצעות ViewModel , היא תישמר גם אחרי יצירה מהירה של Activity . במקרה של סיום התהליך, אפשר לקרוא את האובייקט SavedStateHandle כדי לקבל את כמות המידע המינימלית שנדרשת לשחזור מצב ממשק המשתמש משכבת הנתונים. |
Possesses long lived state | ה-ViewModel מוגדר לגרף הניווט, ולכן אלא אם יעד המחבר יוסר מגרף הניווט, מצב ממשק המשתמש ב-uiState StateFlow יישאר בזיכרון. השימוש ב-StateFlow מוסיף גם את היתרון של יישום עצל של הלוגיקה העסקית שמייצרת את המצב, כי המצב נוצר רק אם יש אוסף של מצב ממשק המשתמש. |
ייחודי לממשק המשתמש שלו | ההגדרה AuthorViewModel רלוונטית רק ליעד הניווט של המחבר, ואי אפשר להשתמש בה שוב בשום מקום אחר. אם יש לוגיקה עסקית שנעשה בה שימוש חוזר ביעדי ניווט שונים, צריך להגדיר את הלוגיקה העסקית הזו כרכיב עם היקף של שכבת נתונים או שכבת דומיין. |
ה-ViewModel כמאגר של מצב הלוגיקה העסקית
היתרונות של ViewModels בפיתוח ל-Android הופכים אותם למתאימים למתן גישה ללוגיקה העסקית ולהכנת נתוני האפליקציה להצגה במסך. ההטבות האלו כוללות:
- פעולות שמופעלות על ידי ViewModels ממשיכות לפעול גם אחרי שינויים בהגדרות.
- שילוב עם ניווט:
- הניווט שומר במטמון את ה-ViewModels בזמן שהמסך נמצא במחסנית האחורית. חשוב שהנתונים שנטענו קודם יהיו זמינים באופן מיידי כשחוזרים ליעד. קשה יותר לעשות את זה עם מחזיק מצב שעוקב אחרי מחזור החיים של המסך שניתן להרכבה.
- ה-ViewModel גם מנוקה כשהיעד מוצא מה-backstack, וכך המצב מנוקה באופן אוטומטי. זה שונה מהאזנה להסרה של רכיב שאפשר להוסיף, שיכולה לקרות מכמה סיבות, כמו מעבר למסך חדש, שינוי בהגדרה או סיבות אחרות.
- שילוב עם ספריות אחרות של Jetpack, כמו Hilt.
לוגיקת ממשק המשתמש ומאחסן המצב שלה
לוגיקת ממשק משתמש היא לוגיקה שפועלת על נתונים שמסופקים על ידי ממשק המשתמש עצמו. יכול להיות שההפרה קשורה למצב של רכיבי ממשק המשתמש או למקורות נתונים של ממשק המשתמש, כמו API של הרשאות או Resources
. מאחסני מצבים שמסתמכים על לוגיקה של ממשק משתמש בדרך כלל כוללים את המאפיינים הבאים:
- יוצר מצב של ממשק משתמש ומנהל את המצב של רכיבים בממשק המשתמש.
- לא שורד יצירה מחדש של
Activity
: מחזיקי מצב שמארחים בלוגיקת ממשק המשתמש תלויים לעיתים קרובות במקורות נתונים מממשק המשתמש עצמו, וניסיון לשמור את המידע הזה בשינויים בתצורה גורם לרוב לדליפת זיכרון. אם בעלי מצב צריכים שהנתונים יישמרו גם אחרי שינויים בהגדרות, הם צריכים להעביר את הטיפול לרכיב אחר שמתאים יותר לשמירה אחרי יצירה מחדש.Activity
לדוגמה, ב-Jetpack Compose, מצבים של רכיבי ממשק משתמש שניתנים להרכבה שנוצרו באמצעות פונקציותremembered
לרוב מועברים ל-rememberSaveable
כדי לשמור על המצב במהלך יצירה מחדש שלActivity
. דוגמאות לפונקציות כאלה כוללותrememberScaffoldState()
ו-rememberLazyListState()
. - יש הפניות למקורות נתונים בהיקף ממשק המשתמש: אפשר להפנות למקורות נתונים כמו ממשקי API של מחזור החיים ומשאבים ולקרוא אותם בצורה בטוחה, כי למחזיק מצב הלוגיקה של ממשק המשתמש יש את אותו מחזור חיים כמו לממשק המשתמש.
- אפשר להשתמש בו שוב בכמה ממשקי משתמש: אפשר להשתמש שוב במופעים שונים של אותו מחזיק מצב לוגי של ממשק משתמש בחלקים שונים של האפליקציה. לדוגמה, אפשר להשתמש במחזיק מצב לניהול אירועי קלט של משתמשים בקבוצת צ'יפים בדף חיפוש לצ'יפים של מסננים, וגם בשדה 'אל' של נמעני אימייל.
בדרך כלל, מחזיק מצב הלוגיקה של ממשק המשתמש מיושם באמצעות מחלקה רגילה. הסיבה לכך היא שהממשק אחראי ליצירת מאחסן המצבים של לוגיקת הממשק, ומאחסן המצבים של לוגיקת הממשק חולק את אותו מחזור חיים עם הממשק עצמו. לדוגמה, ב-Jetpack Compose, מחזיק המצב הוא חלק מהקומפוזיציה ופועל בהתאם למחזור החיים של הקומפוזיציה.
הדוגמה הבאה מתוך הדוגמה Now in Android ממחישה את מה שצוין למעלה:

בדוגמה Now in Android מוצג סרגל אפליקציות תחתון או פס ניווט לניווט, בהתאם לגודל המסך של המכשיר. במסכים קטנים יותר משתמשים בסרגל האפליקציות התחתון, ובמסכים גדולים יותר משתמשים בסרגל הניווט.
הלוגיקה להחלטה על רכיב ממשק המשתמש המתאים לניווט שמשמש בפונקציה הניתנת להרכבה NiaApp
לא תלויה בלוגיקה עסקית, ולכן אפשר לנהל אותה באמצעות מחזיק מצב רגיל של מחלקה שנקרא NiaAppState
:
@Stable
class NiaAppState(
val navController: NavHostController,
val windowSizeClass: WindowSizeClass
) {
// UI logic
val shouldShowBottomBar: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
// UI logic
val shouldShowNavRail: Boolean
get() = !shouldShowBottomBar
// UI State
val currentDestination: NavDestination?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
// UI logic
fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }
/* ... */
}
בדוגמה שלמעלה, הפרטים הבאים לגבי NiaAppState
בולטים:
- לא שורד יצירה מחדש של
Activity
:NiaAppState
הואremembered
ב-Composition על ידי יצירה שלו באמצעות פונקציה ComposablerememberNiaAppState
בהתאם למוסכמות השמות של Compose. אחרי שיוצרים מחדש אתActivity
, המופע הקודם אובד ונוצר מופע חדש עם כל התלויות שלו, שמתאים להגדרה החדשה שלActivity
שנוצר מחדש. יכול להיות שהתלות הזו חדשה או שהיא שוחזרה מההגדרה הקודמת. לדוגמה,rememberNavController()
משמש בבונהNiaAppState
והוא מעביר את הפעולה אלrememberSaveable
כדי לשמור על המצב במהלך יצירה מחדש שלActivity
. - כולל הפניות למקורות נתונים בהיקף ממשק המשתמש: אפשר להשתמש ב-
NiaAppState
כדי להפנות ל-navigationController
, ל-Resources
ולסוגים דומים אחרים בהיקף מחזור החיים, כי הם חולקים את אותו היקף מחזור חיים.
בחירה בין ViewModel לבין מחלקה רגילה לניהול מצב
מהקטעים הקודמים, הבחירה בין ViewModel
לבין מחזיק מצב של מחלקה רגילה תלויה בלוגיקה שמוחלת על מצב ממשק המשתמש ובמקורות הנתונים שהלוגיקה פועלת עליהם.
לסיכום, בתרשים הבא מוצג המיקום של מחזיקי המצב בצינור עיבוד הנתונים של מצב ממשק המשתמש:

בסופו של דבר, צריך ליצור מצב של ממשק משתמש באמצעות מחזיקי מצב שקרובים ככל האפשר למקום שבו המצב נצרך. באופן פחות רשמי, מומלץ לשמור את המצב ברמה הנמוכה ביותר האפשרית תוך שמירה על בעלות נכונה. אם אתם צריכים גישה ללוגיקה עסקית ושהמצב של ממשק המשתמש יישמר כל עוד אפשר לנווט למסך, גם אחרי יצירה מחדש של Activity
, ViewModel
הוא בחירה מצוינת להטמעה של מחזיק המצב של הלוגיקה העסקית. במקרים של מצב ממשק משתמש ולוגיקה של ממשק משתמש עם מחזור חיים קצר יותר, מספיק להשתמש במחלקה רגילה שמחזור החיים שלה תלוי רק בממשק המשתמש.
אפשר לשלב בין מאחסני מצב
מחזיקי מצב יכולים להיות תלויים במחזיקי מצב אחרים, כל עוד משך החיים של התלות שווה או קצר יותר. דוגמאות:
- מאחסן מצבים של לוגיקה של ממשק משתמש יכול להיות תלוי במאחסן מצבים אחר של לוגיקה של ממשק משתמש.
- מאחסן מצבים ברמת המסך יכול להיות תלוי במאחסן מצבים של לוגיקת ממשק המשתמש.
בקטע הקוד הבא מוצגות התלויות של Compose's DrawerState
במחזיק מצב פנימי אחר, SwipeableState
, ואיך מחזיק מצב של לוגיקת ממשק המשתמש של אפליקציה יכול להיות תלוי ב-DrawerState
:
@Stable
class DrawerState(/* ... */) {
internal val swipeableState = SwipeableState(/* ... */)
// ...
}
@Stable
class MyAppState(
private val drawerState: DrawerState,
private val navController: NavHostController
) { /* ... */ }
@Composable
fun rememberMyAppState(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
MyAppState(drawerState, navController)
}
דוגמה לתלות שקיימת מעבר לזמן החיים של מאחסן מצב היא מאחסן מצב של לוגיקת ממשק משתמש שתלוי במאחסן מצב ברמת המסך. כך תפחת האפשרות לשימוש חוזר במחזיק המצב עם משך החיים הקצר יותר, ותינתן לו גישה ללוגיקה ולמצב שלא נדרשים לו בפועל.
אם מאגר מצב עם משך חיים קצר יותר צריך מידע מסוים ממאגר מצב עם היקף רחב יותר, צריך להעביר רק את המידע שהוא צריך כפרמטר במקום להעביר את מופע מאגר המצב. לדוגמה, בקטע הקוד הבא, המחלקה של מחזיק מצב הלוגיקה של ממשק המשתמש מקבלת רק את מה שהיא צריכה כפרמטרים מ-ViewModel, במקום להעביר את כל המופע של ViewModel כתלות.
class MyScreenViewModel(/* ... */) {
val uiState: StateFlow<MyScreenUiState> = /* ... */
fun doSomething() { /* ... */ }
fun doAnotherThing() { /* ... */ }
// ...
}
@Stable
class MyScreenState(
// DO NOT pass a ViewModel instance to a plain state holder class
// private val viewModel: MyScreenViewModel,
// Instead, pass only what it needs as a dependency
private val someState: StateFlow<SomeState>,
private val doSomething: () -> Unit,
// Other UI-scoped types
private val scaffoldState: ScaffoldState
) {
/* ... */
}
@Composable
fun rememberMyScreenState(
someState: StateFlow<SomeState>,
doSomething: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
MyScreenState(someState, doSomething, scaffoldState)
}
@Composable
fun MyScreen(
modifier: Modifier = Modifier,
viewModel: MyScreenViewModel = viewModel(),
state: MyScreenState = rememberMyScreenState(
someState = viewModel.uiState.map { it.toSomeState() },
doSomething = viewModel::doSomething
),
// ...
) {
/* ... */
}
התרשים הבא מייצג את התלות בין ממשק המשתמש לבין מחזיקי מצב שונים בקטע הקוד הקודם:

טעימות
בדוגמאות הבאות של Google אפשר לראות איך משתמשים ב-state holders בשכבת ממשק המשתמש. כדאי לעיין בהם כדי לראות את ההנחיות האלה בפועל:
מומלץ
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- שכבת ממשק המשתמש
- ייצור מצבים לממשק המשתמש
- מדריך לארכיטקטורת אפליקציות