ב-Compose, ממשק המשתמש לא ניתן לשינוי – אי אפשר לעדכן אותו אחרי שהוא מצויר. אתם יכולים לקבוע את המצב של ממשק המשתמש. בכל פעם שמשתנה המצב של ממשק המשתמש, Compose יוצר מחדש את החלקים של עץ ממשק המשתמש שהשתנו. רכיבים מורכבים יכולים לקבל מצב ולהציג אירועים. לדוגמה, רכיב TextField
מקבל ערך ומציג פונקציית קריאה חוזרת (callback) onValueChange
שמבקשת מהמתנהל של פונקציית הקריאה החוזרת לשנות את הערך.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
מכיוון שרכיבי Compose מקבלים מצב ומציגים אירועים, דפוס זרימת הנתונים החד-כיווני מתאים מאוד ל-Jetpack Compose. במדריך הזה נסביר איך מטמיעים את התבנית של זרימת הנתונים החד-כיוונית ב-Compose, איך מטמיעים אירועים ומחזיקי מצב ואיך עובדים עם ViewModels ב-Compose.
זרימת נתונים חד-כיוונית
זרימת נתונים חד-כיוונית (UDF) היא תבנית עיצוב שבה המצב זורם למטה והאירועים זורמים למעלה. כשמשתמשים בזרימת נתונים חד-כיוונית, אפשר לנתק בין רכיבי ה-Composable שמוצגים בממשק המשתמש לבין החלקים באפליקציה שמאחסנים ומשתנים את המצב.
לולאת העדכון של ממשק המשתמש באפליקציה שמשתמשת בזרימת נתונים חד-כיוונית נראית כך:
- אירוע: חלק מממשק המשתמש יוצר אירוע ומעביר אותו למעלה, למשל לחיצה על לחצן שמועברת ל-ViewModel לטיפול. לחלופין, אירוע מועבר משכבות אחרות באפליקציה, למשל כדי לציין שהסשן של המשתמש פג.
- עדכון המצב: ייתכן שמטפל באירוע ישנה את המצב.
- הצגת המצב: הבעלים של המצב מעביר את המצב, וממשק המשתמש מציג אותו.

שימוש בתבנית הזו ב-Jetpack Compose טומן בחובו כמה יתרונות:
- בדיקות: ניתוק המצב מממשק המשתמש שמוצג בו מאפשר לבדוק את שניהם בנפרד.
- אנקפסולציה של מצב: מכיוון שאפשר לעדכן את המצב רק במקום אחד, ויש רק מקור אחד לאמת לגבי המצב של רכיב ה-composable, יש פחות סיכוי ליצור באגים בגלל מצבים לא עקביים.
- עקביות בממשק המשתמש: כל עדכוני המצב משתקפים מיד בממשק המשתמש באמצעות שימוש ב-observable state holders, כמו
StateFlow
אוLiveData
.
תעבורת נתונים חד-כיוונית ב-Jetpack פיתוח נייטיב
רכיבים מורכבים פועלים על סמך מצב ואירועים. לדוגמה, TextField
מתעדכן רק כשפרמטר value
שלו מתעדכן, והוא חושף קריאה חוזרת (callback) מסוג onValueChange
– אירוע שמבקש לשנות את הערך לערך חדש. Compose מגדיר את האובייקט State
כמאגר ערכים, ושינויים בערך המצב מפעילים יצירת קומפוזיציה מחדש. אפשר לשמור את המצב ב-remember { mutableStateOf(value) }
או ב-rememberSaveable { mutableStateOf(value)
, בהתאם למשך הזמן שבו צריך לזכור את הערך.
הסוג של הערך של ה-composable TextField
הוא String
, כך שהוא יכול להגיע מכל מקום – מערך מקודד, מ-ViewModel או מה-composable ההורה. אין צורך לאחסן אותו באובייקט State
, אבל צריך לעדכן את הערך כשקוראים ל-onValueChange
.
הגדרת פרמטרים שניתנים ליצירה
כשמגדירים את פרמטרים המצב של רכיב ה-Composable, כדאי לזכור את השאלות הבאות:
- עד כמה הרכיב הניתן לקיפול גמיש או מתאים לשימוש חוזר?
- איך הפרמטרים של המצב משפיעים על הביצועים של הרכיב הניתן לקישור?
כדי לעודד ניתוק ושימוש חוזר, כל רכיב מורכב צריך להכיל את כמות המידע המינימלית האפשרית. לדוגמה, כשאתם יוצרים רכיב שמורכב מכמה רכיבים כדי להציג את הכותרת של כתבה חדשותית, מומלץ להעביר רק את המידע שצריך להציג, ולא את הכתבה כולה:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
לפעמים, שימוש בפרמטרים נפרדים גם משפר את הביצועים. לדוגמה, אם News
מכיל מידע נוסף מלבד title
ו-subtitle
, בכל פעם שמעבירים מופע חדש של News
אל Header(news)
, הרכיב הניתן לקיבוץ יתבצע מחדש, גם אם title
ו-subtitle
לא השתנו.
חשוב להקפיד על מספר הפרמטרים שאתם מעבירים. פונקציה עם יותר מדי פרמטרים פוגעת בנוחות השימוש בה, ולכן במקרה כזה עדיף לקבץ אותם בכיתה.
אירועים ב-Compose
כל קלט באפליקציה צריך להיות מיוצג כאירוע: הקשות, שינויים בטקסט ואפילו שעונים או עדכונים אחרים. האירועים האלה משנים את המצב של ממשק המשתמש, ולכן ViewModel
צריך לטפל בהם ולעדכן את המצב של ממשק המשתמש.
שכבת ממשק המשתמש לא אמורה לשנות את המצב שלה מחוץ למטפל אירועים, כי זה עלול לגרום לאי-עקביות ולבאגים באפליקציה.
מומלץ להעביר ערכים לא משתנים ל-lambdas של סטטוס ושל טיפול באירועים. הגישה הזו כוללת את היתרונות הבאים:
- אפשר לעשות שימוש חוזר בקוד.
- מוודאים שממשק המשתמש לא משנה את הערך של המצב ישירות.
- כך אפשר להימנע מבעיות של בו-זמניות, כי מוודאים שהמצב לא משתנה על ידי שרשור אחר.
- בדרך כלל, הפעולה הזו מפחיתה את המורכבות של הקוד.
לדוגמה, אפשר להפעיל פונקציית composable שמקבלת String
ו-lambda כפרמטרים בהרבה הקשרים, והיא ניתנת לשימוש חוזר במידה רבה. נניח שבסרגל האפליקציות העליון באפליקציה שלכם תמיד מוצג טקסט ויש בו לחצן חזרה. אפשר להגדיר רכיב MyAppTopAppBar
גנרי יותר שמקבל את הטקסט ואת ה-handle של לחצן החזרה כפרמטרים:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
תצוגות מודל, מצבים ואירועים: דוגמה
באמצעות ViewModel
ו-mutableStateOf
, אפשר גם להוסיף לאפליקציה תעבורת נתונים חד-כיוונית אם מתקיים אחד מהמצבים הבאים:
- המצב של ממשק המשתמש נחשף באמצעות מאגרי מצב שניתן לצפות בהם, כמו
StateFlow
אוLiveData
. ViewModel
מטפל באירועים שמגיעים מממשק המשתמש או משכבות אחרות באפליקציה, ומעדכן את מאגר המצבים על סמך האירועים.
לדוגמה, כשמטמיעים מסך כניסה, הקשה על הלחצן כניסה אמורה לגרום לאפליקציה להציג גלגל התקדמות וקריאה לרשת. אם ההתחברות תתבצע בהצלחה, האפליקציה תוביל למסך אחר. אם תופיע שגיאה, תוצג באפליקציה סרגל סטטוסים. כך יוצרים מודל של מצב המסך והאירוע:
למסך יש ארבעה מצבים:
- לא מחובר: כשהמשתמש עדיין לא נכנס לחשבון.
- בטיפול: כשהאפליקציה מנסה כרגע להיכנס באמצעות קריאה לרשת.
- שגיאה: כשאירעה שגיאה במהלך הכניסה לחשבון.
- מחובר לחשבון: כשהמשתמש מחובר לחשבון.
אפשר ליצור מודל של המצבים האלה ככיתה אטומה. ViewModel
חושף את המצב בתור State
, מגדיר את המצב הראשוני ומעדכן את המצב לפי הצורך. ה-ViewModel
מטפל גם באירוע הכניסה על ידי חשיפת השיטה onSignIn()
.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
בנוסף ל-API של mutableStateOf
, Compose מספק תוספים ל-LiveData
, ל-Flow
ול-Observable
כדי להירשם כמאזין ולתאר את הערך כמצב.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
מידע נוסף
מידע נוסף על הארכיטקטורה של Jetpack Compose זמין במקורות המידע הבאים:
טעימות
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- State ו-Jetpack פיתוח נייטיב
- שמירת מצב ממשק המשתמש ב'כתיבה'
- טיפול בקלט של משתמשים