יציבות בכתיבה

ב-Compose, הסוגים נחשבים ליציבים או לא יציבים. סוג הוא יציב אם הוא לא ניתן לשינוי, או אם פיתוח נייטיב יכול לדעת אם הערך שלו השתנה בין שינויים. סוג הוא לא יציב אם Compose לא יכול לדעת אם הערך שלו השתנה בין קומפוזיציות מחדש.

‫Compose משתמש ביציבות של הפרמטרים של הפונקציה הקומפוזבילית כדי לקבוע איך להשוות בין הקלטים ולהחליט אם אפשר לדלג על הפונקציה הקומפוזבילית במהלך רה-קומפוזיציה (מצב דילוג חזק מופעל כברירת מחדל החל מ-Kotlin 2.0.20):

  • פרמטרים יציבים: Compose משווה בין פרמטרים יציבים באמצעות שוויון מבני (Object.equals()). אם הפרמטרים היציבים של פונקציה קומפוזבילית שווים לערכים הקודמים שלהם, Compose מדלג עליה.
  • פרמטרים לא יציבים: כשמצב דילוג חזק מופעל (ברירת המחדל ב-Kotlin מגרסה 2.0.20 ואילך), Compose משווה בין פרמטרים לא יציבים באמצעות שוויון מופעים (===) ומדלג על הפונקציה הקומפוזבילית אם מועברים אותם מופעי אובייקטים. בגרסאות שקדמו ל-Kotlin 2.0.20 (או כשמצב דילוג חזק מושבת), Compose תמיד יוצר מחדש קומפוזיציה של פונקציה קומפוזבילית עם פרמטרים לא יציבים כשההורה שלה יוצר מחדש קומפוזיציה.

אם האפליקציה מקצה לעיתים קרובות מופעים חדשים של פרמטרים לא יציבים, או מאלצת בדיקות .equals() יקרות על אוספים גדולים על ידי הוספת הערות מרובות למודלים, יכול להיות שתבחינו בהרכבות מחדש מיותרות או בעומס מיותר של השוואות.

במסמך הזה מוסבר איך Compose קובע את היציבות ואיך אפשר לבצע אופטימיזציה כדי לשפר את הביצועים ואת חוויית המשתמש הכוללת.

אובייקטים שלא ניתנים לשינוי

קטעי הקוד הבאים מדגימים את העקרונות הכלליים של יציבות ושל הרכבה מחדש.

הכיתה Contact היא כיתת נתונים שלא ניתנת לשינוי. הסיבה לכך היא שכל הפרמטרים שלו הם פרימיטיבים שמוגדרים באמצעות מילת המפתח val. אחרי שיוצרים מופע של Contact, אי אפשר לשנות את הערך של מאפייני האובייקט. אם תנסו לעשות את זה, תצטרכו ליצור אובייקט חדש.

data class Contact(val name: String, val number: String)

לפונקציה הניתנת ליצירה ContactRow יש פרמטר מסוג Contact.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

מה קורה כשהמשתמש לוחץ על המתג והמצב של selected משתנה:

  1. ‫Compose בודק אם צריך ליצור מחדש את הקוד בתוך ContactRow.
  2. הוא רואה שהארגומנט היחיד של ContactDetails הוא מסוג Contact.
  3. מכיוון ש-Contact היא מחלקת נתונים שלא ניתנת לשינוי, פיתוח נייטיב בטוח שאף אחד מהארגומנטים של ContactDetails לא השתנה.
  4. לכן, Compose מדלג על ContactDetails ולא יוצר אותו מחדש.
  5. מצד שני, הארגומנטים של ToggleButton השתנו, ו-Compose יוצר מחדש את הרכיב הזה.

אובייקטים שניתנים לשינוי

בדוגמה הקודמת השתמשנו באובייקט שלא ניתן לשינוי, אבל אפשר ליצור אובייקט שניתן לשינוי. לדוגמה, בקטע הקוד הבא:

data class Contact(var name: String, var number: String)

כל פרמטר של Contact הוא עכשיו var, ולכן המחלקה כבר לא ניתנת לשינוי. אם המאפיינים שלו ישתנו, מערכת Compose לא תהיה מודעת לכך. הסיבה לכך היא ש-Compose עוקב רק אחרי שינויים באובייקטים של מצב ב-Compose.

ב-Compose, מחלקה כזו נחשבת לא יציבה. במצב דילוג חזק (Kotlin 2.0.20 ואילך), Compose משווה את Contact באמצעות שוויון מופעים (===): שינוי של contact.name במקום לא יפעיל קומפוזיציה מחדש, אבל העברה של מופע Contact חדש שהוקצה עם ערכים זהים עדיין תאלץ את ContactDetails לבצע קומפוזיציה מחדש (ובלי מצב דילוג חזק, ContactDetails מבצע קומפוזיציה מחדש בכל פעם ש-selected משתנה).

הטמעה ב-Compose

כדאי להבין איך Compose קובע אילו פונקציות לדלג עליהן במהלך היצירה מחדש, אבל זה לא חיוני.

כשהקומפיילר של Compose פועל על הקוד, הוא מסמן כל פונקציה וסוג באחד מכמה תגים. התגים האלה משקפים את האופן שבו Compose מטפל בפונקציה או בסוג במהלך קומפוזיציה מחדש.

פונקציות

‫Compose יכול לסמן פונקציות כ-skippable או כ-restartable. שימו לב שהכלי יכול לסמן פונקציה כאחת מהאפשרויות הבאות, כשתי האפשרויות או כאף אחת מהן:

  • ניתן לדילוג: אם המקמפל מסמן פונקציה קומפוזבילית כניתנת לדילוג, Compose יכול לדלג עליה במהלך ההרכבה מחדש אם כל הארגומנטים שלה שווים לערכים הקודמים שלהם. (במצב דילוג חזק ב-Kotlin 2.0.20 ואילך, כל הפונקציות הניתנות להפעלה מחדש ניתנות לדילוג באופן אוטומטי).
  • ניתן להפעלה מחדש: רכיב קומפוזבילי שניתן להפעלה מחדש משמש כ "היקף" שבו אפשר להתחיל את היצירה מחדש. במילים אחרות, הפונקציה יכולה להיות נקודת כניסה שבה Compose יכול להתחיל להריץ מחדש את הקוד ליצירה מחדש אחרי שינויים במצב.

סוגים

פיתוח נייטיב מסמן סוגים כבלתי ניתנים לשינוי או כיציבים. כל סוג הוא אחד מהשניים:

  • לא ניתן לשינוי: פיתוח נייטיב מסמן סוג כלא ניתן לשינוי אם הערך של המאפיינים שלו לא יכול להשתנות אף פעם, וכל השיטות שקופות מבחינת הפניות.
    • שימו לב שכל הסוגים הפרימיטיביים מסומנים כבלתי ניתנים לשינוי. למשל: String,‏ Int ו-Float.
  • יציב: מציין סוג שהמאפיינים שלו יכולים להשתנות אחרי הבנייה. אם המאפיינים האלה משתנים במהלך זמן הריצה, מערכת Compose מודעת לשינויים האלה.

יציבות ניפוי הבאגים

אם האפליקציה שלכם יוצרת מחדש קומפוזבילי שהפרמטרים שלו לא השתנו, קודם בדקו אם מוקצים מופעים חדשים של טיפוסים לא יציבים בכל יצירה מחדש (או, אם מצב דילוג חזק מושבת, אם לקומפוזבילי יש פרמטרים עם מאפייני var או מאפייני val של טיפוס לא יציב).

במדריך ניפוי באגים ביציבות מוסבר בפירוט איך לאבחן בעיות מורכבות שקשורות ליציבות ב-Compose.

פתרון בעיות שקשורות ליציבות

כדי להבין איך לשפר את היציבות של הטמעת Compose, כדאי לעיין במדריך פתרון בעיות שקשורות ליציבות.

סיכום

באופן כללי, חשוב לשים לב לנקודות הבאות:

  • פרמטרים: Compose קובע את היציבות של כל פרמטר של ה-composables כדי להחליט אם להשוות ביניהם באמצעות שוויון מבני (.equals()) או שוויון של מכונות (===) במהלך ה-recomposition.
  • תיקונים מיידיים: אם אתם מבחינים שה-composable לא מדלג וזה גורם לבעיה בביצועים, בדקו אם נוצרים מופעים חדשים של פרמטרים לא יציבים בכל מעבר, או אם נעשה שימוש במאפייני var במקום ב-State.
  • דוחות קומפיילר: אתם יכולים להשתמש בדוחות הקומפיילר כדי לקבוע איזו יציבות מוסקת לגבי המחלקות שלכם.
  • אוספים: ממשקי אוספים סטנדרטיים (List,‏ Set ו-Map) נחשבים לא יציבים ב-Compose כי יכול להיות שההטמעות הבסיסיות שלהם ניתנות לשינוי. במצב דילוג חזק (מופעל כברירת מחדל ב-Kotlin מגרסה 2.0.20 ואילך), אוספים לא יציבים עדיין מדלגים על הרכבה מחדש באמצעות שוויון מופעים (===) כשההפניה לאוסף לא משתנה. חשוב להיזהר כשמחליפים אוספים רגילים באוספים שלא ניתנים לשינוי של Kotlinx או כשמוסיפים הערות לכיתות שמכילות אוספים באמצעות @Immutable או @Stable, כי השוואה של כל רכיב עם .equals() בכל מעבר של יצירה מחדש יכולה להיות יקרה יותר מהיצירה מחדש עצמה.
  • מודולים אחרים: Compose מתייחס למחלקות ממודולים שבהם המהדר של Compose לא פועל כלא יציבות (בהשוואה באמצעות === במצב דילוג חזק). אם מקור נתונים יוצר לעיתים קרובות מופעים מחדש של מודלים קטנים ושטוחים ממודולים שאינם של Compose עם ערכים זהים, אתם יכולים להגדיר קובץ תצורה של יציבות או להשתמש ב-@Stable או ב-@Immutable כאשר .equals() הוא זול.

קריאה נוספת